Fix ActivityEmbedding animation flicker

1. Fix flicker when rotate display with expanded TaskFragment:
   Only startChangeTransition if organizer request resizing, so we won't
   accidentally start the change transition for other resizing case,
   such as display rotation.
2. Fix flciker when exit split to fullscreen:
   Make sure to wait activity window redraw when the embedded
   TaskFragment is changing.

Fix: 258755905
Test: manual verify open fullscreen fixed-portrait embedded activity
      from landscape display.
Test: atest WmTests:WindowStateTests
Change-Id: Ifc825075abfb7c27461f23281f41425f66c3f680
This commit is contained in:
Chris Li
2022-11-11 15:56:36 +08:00
parent 32c9995560
commit 99604f0f62
5 changed files with 126 additions and 42 deletions

View File

@@ -288,6 +288,12 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Nullable
private final IBinder mFragmentToken;
/**
* Whether to delay the call to {@link #updateOrganizedTaskFragmentSurface()} when there is a
* configuration change.
*/
private boolean mDelayOrganizedTaskFragmentSurfaceUpdate;
/**
* Whether to delay the last activity of TaskFragment being immediately removed while finishing.
* This should only be set on a embedded TaskFragment, where the organizer can have the
@@ -2273,35 +2279,41 @@ class TaskFragment extends WindowContainer<WindowContainer> {
@Override
public void onConfigurationChanged(Configuration newParentConfig) {
// Task will animate differently.
if (mTaskFragmentOrganizer != null) {
mTmpPrevBounds.set(getBounds());
}
super.onConfigurationChanged(newParentConfig);
final boolean shouldStartChangeTransition = shouldStartChangeTransition(mTmpPrevBounds);
if (shouldStartChangeTransition) {
initializeChangeTransition(mTmpPrevBounds);
}
if (mTaskFragmentOrganizer != null) {
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.isCollecting(this)) {
// TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
// update the surface here if it is not collected by Shell transition.
updateOrganizedTaskFragmentSurface();
} else if (!mTransitionController.isShellTransitionsEnabled()
&& !shouldStartChangeTransition) {
// Update the surface here instead of in the organizer so that we can make sure
// it can be synced with the surface freezer for legacy app transition.
updateOrganizedTaskFragmentSurface();
}
updateOrganizedTaskFragmentSurface();
}
sendTaskFragmentInfoChanged();
}
void deferOrganizedTaskFragmentSurfaceUpdate() {
mDelayOrganizedTaskFragmentSurfaceUpdate = true;
}
void continueOrganizedTaskFragmentSurfaceUpdate() {
mDelayOrganizedTaskFragmentSurfaceUpdate = false;
updateOrganizedTaskFragmentSurface();
}
private void updateOrganizedTaskFragmentSurface() {
if (mDelayOrganizedTaskFragmentSurfaceUpdate) {
return;
}
if (mTransitionController.isShellTransitionsEnabled()
&& !mTransitionController.isCollecting(this)) {
// TaskFragmentOrganizer doesn't have access to the surface for security reasons, so
// update the surface here if it is not collected by Shell transition.
updateOrganizedTaskFragmentSurfaceUnchecked();
} else if (!mTransitionController.isShellTransitionsEnabled() && !isAnimating()) {
// Update the surface here instead of in the organizer so that we can make sure
// it can be synced with the surface freezer for legacy app transition.
updateOrganizedTaskFragmentSurfaceUnchecked();
}
}
private void updateOrganizedTaskFragmentSurfaceUnchecked() {
final SurfaceControl.Transaction t = getSyncTransaction();
updateSurfacePosition(t);
updateOrganizedTaskFragmentSurfaceSize(t, false /* forceUpdate */);
@@ -2355,7 +2367,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
}
/** Whether we should prepare a transition for this {@link TaskFragment} bounds change. */
private boolean shouldStartChangeTransition(Rect startBounds) {
boolean shouldStartChangeTransition(Rect startBounds) {
if (mTaskFragmentOrganizer == null || !canStartChangeTransition()) {
return false;
}
@@ -2375,7 +2387,7 @@ class TaskFragment extends WindowContainer<WindowContainer> {
void setSurfaceControl(SurfaceControl sc) {
super.setSurfaceControl(sc);
if (mTaskFragmentOrganizer != null) {
updateOrganizedTaskFragmentSurface();
updateOrganizedTaskFragmentSurfaceUnchecked();
// If the TaskFragmentOrganizer was set before we created the SurfaceControl, we need to
// emit the callbacks now.
sendTaskFragmentAppeared();

View File

@@ -146,6 +146,8 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
@VisibleForTesting
final ArrayMap<IBinder, TaskFragment> mLaunchTaskFragments = new ArrayMap<>();
private final Rect mTmpBounds = new Rect();
WindowOrganizerController(ActivityTaskManagerService atm) {
mService = atm;
mGlobalLock = atm.mGlobalLock;
@@ -710,7 +712,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
}
private int applyTaskChanges(Task tr, WindowContainerTransaction.Change c) {
int effects = 0;
int effects = applyChanges(tr, c, null /* errorCallbackToken */);
final SurfaceControl.Transaction t = c.getBoundsChangeTransaction();
if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
@@ -767,6 +769,7 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private int applyDisplayAreaChanges(DisplayArea displayArea,
WindowContainerTransaction.Change c) {
final int[] effects = new int[1];
effects[0] = applyChanges(displayArea, c, null /* errorCallbackToken */);
if ((c.getChangeMask()
& WindowContainerTransaction.Change.CHANGE_IGNORE_ORIENTATION_REQUEST) != 0) {
@@ -787,6 +790,27 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
return effects[0];
}
private int applyTaskFragmentChanges(@NonNull TaskFragment taskFragment,
@NonNull WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
if (taskFragment.isEmbeddedTaskFragmentInPip()) {
// No override from organizer for embedded TaskFragment in a PIP Task.
return 0;
}
// When the TaskFragment is resized, we may want to create a change transition for it, for
// which we want to defer the surface update until we determine whether or not to start
// change transition.
mTmpBounds.set(taskFragment.getBounds());
taskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
final int effects = applyChanges(taskFragment, c, errorCallbackToken);
if (taskFragment.shouldStartChangeTransition(mTmpBounds)) {
taskFragment.initializeChangeTransition(mTmpBounds);
}
taskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
mTmpBounds.set(0, 0, 0, 0);
return effects;
}
private int applyHierarchyOp(WindowContainerTransaction.HierarchyOp hop, int effects,
int syncId, @Nullable Transition transition, boolean isInLockTaskMode,
@NonNull CallerInfo caller, @Nullable IBinder errorCallbackToken,
@@ -1452,20 +1476,15 @@ class WindowOrganizerController extends IWindowOrganizerController.Stub
private int applyWindowContainerChange(WindowContainer wc,
WindowContainerTransaction.Change c, @Nullable IBinder errorCallbackToken) {
sanitizeWindowContainer(wc);
if (wc.asTaskFragment() != null && wc.asTaskFragment().isEmbeddedTaskFragmentInPip()) {
// No override from organizer for embedded TaskFragment in a PIP Task.
return 0;
if (wc.asDisplayArea() != null) {
return applyDisplayAreaChanges(wc.asDisplayArea(), c);
} else if (wc.asTask() != null) {
return applyTaskChanges(wc.asTask(), c);
} else if (wc.asTaskFragment() != null) {
return applyTaskFragmentChanges(wc.asTaskFragment(), c, errorCallbackToken);
} else {
return applyChanges(wc, c, errorCallbackToken);
}
int effects = applyChanges(wc, c, errorCallbackToken);
if (wc instanceof DisplayArea) {
effects |= applyDisplayAreaChanges(wc.asDisplayArea(), c);
} else if (wc instanceof Task) {
effects |= applyTaskChanges(wc.asTask(), c);
}
return effects;
}
@Override

View File

@@ -1535,10 +1535,11 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
mWmService.makeWindowFreezingScreenIfNeededLocked(this);
// If the orientation is changing, or we're starting or ending a drag resizing action,
// then we need to hold off on unfreezing the display until this window has been
// redrawn; to do that, we need to go through the process of getting informed by the
// application when it has finished drawing.
if (getOrientationChanging() || dragResizingChanged) {
// or we're resizing an embedded Activity, then we need to hold off on unfreezing the
// display until this window has been redrawn; to do that, we need to go through the
// process of getting informed by the application when it has finished drawing.
if (getOrientationChanging() || dragResizingChanged
|| isEmbeddedActivityResizeChanged()) {
if (dragResizingChanged) {
ProtoLog.v(WM_DEBUG_RESIZE,
"Resize start waiting for draw, "
@@ -4144,6 +4145,20 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP
return mActivityRecord == null || mActivityRecord.isFullyTransparentBarAllowed(frame);
}
/**
* Whether this window belongs to a resizing embedded activity.
*/
private boolean isEmbeddedActivityResizeChanged() {
if (mActivityRecord == null || !isVisibleRequested()) {
// No need to update if the window is in the background.
return false;
}
final TaskFragment embeddedTaskFragment = mActivityRecord.getOrganizedTaskFragment();
return embeddedTaskFragment != null
&& mDisplayContent.mChangingContainers.contains(embeddedTaskFragment);
}
boolean isDragResizeChanged() {
return mDragResizing != computeDragResizing();
}

View File

@@ -118,10 +118,13 @@ public class TaskFragmentTest extends WindowTestsBase {
doReturn(true).when(mTaskFragment).isVisibleRequested();
clearInvocations(mTransaction);
mTaskFragment.deferOrganizedTaskFragmentSurfaceUpdate();
mTaskFragment.setBounds(endBounds);
assertTrue(mTaskFragment.shouldStartChangeTransition(startBounds));
mTaskFragment.initializeChangeTransition(startBounds);
mTaskFragment.continueOrganizedTaskFragmentSurfaceUpdate();
// Surface reset when prepare transition.
verify(mTaskFragment).initializeChangeTransition(startBounds);
verify(mTransaction).setPosition(mLeash, 0, 0);
verify(mTransaction).setWindowCrop(mLeash, 0, 0);
@@ -166,7 +169,7 @@ public class TaskFragmentTest extends WindowTestsBase {
mTaskFragment.setBounds(endBounds);
verify(mTaskFragment, never()).initializeChangeTransition(any());
assertFalse(mTaskFragment.shouldStartChangeTransition(startBounds));
}
/**

View File

@@ -95,6 +95,8 @@ import android.view.InsetsState;
import android.view.InsetsVisibilities;
import android.view.SurfaceControl;
import android.view.WindowManager;
import android.window.ITaskFragmentOrganizer;
import android.window.TaskFragmentOrganizer;
import androidx.test.filters.SmallTest;
@@ -797,6 +799,39 @@ public class WindowStateTests extends WindowTestsBase {
assertThat(mWm.mResizingWindows).doesNotContain(win);
}
@Test
public void testEmbeddedActivityResizing_clearAllDrawn() {
final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
mAtm.mTaskFragmentOrganizerController.registerOrganizer(
ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder()));
final Task task = createTask(mDisplayContent);
final TaskFragment embeddedTf = createTaskFragmentWithEmbeddedActivity(task, organizer);
final ActivityRecord embeddedActivity = embeddedTf.getTopMostActivity();
final WindowState win = createWindow(null /* parent */, TYPE_APPLICATION, embeddedActivity,
"App window");
doReturn(true).when(embeddedActivity).isVisible();
embeddedActivity.mVisibleRequested = true;
makeWindowVisible(win);
win.mLayoutSeq = win.getDisplayContent().mLayoutSeq;
// Set the bounds twice:
// 1. To make sure there is no orientation change after #reportResized, which can also cause
// #clearAllDrawn.
// 2. Make #isLastConfigReportedToClient to be false after #reportResized, so it can process
// to check if we need redraw.
embeddedTf.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
embeddedTf.setBounds(0, 0, 1000, 2000);
win.reportResized();
embeddedTf.setBounds(500, 0, 1000, 2000);
// Clear all drawn when the embedded TaskFragment is in mDisplayContent.mChangingContainers.
win.updateResizingWindowIfNeeded();
verify(embeddedActivity, never()).clearAllDrawn();
mDisplayContent.mChangingContainers.add(embeddedTf);
win.updateResizingWindowIfNeeded();
verify(embeddedActivity).clearAllDrawn();
}
@Test
public void testCantReceiveTouchWhenAppTokenHiddenRequested() {
final WindowState win0 = createWindow(null, TYPE_APPLICATION, "win0");