From 0f536fa99489c5cfd2d266e94798b19540e2cd12 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Thu, 14 Jan 2021 21:03:15 +0800 Subject: Better IME transition while switching app with recents (6/N) Add WindowState#{freezeInsetsState, clearFrozenInsetsState} to freeze the insets state of the window to keep the insets state when the window is in app exiting transition, to ensure the exiting window won't receive unexpected insets changes from the next window. And, in order to easy maintain the logic of dispatching all windows's insets changed event in InsetsStateController, instead of adding complicated rule in InsetsStateController to judge if the window can disatch insets change, we can replace with WindowState#isReadyToDispatchInsetsState() to wrap the dispatch judgement. Bug: 166736352 Test: atest CtsWindowManagerDeviceTestCases WmTests Test: atest WindowStateTests#testSetFreezeInsetsState Test: atest WindowContainerTests#testFreezeInsetsStateWhenAppTransition Change-Id: I56418733c1abbd73e88a8918a5d55ecc15344c5e --- .../java/com/android/server/wm/DisplayContent.java | 9 +++++- .../android/server/wm/InsetsStateController.java | 12 ++++---- .../com/android/server/wm/WindowContainer.java | 10 ++++++- .../java/com/android/server/wm/WindowState.java | 33 ++++++++++++++++++++++ .../android/server/wm/WindowContainerTests.java | 27 ++++++++++++++++++ .../com/android/server/wm/WindowStateTests.java | 23 +++++++++++++++ 6 files changed, 106 insertions(+), 8 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 2a40500258c9..8af809cad082 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4102,8 +4102,15 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * Callbacks when the given type of {@link WindowContainer} animation finished running in the * hierarchy. */ - void onWindowAnimationFinished(int type) { + void onWindowAnimationFinished(@NonNull WindowContainer wc, int type) { if (type == ANIMATION_TYPE_APP_TRANSITION || type == ANIMATION_TYPE_RECENTS) { + // Unfreeze the insets state of the frozen target when the animation finished if exists. + final Task task = wc.asTask(); + if (task != null) { + task.forAllWindows(w -> { + w.clearFrozenInsetsState(); + }, true /* traverseTopToBottom */); + } removeImeSurfaceImmediately(); } } diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java index 398049fb97c3..267f67759a24 100644 --- a/services/core/java/com/android/server/wm/InsetsStateController.java +++ b/services/core/java/com/android/server/wm/InsetsStateController.java @@ -74,7 +74,7 @@ class InsetsStateController { private final ArraySet mPendingControlChanged = new ArraySet<>(); private final Consumer mDispatchInsetsChanged = w -> { - if (w.isVisible()) { + if (w.isReadyToDispatchInsetsState()) { w.notifyInsetsChanged(); } }; @@ -117,7 +117,8 @@ class InsetsStateController { final @InternalInsetsType int type = provider != null ? provider.getSource().getType() : ITYPE_INVALID; return getInsetsForTarget(type, target.getWindowingMode(), target.isAlwaysOnTop(), - isAboveIme(target)); + isAboveIme(target), + target.getFrozenInsetsState() != null ? target.getFrozenInsetsState() : mState); } InsetsState getInsetsForWindowMetrics(@NonNull WindowManager.LayoutParams attrs) { @@ -132,7 +133,7 @@ class InsetsStateController { final @WindowingMode int windowingMode = token != null ? token.getWindowingMode() : WINDOWING_MODE_UNDEFINED; final boolean alwaysOnTop = token != null && token.isAlwaysOnTop(); - return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token)); + return getInsetsForTarget(type, windowingMode, alwaysOnTop, isAboveIme(token), mState); } private boolean isAboveIme(WindowContainer target) { @@ -180,9 +181,8 @@ class InsetsStateController { * @see #getInsetsForWindowMetrics */ private InsetsState getInsetsForTarget(@InternalInsetsType int type, - @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme) { - InsetsState state = mState; - + @WindowingMode int windowingMode, boolean isAlwaysOnTop, boolean aboveIme, + @NonNull InsetsState state) { if (type != ITYPE_INVALID) { state = new InsetsState(state); state.removeSource(type); diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java index 03fca1137e47..dd4ee877c05b 100644 --- a/services/core/java/com/android/server/wm/WindowContainer.java +++ b/services/core/java/com/android/server/wm/WindowContainer.java @@ -2684,6 +2684,14 @@ class WindowContainer extends ConfigurationContainer< @Nullable ArrayList sources) { final Task task = asTask(); if (task != null && !enter && !task.isHomeOrRecentsRootTask()) { + if (AppTransition.isClosingTransitOld(transit)) { + // Freezes the insets state when the window is in app exiting transition, to + // ensure the exiting window won't receive unexpected insets changes from the + // next window. + task.forAllWindows(w -> { + w.freezeInsetsState(); + }, true /* traverseTopToBottom */); + } mDisplayContent.showImeScreenshot(); } final Pair adapters = getAnimationAdapter(lp, @@ -2831,7 +2839,7 @@ class WindowContainer extends ConfigurationContainer< } mSurfaceAnimationSources.clear(); if (mDisplayContent != null) { - mDisplayContent.onWindowAnimationFinished(type); + mDisplayContent.onWindowAnimationFinished(this, type); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 093106f123e5..feaaaec5a18f 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -713,6 +713,12 @@ class WindowState extends WindowContainer implements WindowManagerP private @Nullable InsetsSourceProvider mControllableInsetProvider; private final InsetsState mRequestedInsetsState = new InsetsState(); + /** + * Freeze the insets state in some cases that not necessarily keeps up-to-date to the client. + * (e.g app exiting transition) + */ + private InsetsState mFrozenInsetsState; + @Nullable InsetsSourceProvider mPendingPositionChanged; private static final float DEFAULT_DIM_AMOUNT_DEAD_WINDOW = 0.5f; @@ -758,6 +764,33 @@ class WindowState extends WindowContainer implements WindowManagerP } } + /** + * Set a freeze state for the window to ignore dispatching its insets state to the client. + * + * Used to keep the insets state for some use cases. (e.g. app exiting transition) + */ + void freezeInsetsState() { + if (mFrozenInsetsState == null) { + mFrozenInsetsState = new InsetsState(getInsetsState(), true /* copySources */); + } + } + + void clearFrozenInsetsState() { + mFrozenInsetsState = null; + } + + InsetsState getFrozenInsetsState() { + return mFrozenInsetsState; + } + + /** + * Check if the insets state of the window is ready to dispatch to the client when invoking + * {@link InsetsStateController#notifyInsetsChanged}. + */ + boolean isReadyToDispatchInsetsState() { + return isVisible() && mFrozenInsetsState == null; + } + void seamlesslyRotateIfAllowed(Transaction transaction, @Rotation int oldRotation, @Rotation int rotation, boolean requested) { // Invisible windows and the wallpaper do not participate in the seamless rotation animation diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java index b0b8afd6c3a4..df5b48a038f3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowContainerTests.java @@ -22,6 +22,8 @@ import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSET; import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.TRANSIT_CLOSE; +import static android.view.WindowManager.TRANSIT_OLD_TASK_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_TASK_OPEN; import static android.view.WindowManager.TRANSIT_OPEN; import static android.window.DisplayAreaOrganizer.FEATURE_DEFAULT_TASK_CONTAINER; @@ -978,6 +980,31 @@ public class WindowContainerTests extends WindowTestsBase { assertEquals(200, listener.mConfiguration.densityDpi); } + @Test + public void testFreezeInsetsStateWhenAppTransition() { + final Task stack = createTaskStackOnDisplay(mDisplayContent); + final Task task = createTaskInStack(stack, 0 /* userId */); + final ActivityRecord activity = createActivityRecord(mDisplayContent, task); + final WindowState win = createWindow(null, TYPE_BASE_APPLICATION, activity, "win"); + task.getDisplayContent().prepareAppTransition(TRANSIT_CLOSE); + spyOn(win); + doReturn(true).when(task).okToAnimate(); + ArrayList sources = new ArrayList<>(); + sources.add(activity); + + // Simulate the task applying the exit transition, verify the main window of the task + // will be set the frozen insets state. + task.applyAnimation(null, TRANSIT_OLD_TASK_CLOSE, false /* enter */, + false /* isVoiceInteraction */, sources); + verify(win).freezeInsetsState(); + + // Simulate the task transition finished, verify the frozen insets state of the window + // will be reset. + task.onAnimationFinished(ANIMATION_TYPE_APP_TRANSITION, + task.mSurfaceAnimator.getAnimation()); + verify(win).clearFrozenInsetsState(); + } + /* Used so we can gain access to some protected members of the {@link WindowContainer} class */ private static class TestWindowContainer extends WindowContainer { private final int mLayer; diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 263aa194a108..3231f8b6551a 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -810,4 +810,27 @@ public class WindowStateTests extends WindowTestsBase { WINDOWING_MODE_SPLIT_SCREEN_PRIMARY); assertFalse(sameTokenWindow.needsRelativeLayeringToIme()); } + + @Test + public void testSetFreezeInsetsState() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + spyOn(app); + doReturn(true).when(app).isVisible(); + + // Set freezing the insets state to make the window ignore to dispatch insets changed. + final InsetsState expectedState = new InsetsState(app.getInsetsState(), + true /* copySources */); + app.freezeInsetsState(); + assertEquals(expectedState, app.getFrozenInsetsState()); + assertFalse(app.isReadyToDispatchInsetsState()); + assertEquals(expectedState, app.getInsetsState()); + mDisplayContent.getInsetsStateController().notifyInsetsChanged(); + verify(app, never()).notifyInsetsChanged(); + + // Unfreeze the insets state to make the window can dispatch insets changed. + app.clearFrozenInsetsState(); + assertTrue(app.isReadyToDispatchInsetsState()); + mDisplayContent.getInsetsStateController().notifyInsetsChanged(); + verify(app).notifyInsetsChanged(); + } } -- cgit v1.2.3-59-g8ed1b