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

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

Polish IME transition from fullscreen task to split-screen task

As the new split-screen behavior that the root task of the split-screen
can be in the recents task, so using quick switch can switch between
the split-screen task and other fullscreen tasks.

So if the IME shown on the fullscreen task, when quick switch to
the split-screen task, basically the expected result will be IME
attached on the fullscreen task then won't expect IME moved to the
split-screen task.

However, the issue case is that seen the IME flicker on top of the
split-screen task and then disappeared.

The root case is in DC#computeImeParent didn't consider to not change
the new IME surface parent while the next IME input target has not yet
updated and settled the new IME visiblity, so IME may flicker if
changing the new IME parent too early.
(Typically the IME parent will be on the root displayArea when the
next target task is in multi-window mode)

We do defer changing IME parent from CL[1] when shouldImeAttachedToApp is
true, but this check didn't applied when switching to the split-screen
task, because shouldImeAttachedToApp will be false when the next IME
layering taget is in multi-window mode.

Simply move the above defer update IME parent logic out from
shouldImeAttachedToApp check to fix this IME flicker issue.

Also, remove shouldImeAttachedToApp check in
InsetsPolicy#adjustVisibilityForIme when the window is in insets frozen
state waiting for the next input started. Since there is no strong
reason to froze IME insets only when switching on fullscreen tasks.

[1]: I01b67b9a7fdf04f23ab05c244281f42518352ca5

Fix: 210391817
Test: atest DisplayContentTests#testComputeImeParent_inputTargetNotUpdate
Test: atest WindowStateTests#\
   testAdjustImeInsetsVisibilityWhenSwitchingApps_toAppInMultiWindowMode

