From 7d9eb2b6f953773887289b910cd4d7337ad246bf Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Fri, 2 Jul 2021 10:55:16 +0800 Subject: [PATCH] Fix flickering when switched to the task without IME shown When switching tasks from the task has IME shown to another task without showing IME (e.g. youtube apps.), the flickering might happen because the next activity accidentally received the IME insets attached in the previous activity. 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. Fix the logic in InsetsPolicy#adjustVisibilityForIme for the above case. Fix: 192337037 Bug: 194746204 Test: atest WindowStateTests#\ testAdjustImeInsetsVisibilityWhenTaskSwitchIsAnimating Merged-In: Ib3a1eacedd3763bcf1e6eed82e7efdf01c50b204 Change-Id: Ib3a1eacedd3763bcf1e6eed82e7efdf01c50b204 (cherry picked from commit f4eb0544601ced85aca5acac1b24f3fe8babfedd) --- .../com/android/server/wm/InsetsPolicy.java | 46 +++++++++++++++---- .../android/server/wm/WindowStateTests.java | 33 +++++++++++++ 2 files changed, 69 insertions(+), 10 deletions(-) diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index f2f192686ad55..11a0fba4b0151 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -32,6 +32,10 @@ import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_B import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; +import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; + import android.annotation.NonNull; import android.annotation.Nullable; import android.app.StatusBarManager; @@ -211,7 +215,7 @@ class InsetsPolicy { InsetsState getInsetsForWindow(WindowState target) { final InsetsState originalState = mStateController.getInsetsForWindow(target); final InsetsState state = adjustVisibilityForTransientTypes(originalState); - return target.mIsImWindow ? adjustVisibilityForIme(state, state == originalState) : state; + return adjustVisibilityForIme(target, state, state == originalState); } /** @@ -241,16 +245,38 @@ class InsetsPolicy { return state; } - // Navigation bar insets is always visible to IME. - private static InsetsState adjustVisibilityForIme(InsetsState originalState, + private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState, boolean copyState) { - final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); - if (originalNavSource != null && !originalNavSource.isVisible()) { - final InsetsState state = copyState ? new InsetsState(originalState) : originalState; - final InsetsSource navSource = new InsetsSource(originalNavSource); - navSource.setVisible(true); - state.addSource(navSource); - return state; + if (w.mIsImWindow) { + // Navigation bar insets is always visible to IME. + final InsetsSource originalNavSource = originalState.peekSource(ITYPE_NAVIGATION_BAR); + if (originalNavSource != null && !originalNavSource.isVisible()) { + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + final InsetsSource navSource = new InsetsSource(originalNavSource); + navSource.setVisible(true); + state.addSource(navSource); + return state; + } + } else if (w.mActivityRecord != null && !w.mActivityRecord.mLastImeShown) { + // 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(); + final InsetsSource originalImeSource = originalState.peekSource(ITYPE_IME); + + if (originalImeSource != null && shouldImeAttachedToApp + && (w.isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS) + || !w.getRequestedVisibility(ITYPE_IME))) { + final InsetsState state = copyState ? new InsetsState(originalState) + : originalState; + + final InsetsSource imeSource = new InsetsSource(originalImeSource); + imeSource.setVisible(false); + state.addSource(imeSource); + return state; + } } return originalState; } 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 92b670ed96995..3a50bb0f06bc3 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -53,6 +53,9 @@ import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.android.server.wm.DisplayContent.IME_TARGET_CONTROL; import static com.android.server.wm.DisplayContent.IME_TARGET_LAYERING; +import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_RECENTS; +import static com.android.server.wm.WindowContainer.AnimationFlags.PARENTS; +import static com.android.server.wm.WindowContainer.AnimationFlags.TRANSITION; import static com.android.server.wm.WindowContainer.SYNC_STATE_WAITING_FOR_DRAW; import static com.google.common.truth.Truth.assertThat; @@ -891,6 +894,36 @@ public class WindowStateTests extends WindowTestsBase { assertTrue(mAppWindow.getInsetsState().getSourceOrDefaultVisibility(ITYPE_NAVIGATION_BAR)); } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testAdjustImeInsetsVisibilityWhenTaskSwitchIsAnimating() { + final WindowState app = createWindow(null, TYPE_APPLICATION, "app"); + final WindowState app2 = createWindow(null, TYPE_APPLICATION, "app2"); + final InsetsStateController controller = mDisplayContent.getInsetsStateController(); + controller.getImeSourceProvider().setWindow(mImeWindow, null, null); + + // Simulate app requests IME with updating all windows Insets State when IME is above app. + mDisplayContent.setImeLayeringTarget(app); + mDisplayContent.setImeInputTarget(app); + assertTrue(mDisplayContent.shouldImeAttachedToApp()); + controller.getImeSourceProvider().scheduleShowImePostLayout(app); + controller.getImeSourceProvider().getSource().setVisible(true); + controller.updateAboveInsetsState(mImeWindow, false); + + // Simulate task switching animation happens when switching app to app2. + spyOn(app); + spyOn(app2); + doReturn(true).when(app).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS); + doReturn(true).when(app2).isAnimating(PARENTS | TRANSITION, ANIMATION_TYPE_RECENTS); + app.mActivityRecord.mLastImeShown = true; + + // Verify the IME insets is visible on app, but not for app2 during task animating. + InsetsState stateApp = app.getInsetsState(); + InsetsState stateApp2 = app2.getInsetsState(); + assertTrue(stateApp.getSource(ITYPE_IME).isVisible()); + assertFalse(stateApp2.getSource(ITYPE_IME).isVisible()); + } + @UseTestDisplay(addWindows = { W_ACTIVITY }) @Test public void testUpdateImeControlTargetWhenLeavingMultiWindow() {