summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Cosmin Băieș <cosminbaies@google.com> 2025-02-14 17:56:24 +0100
committer Cosmin Băieș <cosminbaies@google.com> 2025-02-19 14:24:42 +0100
commitc1fe8472c9c2f9b262ce10fff42df6fdce3d8f92 (patch)
tree9d616a53a74bc608da042f3d981e800507592e79
parent4fb4149e4df534796ded579fae56d4f8cf56118b (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.java35
-rw-r--r--services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java92
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