From dd59d534a39c5c694f8f8e07c103057f1328bf0b Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Fri, 15 Oct 2021 21:42:34 +0800 Subject: [PATCH] Fix IME flickering when playing Keyguard launching remote animation When starting the remote animation, before RemoteAnimationController#goodToGo, WM will traverse all windows to perform mApplySurfaceChangesTransaction to prepare showing the window surface if needed. Howerver, in issue case, IME surface accidientally being in traverse window list and shown on the screen even Gpay didn't request show IME. As IME window now is being in ImeContainer, to apply IME surface change transaction, in WindowState#forAllWindows -> applyInOrderWithImeWindows -> applyImeWindowsIfNeeded that has logic to apply for IME window when the traversing window is non-split-screen IME layering target. However, this logic didn't cover the case if the last IME input target that interact with IME is not drawn. For issue case, the last IME input target is the Message app and invisible because it has obscured by lockscreen. So during launching the Gpay activity, even Gpay activity is layering target, since the last IME input target is not visible, in that case we should not apply IME surface preperation. As the result, the fix will be: - Add a check in WindowState#applyImeWindowsIfNeeded to ignore applying callback for IME window if the IME input target is not drawn. Fix: 201987724 Test: atest RemoteAnimationControllerTests#\ testLaunchRemoteAnimationWithoutImeBehind Test: manual steps as below: 0-1) Set secure unlock method (e.g. pattern) 0-2) Install "Google Pay" app 0-3) Add a payment method instruction 1) Start Message app 2) Click search window and wait IME window appears 3) Hit power button to lock the device 4) Tap screen to show Lockscreen 5) Tap GPay icon 6) See animation and expect there is no IME flickering behind Change-Id: I57357ba85501397fa5926ab4dee116c42df24506 --- .../com/android/server/wm/DisplayContent.java | 2 + .../com/android/server/wm/WindowState.java | 33 ++++++++----- .../wm/RemoteAnimationControllerTest.java | 47 +++++++++++++++++++ 3 files changed, 69 insertions(+), 13 deletions(-) diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 5f03f89624876..181c518f674c1 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -4082,6 +4082,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false; } setImeInputTarget(target); + mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController + .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME)); updateImeControlTarget(); } } diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 6afa2e0f690dd..39295e4394e0c 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -4951,20 +4951,27 @@ class WindowState extends WindowContainer implements WindowManagerP private boolean applyImeWindowsIfNeeded(ToBooleanFunction callback, boolean traverseTopToBottom) { - // If this window is the current IME target, so we need to process the IME windows - // directly above it. The exception is if we are in split screen - // in which case we process the IME at the DisplayContent level to - // ensure it is above the docked divider. - // (i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME - // window will be ignored to traverse when the IME target is still in split-screen mode). - if (isImeLayeringTarget() - && (!mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated() - || getTask() == null)) { - if (mDisplayContent.forAllImeWindows(callback, traverseTopToBottom)) { - return true; - } + // No need to apply to IME window if the window is not the current IME layering target. + if (!isImeLayeringTarget()) { + return false; } - return false; + // If we are in split screen which case we process the IME at the DisplayContent level to + // ensure it is above the docked divider. + // i.e. Like {@link DisplayContent.ImeContainer#skipImeWindowsDuringTraversal}, the IME + // window will be ignored to traverse when the IME target is still in split-screen mode. + if (mDisplayContent.getDefaultTaskDisplayArea().isSplitScreenModeActivated() + && getTask() != null) { + return false; + } + // Note that we don't process IME window if the IME input target is not on the screen. + // In case some unexpected IME visibility cases happen like starting the remote + // animation on the keyguard but seeing the IME window that originally on the app + // which behinds the keyguard. + final WindowState imeInputTarget = getImeInputTarget(); + if (imeInputTarget != null && !(imeInputTarget.isDrawn() || imeInputTarget.isVisible())) { + return false; + } + return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom); } private boolean applyInOrderWithImeWindows(ToBooleanFunction callback, diff --git a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java index 08312ef0b8651..575e0820eca9f 100644 --- a/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/RemoteAnimationControllerTest.java @@ -36,12 +36,14 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.when; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_APP_TRANSITION; import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION; +import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.fail; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyFloat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; @@ -699,6 +701,51 @@ public class RemoteAnimationControllerTest extends WindowTestsBase { } } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testLaunchRemoteAnimationWithoutImeBehind() { + final WindowState win1 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin1"); + final WindowState win2 = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin2"); + + // Simulating win1 has shown IME and being IME layering/input target + mDisplayContent.setImeLayeringTarget(win1); + mDisplayContent.setImeInputTarget(win1); + mImeWindow.mWinAnimator.mSurfaceController = mock(WindowSurfaceController.class); + mImeWindow.mWinAnimator.hide(mDisplayContent.getPendingTransaction(), "test"); + spyOn(mDisplayContent); + doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController).hasSurface(); + doReturn(true).when(mImeWindow.mWinAnimator.mSurfaceController) + .prepareToShowInTransaction(any(), anyFloat()); + makeWindowVisibleAndDrawn(mImeWindow); + assertTrue(mImeWindow.isOnScreen()); + assertFalse(mImeWindow.isParentWindowHidden()); + + try { + // Simulating now win1 is being covered by the lockscreen which has no surface, + // and then launching an activity win2 with the remote animation + win1.mHasSurface = false; + mDisplayContent.mOpeningApps.add(win2.mActivityRecord); + final AnimationAdapter adapter = mController.createRemoteAnimationRecord( + win2.mActivityRecord, new Point(50, 100), null, + new Rect(50, 100, 150, 150), null).mAdapter; + adapter.startAnimation(mMockLeash, mMockTransaction, ANIMATION_TYPE_APP_TRANSITION, + mFinishedCallback); + + mDisplayContent.applySurfaceChangesTransaction(); + mController.goodToGo(TRANSIT_OLD_TASK_OPEN); + mWm.mAnimator.executeAfterPrepareSurfacesRunnables(); + + verify(mMockRunner).onAnimationStart(eq(TRANSIT_OLD_TASK_OPEN), + any(), any(), any(), any()); + // Verify the IME window won't apply surface change transaction with forAllImeWindows + verify(mDisplayContent, never()).forAllImeWindows(any(), eq(true)); + } catch (Exception e) { + // no-op + } finally { + mDisplayContent.mOpeningApps.clear(); + } + } + private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) { final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin"); mDisplayContent.mOpeningApps.add(win.mActivityRecord);