From 93b9a65b0b7e16958f55a4be1c50d9fa1d165bdb Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Sat, 26 Feb 2022 16:55:29 +0800 Subject: [PATCH] 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 --- .../com/android/server/wm/DisplayContent.java | 10 ++--- .../com/android/server/wm/InsetsPolicy.java | 10 ++--- .../server/wm/ActivityRecordTests.java | 4 +- .../server/wm/DisplayContentTests.java | 15 +++++++ .../android/server/wm/WindowStateTests.java | 40 +++++++++++++++++++ 5 files changed, 66 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 77787e1e7530f..b9e81bada8b2e 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -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; diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index 4fdb1f7930f5d..08b60773a713a 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -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) diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java index 2ea0bdc4354ea..92d0341c40044 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java @@ -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); diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java index 925f4f5a4130e..2377d63d10cda 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -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(); 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 a554fab76c2b8..bcf4e88497477 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -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() {