Loading services/core/java/com/android/server/wm/DisplayContent.java +21 −14 Original line number Original line Diff line number Diff line Loading @@ -831,21 +831,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; return false; } } // When switching the app task, we keep the IME window visibility for better // IME windows remain visibleRequested while switching apps to maintain a smooth animation. // transitioning experiences. // This persists until the new app is focused, so they can be visibleRequested despite not // However, in case IME created a child window or the IME selection dialog without // being visible to the user (i.e. occluded). These rank higher in the window hierarchy than // dismissing during the task switching to keep the window focus because IME window has // app windows, so they will always be considered first. To avoid having the focus stuck, // higher window hierarchy, we don't give it focus if the next IME layering target // an IME window (child or not) cannot be focused if the IME parent is not visible. However, // doesn't request IME visible. // child windows also require the IME to be visible in the current app. if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null if (w.mIsImWindow) { || !mImeLayeringTarget.isRequestedVisible(ime()))) { 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; return false; } } if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null && !(mImeLayeringTarget.isRequestedVisible(ime()) if (w.isChildWindow() && mImeLayeringTarget.isVisibleRequested())) { && !getInsetsStateController().getImeSourceProvider().isImeShowing()) { ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME child window not focusable as" + " IME is not visible"); return false; return false; } } } final ActivityRecord activity = w.mActivityRecord; final ActivityRecord activity = w.mActivityRecord; Loading services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +57 −35 Original line number Original line Diff line number Diff line Loading @@ -37,7 +37,6 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; 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.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; Loading @@ -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_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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_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_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; Loading Loading @@ -2693,55 +2693,77 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @SetupWindows(addWindows = W_INPUT_METHOD) @Test @Test public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { public void testImeChildWindowFocusWhenImeParentWindowChanges() { final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", final var imeChildWindow = newWindowBuilder("imeChildWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); doTestImeWindowFocusWhenImeParentWindowChanged(imeChildWindow); 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()); } } @SetupWindows(addWindows = W_INPUT_METHOD) @SetupWindows(addWindows = W_INPUT_METHOD) @Test @Test public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { public void testImeDialogWindowFocusWhenImeParentWindowChanges() { final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", final var imeDialogWindow = newWindowBuilder("imeMenuDialog", TYPE_INPUT_METHOD_DIALOG).build(); TYPE_INPUT_METHOD_DIALOG).build(); makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); doTestImeWindowFocusWhenImeParentWindowChanged(imeDialogWindow); assertTrue(imeMenuDialog.canReceiveKeys()); } @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); 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", final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); mDisplayContent.setImeLayeringTarget(imeAppTarget); imeAppTarget.setRequestedVisibleTypes(ime()); mDisplayContent.updateImeInputAndControlTarget(imeAppTarget); assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); final var imeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider(); imeProvider.setImeShowing(true); // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. 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", final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); makeWindowVisibleAndDrawn(nextImeAppTarget); // Even if the app still requests IME, the ime dialog should not gain focus if the target // Change layering target but keep input target (and thus imeParent) the same. // app is invisible. nextImeAppTarget.setRequestedVisibleTypes(ime()); nextImeAppTarget.mActivityRecord.setVisibility(false); mDisplayContent.setImeLayeringTarget(nextImeAppTarget); 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 @Test Loading Loading
services/core/java/com/android/server/wm/DisplayContent.java +21 −14 Original line number Original line Diff line number Diff line Loading @@ -831,21 +831,28 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp return false; return false; } } // When switching the app task, we keep the IME window visibility for better // IME windows remain visibleRequested while switching apps to maintain a smooth animation. // transitioning experiences. // This persists until the new app is focused, so they can be visibleRequested despite not // However, in case IME created a child window or the IME selection dialog without // being visible to the user (i.e. occluded). These rank higher in the window hierarchy than // dismissing during the task switching to keep the window focus because IME window has // app windows, so they will always be considered first. To avoid having the focus stuck, // higher window hierarchy, we don't give it focus if the next IME layering target // an IME window (child or not) cannot be focused if the IME parent is not visible. However, // doesn't request IME visible. // child windows also require the IME to be visible in the current app. if (w.mIsImWindow && w.isChildWindow() && (mImeLayeringTarget == null if (w.mIsImWindow) { || !mImeLayeringTarget.isRequestedVisible(ime()))) { 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; return false; } } if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG && mImeLayeringTarget != null && !(mImeLayeringTarget.isRequestedVisible(ime()) if (w.isChildWindow() && mImeLayeringTarget.isVisibleRequested())) { && !getInsetsStateController().getImeSourceProvider().isImeShowing()) { ProtoLog.v(WM_DEBUG_FOCUS, "findFocusedWindow: IME child window not focusable as" + " IME is not visible"); return false; return false; } } } final ActivityRecord activity = w.mActivityRecord; final ActivityRecord activity = w.mActivityRecord; Loading
services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +57 −35 Original line number Original line Diff line number Diff line Loading @@ -37,7 +37,6 @@ import static android.view.Surface.ROTATION_0; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_180; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_270; import static android.view.Surface.ROTATION_90; 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.navigationBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsets.Type.statusBars; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE; Loading @@ -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_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 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_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_INPUT_METHOD_DIALOG; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE; Loading Loading @@ -2693,55 +2693,77 @@ public class DisplayContentTests extends WindowTestsBase { @SetupWindows(addWindows = W_INPUT_METHOD) @SetupWindows(addWindows = W_INPUT_METHOD) @Test @Test public void testImeChildWindowFocusWhenImeLayeringTargetChanges() { public void testImeChildWindowFocusWhenImeParentWindowChanges() { final WindowState imeChildWindow = newWindowBuilder("imeChildWindow", final var imeChildWindow = newWindowBuilder("imeChildWindow", TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); TYPE_APPLICATION_ATTACHED_DIALOG).setParent(mImeWindow).build(); makeWindowVisibleAndDrawn(imeChildWindow, mImeWindow); doTestImeWindowFocusWhenImeParentWindowChanged(imeChildWindow); 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()); } } @SetupWindows(addWindows = W_INPUT_METHOD) @SetupWindows(addWindows = W_INPUT_METHOD) @Test @Test public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { public void testImeDialogWindowFocusWhenImeParentWindowChanges() { final WindowState imeMenuDialog = newWindowBuilder("imeMenuDialog", final var imeDialogWindow = newWindowBuilder("imeMenuDialog", TYPE_INPUT_METHOD_DIALOG).build(); TYPE_INPUT_METHOD_DIALOG).build(); makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); doTestImeWindowFocusWhenImeParentWindowChanged(imeDialogWindow); assertTrue(imeMenuDialog.canReceiveKeys()); } @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); 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", final WindowState imeAppTarget = newWindowBuilder("imeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); mDisplayContent.setImeLayeringTarget(imeAppTarget); mDisplayContent.setImeLayeringTarget(imeAppTarget); imeAppTarget.setRequestedVisibleTypes(ime()); mDisplayContent.updateImeInputAndControlTarget(imeAppTarget); assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); final var imeProvider = mDisplayContent.getInsetsStateController().getImeSourceProvider(); imeProvider.setImeShowing(true); // Verify imeMenuDialog doesn't be focused window if the next IME target is closing. 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", final WindowState nextImeAppTarget = newWindowBuilder("nextImeAppTarget", TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); TYPE_BASE_APPLICATION).setDisplay(mDisplayContent).build(); makeWindowVisibleAndDrawn(nextImeAppTarget); makeWindowVisibleAndDrawn(nextImeAppTarget); // Even if the app still requests IME, the ime dialog should not gain focus if the target // Change layering target but keep input target (and thus imeParent) the same. // app is invisible. nextImeAppTarget.setRequestedVisibleTypes(ime()); nextImeAppTarget.mActivityRecord.setVisibility(false); mDisplayContent.setImeLayeringTarget(nextImeAppTarget); 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 @Test Loading