Change-Id: I332c0e4fff62df5d7b793eda2767bb58fe85a938
parent 2773ac9c
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -4255,17 +4255,17 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp
     */
    @VisibleForTesting
    SurfaceControl computeImeParent() {
        if (mImeLayeringTarget != null && mImeInputTarget != null
                && mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) {
            // Do not change parent if the window hasn't requested IME.
            return null;
        }
        // Attach it to app if the target is part of an app and such app is covering the entire
        // screen. If it's not covering the entire screen the IME might extend beyond the apps
        // bounds.
        if (shouldImeAttachedToApp()) {
            if (mImeLayeringTarget.mActivityRecord != mImeInputTarget.getActivityRecord()) {
                // Do not change parent if the window hasn't requested IME.
                return null;
            }
            return mImeLayeringTarget.mActivityRecord.getSurfaceControl();
        }

        // Otherwise, we just attach it to where the display area policy put it.
        return mImeWindowsContainer.getParent() != null
                ? mImeWindowsContainer.getParent().getSurfaceControl() : null;
+4 −6
Original line number Diff line number Diff line
@@ -424,14 +424,12 @@ class InsetsPolicy {
                return state;
            }
        } else if (w.mActivityRecord != null && w.mActivityRecord.mImeInsetsFrozenUntilStartInput) {
            // During switching tasks with gestural navigation, if the IME is attached to
            // one app window on that time, even the next app window is behind the IME window,
            // conceptually the window should not receive the IME insets if the next window is
            // not eligible IME requester and ready to show IME on top of it.
            final boolean shouldImeAttachedToApp = mDisplayContent.shouldImeAttachedToApp();
            // During switching tasks with gestural navigation, before the next IME input target
            // starts the input, we should adjust and freeze the last IME visibility of the window
            // in case delivering obsoleted IME insets state during transitioning.
            final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME);

            if (shouldImeAttachedToApp && originalImeSource != null) {
            if (originalImeSource != null) {
                final boolean imeVisibility =
                        w.mActivityRecord.mLastImeShown || w.getRequestedVisibility(ITYPE_IME);
                final InsetsState state = copyState ? new InsetsState(originalState)
+2 −2
Original line number Diff line number Diff line
@@ -3073,11 +3073,11 @@ public class ActivityRecordTests extends WindowTestsBase {

        // Simulate app re-start input or turning screen off/on then unlocked by un-secure
        // keyguard to back to the app, expect IME insets is not frozen
        mDisplayContent.updateImeInputAndControlTarget(app);
        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);
        imeSource.setFrame(new Rect(100, 400, 500, 500));
        app.getInsetsState().addSource(imeSource);
        app.getInsetsState().setSourceVisible(ITYPE_IME, true);
        mDisplayContent.updateImeInputAndControlTarget(app);
        assertFalse(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);

        // Verify when IME is visible and the app can receive the right IME insets from policy.
        makeWindowVisibleAndDrawn(app, mImeWindow);
+15 −0
Original line number Diff line number Diff line
@@ -1105,6 +1105,21 @@ public class DisplayContentTests extends WindowTestsBase {
        assertEquals(dc.getImeContainer().getParentSurfaceControl(), dc.computeImeParent());
    }

    @UseTestDisplay(addWindows = W_ACTIVITY)
    @Test
    public void testComputeImeParent_inputTargetNotUpdate() throws Exception {
        WindowState app1 = createWindow(null, TYPE_BASE_APPLICATION, "app1");
        WindowState app2 = createWindow(null, TYPE_BASE_APPLICATION, "app2");
        doReturn(true).when(mDisplayContent).shouldImeAttachedToApp();
        mDisplayContent.setImeLayeringTarget(app1);
        mDisplayContent.setImeInputTarget(app1);
        assertEquals(app1.mActivityRecord.getSurfaceControl(), mDisplayContent.computeImeParent());
        mDisplayContent.setImeLayeringTarget(app2);
        // Expect null means no change IME parent when the IME layering target not yet
        // request IME to be the input target.
        assertNull(mDisplayContent.computeImeParent());
    }

    @Test
    public void testInputMethodInputTarget_isClearedWhenWindowStateIsRemoved() throws Exception {
        final DisplayContent dc = createNewDisplay();
+40 −0
Original line number Diff line number Diff line
@@ -917,6 +917,46 @@ public class WindowStateTests extends WindowTestsBase {
        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
    }

    @Test
    public void testAdjustImeInsetsVisibilityWhenSwitchingApps_toAppInMultiWindowMode() {
        final WindowState app = createWindow(null, TYPE_APPLICATION, "app");
        final WindowState app2 = createWindow(null, WINDOWING_MODE_MULTI_WINDOW,
                ACTIVITY_TYPE_STANDARD, TYPE_APPLICATION, mDisplayContent, "app2");
        final WindowState imeWindow = createWindow(null, TYPE_APPLICATION, "imeWindow");
        spyOn(imeWindow);
        doReturn(true).when(imeWindow).isVisible();
        mDisplayContent.mInputMethodWindow = imeWindow;

        final InsetsStateController controller = mDisplayContent.getInsetsStateController();
        controller.getImeSourceProvider().setWindowContainer(imeWindow, null, null);

        // Simulate app2 in multi-window mode is going to background to switch to the fullscreen
        // app which requests IME with updating all windows Insets State when IME is above app.
        app2.mActivityRecord.mImeInsetsFrozenUntilStartInput = true;
        mDisplayContent.setImeLayeringTarget(app);
        mDisplayContent.setImeInputTarget(app);
        assertTrue(mDisplayContent.shouldImeAttachedToApp());
        controller.getImeSourceProvider().scheduleShowImePostLayout(app);
        controller.getImeSourceProvider().getSource().setVisible(true);
        controller.updateAboveInsetsState(false);

        // Expect app windows behind IME can receive IME insets visible,
        // but not for app2 in background.
        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());

        // Simulate app plays closing transition to app2.
        // And app2 is now IME layering target but not yet to be the IME input target.
        mDisplayContent.setImeLayeringTarget(app2);
        app.mActivityRecord.commitVisibility(false, false);
        assertTrue(app.mActivityRecord.mLastImeShown);
        assertTrue(app.mActivityRecord.mImeInsetsFrozenUntilStartInput);

        // Verify the IME insets is still visible on app, but not for app2 during task switching.
        assertTrue(app.getInsetsState().getSource(ITYPE_IME).isVisible());
        assertFalse(app2.getInsetsState().getSource(ITYPE_IME).isVisible());
    }

    @UseTestDisplay(addWindows = {W_ACTIVITY})
    @Test
    public void testUpdateImeControlTargetWhenLeavingMultiWindow() {