diff options
| author | 2025-02-14 17:56:24 +0100 | |
|---|---|---|
| committer | 2025-02-19 14:24:42 +0100 | |
| commit | c1fe8472c9c2f9b262ce10fff42df6fdce3d8f92 (patch) | |
| tree | 9d616a53a74bc608da042f3d981e800507592e79 | |
| parent | 4fb4149e4df534796ded579fae56d4f8cf56118b (diff) | |
Check IME parent visibility for IME window focus
When switching away from an app that has both the IME and the IME dialog
window visible, to an app that previously requested the IME to be
visible, the IME layering target could be updated to the new app before
the input_focus change can take effect. This leads to focus changing to
the IME dialog window, which still exists, but is occluded by the new
app. As this window is not visible, the actual input_focus is set to
null, leaving the IME input target in the previous app. As the IME
surface parent window is computed based on the IME input target, this
will now be not visible.
This changes the logic for checking if an IME dialog window can be
focused from taking into account the IME layering target, to checking
the IME parent. This is only updated in response to input_focus changes,
and represents the current surface the IME container is attached to.
In [1] the existing logic was modified such that the dialog cannot be
focused if either the layering target is not visible, or it did not
request the IME. However, the IME Switcher menu can be shown independent
of the IME being visible, and it wouldn't receive input_focus in this
case anymore, which makes it harder to dismiss. This also removes the
check of the IME being visible for IME dialog windows.
This also unifies the changes with the IME child window branch, which
should also check the IME parent visibility. Lastly, the IME visibility
is better determined from the ImeInsetsSourceProvider rather than the
IME layering target (for RemoteInsetsControlTarget cases).
Note, this adds the IME parent visibility check for all windows that
have mIsImWindow, which includes TYPE_INPUT_METHOD. Normally these
windows fail the canReceiveKeys check above, as they have
FLAG_NOT_FOCUSABLE. Otherwise, these checks also make sense for them.
[1]: Ia5ea318b2a92ccc003f3192a198bf041365f70cf
Flag: EXEMPT bugfix
Test: atest DisplayContentTests#testImeChildWindowFocusWhenImeParentWindowChanges
DisplayContentTests#testImeDialogWindowFocusWhenImeParentWindowChanges
DisplayContentTests#testImeWindowFocusWhenImeParentWindowChanges
Bug: 388732427
Change-Id: I3ac91cf02f0270b2d54581aa9174a875836ca692
| -rw-r--r-- | services/core/java/com/android/server/wm/DisplayContent.java | 35 | ||||
| -rw-r--r-- | services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java | 92 |
2 files changed, 78 insertions, 49 deletions
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 703ce7d24468..26d39798124f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -845,20 +845,27 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; } - // When switching the app task, we keep the IME window visibility for better - // transitioning experiences. - // However, in case IME created a child window or the IME selection dialog without - // dismissing during the task switching to keep the window focus because IME window has - // higher window hierarchy, we don't give it focus if the next IME layering target - // doesn't request IME visible. - if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null - || !mImeLayeringTarget.isRequestedVisible(ime()))) { - return false; - } - if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null - && !(mImeLayeringTarget.isRequestedVisible(ime()) - && mImeLayeringTarget.isVisibleRequested())) { - return false; + // IME windows remain visibleRequested while switching apps to maintain a smooth animation. + // This persists until the new app is focused, so they can be visibleRequested despite not + // being visible to the user (i.e. occluded). These rank higher in the window hierarchy than + // app windows, so they will always be considered first. To avoid having the focus stuck, + // an IME window (child or not) cannot be focused if the IME parent is not visible. However, + // child windows also require the IME to be visible in the current app. + if (w.mIsImWindow) { + final boolean imeParentVisible = mInputMethodSurfaceParentWindow != null + && mInputMethodSurfaceParentWindow.isVisibleRequested(); + if (!imeParentVisible) { + ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME window not focusable as" + + " IME parent is not visible"); + return false; + } + + if (w.isChildWindow() + && !getInsetsStateController().getImeSourceProvider().isImeShowing()) { + ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME child window not focusable as" + + " IME is not visible"); + return false; + } } final ActivityRecord activity = w.mActivityRecord; 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 6e109a8d4eaf..e2722cfc0661 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -37,7 +37,6 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; -import static android.view.WindowInsets.Type.ime; import static android.view.WindowInsets.Type.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; @@ -56,6 +55,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; +import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; @@ -2752,55 +2752,77 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @Test - public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { - final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", + public void testImeChildWindowFocusWhenImeParentWindowChanges() { + final var imeChildWindow = newWindowBuilder("imeChildWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); - makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); - assertTrue(imeChildWindow.canReceiveKeys()); - mDisplayContent.setInputMethodWindowLocked(mImeWindow); - - // Verify imeChildWindow can be focused window if the next IME target requests IME visible. - final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", - TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); - mDisplayContent.setImeLayeringTarget(imeAppTarget); - spyOn(imeAppTarget); - doReturn(true).when(imeAppTarget).isRequestedVisible(ime()); - assertEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); - - // Verify imeChildWindow doesn't be focused window if the next IME target does not - // request IME visible. - final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", - TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); - mDisplayContent.setImeLayeringTarget(nextImeAppTarget); - assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); + doTestImeWindowFocusWhenImeParentWindowChanged(imeChildWindow); } @SetupWindows(addWindows = W_INPUT_METHOD) @Test - public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { - final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", + public void testImeDialogWindowFocusWhenImeParentWindowChanges() { + final var imeDialogWindow = newWindowBuilder("imeMenuDialog", TYPE_INPUT_METHOD_DIALOG).build(); - makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); - assertTrue(imeMenuDialog.canReceiveKeys()); + doTestImeWindowFocusWhenImeParentWindowChanged(imeDialogWindow); + } + + @SetupWindows(addWindows = W_INPUT_METHOD) + @Test + public void testImeWindowFocusWhenImeParentWindowChanges() { + // Verify focusable, non-child IME windows. + final var otherImeWindow = newWindowBuilder("otherImeWindow", + TYPE_INPUT_METHOD).build(); + doTestImeWindowFocusWhenImeParentWindowChanged(otherImeWindow); + } + + private void doTestImeWindowFocusWhenImeParentWindowChanged(@NonNull WindowState window) { + makeWindowVisibleAndDrawn(window, mImeWindow); + assertTrue("Window canReceiveKeys", window.canReceiveKeys()); mDisplayContent.setInputMethodWindowLocked(mImeWindow); - // Verify imeMenuDialog can be focused window if the next IME target requests IME visible. + // Verify window can be focused if the IME parent is visible and the IME is visible. final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); - imeAppTarget.setRequestedVisibleTypes(ime()); - assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); - - // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. + mDisplayContent.updateImeInputAndControlTarget(imeAppTarget); + final var imeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider(); + imeProvider.setImeShowing(true); + final var imeParentWindow = mDisplayContent.getImeParentWindow(); + assertNotNull("IME parent window is not null", imeParentWindow); + assertTrue("IME parent window is visible", imeParentWindow.isVisibleRequested()); + assertTrue("IME is visible", imeProvider.isImeShowing()); + assertEquals("Window is the focused one", window, mDisplayContent.findFocusedWindow()); + + // Verify window can't be focused if the IME parent is not visible. final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); - // Even if the app still requests IME, the ime dialog should not gain focus if the target - // app is invisible. - nextImeAppTarget.setRequestedVisibleTypes(ime()); - nextImeAppTarget.mActivityRecord.setVisibility(false); + // Change layering target but keep input target (and thus imeParent) the same. mDisplayContent.setImeLayeringTarget(nextImeAppTarget); - assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); + // IME parent window is not visible, occluded by new layering target. + imeParentWindow.setVisibleRequested(false); + assertEquals("IME parent window did not change", imeParentWindow, + mDisplayContent.getImeParentWindow()); + assertFalse("IME parent window is not visible", imeParentWindow.isVisibleRequested()); + assertTrue("IME is visible", imeProvider.isImeShowing()); + assertNotEquals("Window is not the focused one when imeParent is not visible", window, + mDisplayContent.findFocusedWindow()); + + // Verify window can be focused if the IME is not visible. + mDisplayContent.updateImeInputAndControlTarget(nextImeAppTarget); + imeProvider.setImeShowing(false); + final var nextImeParentWindow = mDisplayContent.getImeParentWindow(); + assertNotNull("Next IME parent window is not null", nextImeParentWindow); + assertNotEquals("IME parent window changed", imeParentWindow, nextImeParentWindow); + assertTrue("Next IME parent window is visible", nextImeParentWindow.isVisibleRequested()); + assertFalse("IME is not visible", imeProvider.isImeShowing()); + if (window.isChildWindow()) { + assertNotEquals("Child window is not the focused on when the IME is not visible", + window, mDisplayContent.findFocusedWindow()); + } else { + assertEquals("Window is the focused one when the IME is not visible", + window, mDisplayContent.findFocusedWindow()); + } } @Test |