Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit dd59d534 authored by Ming-Shin Lu's avatar Ming-Shin Lu
Browse files

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
parent db885833
Loading
Loading
Loading
Loading
+2 −0
Original line number Original line Diff line number Diff line
@@ -4082,6 +4082,8 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
                target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
                target.mActivityRecord.mImeInsetsFrozenUntilStartInput = false;
            }
            }
            setImeInputTarget(target);
            setImeInputTarget(target);
            mInsetsStateController.updateAboveInsetsState(mInputMethodWindow, mInsetsStateController
                    .getRawInsetsState().getSourceOrDefaultVisibility(ITYPE_IME));
            updateImeControlTarget();
            updateImeControlTarget();
        }
        }
    }
    }
+19 −12
Original line number Original line Diff line number Diff line
@@ -4951,21 +4951,28 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP


    private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
    private boolean applyImeWindowsIfNeeded(ToBooleanFunction<WindowState> callback,
            boolean traverseTopToBottom) {
            boolean traverseTopToBottom) {
        // If this window is the current IME target, so we need to process the IME windows
        // No need to apply to IME window if the window is not the current IME layering target.
        // directly above it. The exception is if we are in split screen
        if (!isImeLayeringTarget()) {
        // in which case we process the IME at the DisplayContent level to
            return false;
        // 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;
        }
        }
        // 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 false;
        }
        }
        return mDisplayContent.forAllImeWindows(callback, traverseTopToBottom);
    }


    private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
    private boolean applyInOrderWithImeWindows(ToBooleanFunction<WindowState> callback,
            boolean traverseTopToBottom) {
            boolean traverseTopToBottom) {
+47 −0
Original line number Original line Diff line number Diff line
@@ -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_APP_TRANSITION;
import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_WINDOW_ANIMATION;
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 junit.framework.Assert.fail;


import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
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) {
    private AnimationAdapter setupForNonAppTargetNavBar(int transit, boolean shouldAttachNavBar) {
        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
        final WindowState win = createWindow(null /* parent */, TYPE_BASE_APPLICATION, "testWin");
        mDisplayContent.mOpeningApps.add(win.mActivityRecord);
        mDisplayContent.mOpeningApps.add(win.mActivityRecord);