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
This commit is contained in:
Ming-Shin Lu
2021-01-14 21:03:15 +08:00
parent cd6911ec52
commit 0f536fa994
6 changed files with 106 additions and 8 deletions

View File

@@ -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();
}
}

View File

@@ -74,7 +74,7 @@ class InsetsStateController {
private final ArraySet<InsetsControlTarget> mPendingControlChanged = new ArraySet<>();
private final Consumer<WindowState> 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);

View File

@@ -2684,6 +2684,14 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
@Nullable ArrayList<WindowContainer> 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<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
@@ -2831,7 +2839,7 @@ class WindowContainer<E extends WindowContainer> extends ConfigurationContainer<
}
mSurfaceAnimationSources.clear();
if (mDisplayContent != null) {
mDisplayContent.onWindowAnimationFinished(type);
mDisplayContent.onWindowAnimationFinished(this, type);
}
}

View File

@@ -713,6 +713,12 @@ class WindowState extends WindowContainer<WindowState> 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<WindowState> 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

View File

@@ -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<WindowContainer> 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<TestWindowContainer> {
private final int mLayer;

View File

@@ -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();
}
}