Set transform hint before rotation transaction is applied

Unlike legacy transition applies setDisplayProjection immediately
when applying rotation change, shell transition applies the
display projection in shell side when the animation is ready.
So if uses the surface when receiving configuration change but
the transaction is not applied yet, it may get an intermediate
inconsistent transform hint.

With this change:
Rotation changed -> send config -> apply pending transaction with
fixed transform hint -> transition ready -> shell starts animation
and applies display projection and unsets fixed transform hint.

Bug: 270282500
Test: atest SurfaceControlTests#testSurfaceChangedOnRotation
Change-Id: Id4ad45adb9719b078f46040a09f786092c325a8a
This commit is contained in:
Riddle Hsu
2023-04-24 19:43:25 +08:00
parent dfc317e1aa
commit 5b5f97ac72
5 changed files with 76 additions and 0 deletions

View File

@@ -2181,6 +2181,14 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
}
mWmService.mDisplayManagerInternal.performTraversal(transaction);
if (shellTransitions) {
// Before setDisplayProjection is applied by the start transaction of transition,
// set the transform hint to avoid using surface in old rotation.
getPendingTransaction().setFixedTransformHint(mSurfaceControl, rotation);
// The sync transaction should already contains setDisplayProjection, so unset the
// hint to restore the natural state when the transaction is applied.
transaction.unsetFixedTransformHint(mSurfaceControl);
}
scheduleAnimation();
mWmService.mRotationWatcherController.dispatchDisplayRotationChange(mDisplayId, rotation);

View File

@@ -806,6 +806,13 @@ class Transition implements BLASTSyncEngine.TransactionReadyListener {
if (c.getSnapshot() != null) {
t.reparent(c.getSnapshot(), null);
}
// The fixed transform hint was set in DisplayContent#applyRotation(). Make sure to
// clear the hint in case the start transaction is not applied.
if (c.hasFlags(FLAG_IS_DISPLAY) && c.getStartRotation() != c.getEndRotation()
&& c.getContainer() != null) {
t.unsetFixedTransformHint(WindowContainer.fromBinder(c.getContainer().asBinder())
.asDisplayContent().mSurfaceControl);
}
}
for (int i = info.getRootCount() - 1; i >= 0; --i) {
final SurfaceControl leash = info.getRoot(i).getLeash();

View File

@@ -725,6 +725,7 @@ class TransitionController {
// Run state-validation checks when no transitions are active anymore.
if (!inTransition()) {
validateStates();
mAtm.mWindowManager.onAnimationFinished();
}
}

View File

@@ -79,6 +79,7 @@
android:turnScreenOn="true"
android:showWhenLocked="true" />
<activity android:name="com.android.server.wm.ActivityOptionsTest$MainActivity"
android:configChanges="screenLayout|screenSize|smallestScreenSize|orientation"
android:turnScreenOn="true"
android:showWhenLocked="true" />
<activity android:name="com.android.server.wm.ScreenshotTests$ScreenshotActivity"

View File

@@ -16,19 +16,34 @@
package com.android.server.wm;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.fail;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.os.Parcel;
import android.platform.test.annotations.Presubmit;
import android.util.Log;
import android.view.SurfaceControl;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Class for testing {@link SurfaceControl}.
*
@@ -106,6 +121,50 @@ public class SurfaceControlTests {
}
}
@Test
public void testSurfaceChangedOnRotation() {
final Instrumentation instrumentation = getInstrumentation();
final Context context = instrumentation.getContext();
final Activity activity = instrumentation.startActivitySync(new Intent().setComponent(
new ComponentName(context, ActivityOptionsTest.MainActivity.class))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
final SurfaceView sv = new SurfaceView(activity);
final AtomicInteger surfaceChangedCount = new AtomicInteger();
instrumentation.runOnMainSync(() -> activity.setContentView(sv));
sv.getHolder().addCallback(new SurfaceHolder.Callback() {
@Override
public void surfaceCreated(@NonNull SurfaceHolder holder) {
}
@Override
public void surfaceChanged(@NonNull SurfaceHolder holder, int format, int width,
int height) {
surfaceChangedCount.getAndIncrement();
Log.i("surfaceChanged", "width=" + width + " height=" + height
+ " getTransformHint="
+ sv.getViewRootImpl().getSurfaceControl().getTransformHint());
}
@Override
public void surfaceDestroyed(@NonNull SurfaceHolder holder) {
}
});
final int rotation = activity.getResources().getConfiguration()
.windowConfiguration.getRotation();
activity.setRequestedOrientation(activity.getResources().getConfiguration().orientation
== Configuration.ORIENTATION_PORTRAIT
? ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
: ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
instrumentation.getUiAutomation().syncInputTransactions();
instrumentation.waitForIdleSync();
final int newRotation = activity.getResources().getConfiguration()
.windowConfiguration.getRotation();
final int count = surfaceChangedCount.get();
activity.finishAndRemoveTask();
// The first count is triggered from creation, so the target number is 2.
if (rotation != newRotation && count > 2) {
fail("More than once surfaceChanged for rotation change: " + count);
}
}
private SurfaceControl buildTestSurface() {
return new SurfaceControl.Builder()
.setContainerLayer()