diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java index 9d80b9c8b93e9..2328dfc8b781a 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java @@ -116,8 +116,10 @@ public abstract class InputMethodManagerInternal { * * @param windowToken the window token that is now in control, or {@code null} if no client * window is in control of the IME. + * @param imeParentChanged {@code true} when the window manager thoughts the IME surface parent + * will end up to change later, or {@code false} otherwise. */ - public abstract void reportImeControl(@Nullable IBinder windowToken); + public abstract void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged); /** * Destroys the IME surface. @@ -176,7 +178,8 @@ public abstract class InputMethodManagerInternal { } @Override - public void reportImeControl(@Nullable IBinder windowToken) { + public void reportImeControl(@Nullable IBinder windowToken, + boolean imeParentChanged) { } @Override diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index fd0f1c38938fc..dc955337fdbc5 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -4937,13 +4937,19 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken); } - private void reportImeControl(@Nullable IBinder windowToken) { + private void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) { synchronized (mMethodMap) { if (mCurFocusedWindow != windowToken) { // mCurPerceptible was set by the focused window, but it is no longer in control, // so we reset mCurPerceptible. mCurPerceptible = true; } + if (imeParentChanged) { + // Hide the IME method menu earlier when the IME surface parent will change in + // case seeing the dialog dismiss flickering during the next focused window + // starting the input connection. + mMenuController.hideInputMethodMenu(); + } } } @@ -5001,8 +5007,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public void reportImeControl(@Nullable IBinder windowToken) { - mService.reportImeControl(windowToken); + public void reportImeControl(@Nullable IBinder windowToken, boolean imeParentChanged) { + mService.reportImeControl(windowToken, imeParentChanged); } @Override diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index ce195e6ddd645..aa4fa7c6f4705 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -225,7 +225,8 @@ public final class MultiClientInputMethodManagerService { } @Override - public void reportImeControl(@Nullable IBinder windowToken) { + public void reportImeControl(@Nullable IBinder windowToken, + boolean imeParentChanged) { } @Override diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index c7ae9fda05b53..4c1142257218b 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -729,12 +729,19 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // When switching the app task, we keep the IME window visibility for better // transitioning experiences. - // However, in case IME created a child window 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 + // 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 && (mImeLayeringTarget == null || !mImeLayeringTarget.getRequestedVisibility(ITYPE_IME))) { - return false; + if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) { + return false; + } + + if (w.isChildWindow()) { + return false; + } } final ActivityRecord activity = w.mActivityRecord; @@ -3978,7 +3985,9 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp // Update Ime parent when IME insets leash created or the new IME layering target might // updated from setImeLayeringTarget, which is the best time that default IME visibility // has been settled down after IME control target changed. - if (prevImeControlTarget != mImeControlTarget || forceUpdateImeParent) { + final boolean imeParentChanged = + prevImeControlTarget != mImeControlTarget || forceUpdateImeParent; + if (imeParentChanged) { updateImeParent(); } @@ -3986,7 +3995,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp final IBinder token = win != null ? win.mClient.asBinder() : null; // Note: not allowed to call into IMMS with the WM lock held, hence the post. mWmService.mH.post(() -> - InputMethodManagerInternal.get().reportImeControl(token) + InputMethodManagerInternal.get().reportImeControl(token, imeParentChanged) ); } 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 745793cf59083..c60b8dcf67cf7 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -50,6 +50,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_DIALOG; 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_SCREENSHOT; @@ -2201,6 +2202,31 @@ public class DisplayContentTests extends WindowTestsBase { assertNotEquals(imeChildWindow, mDisplayContent.findFocusedWindow()); } + @UseTestDisplay(addWindows = W_INPUT_METHOD) + @Test + public void testImeMenuDialogFocusWhenImeLayeringTargetChanges() { + final WindowState imeMenuDialog = + createWindow(mImeWindow, TYPE_INPUT_METHOD_DIALOG, "imeMenuDialog"); + makeWindowVisibleAndDrawn(imeMenuDialog, mImeWindow); + assertTrue(imeMenuDialog.canReceiveKeys()); + mDisplayContent.setInputMethodWindowLocked(mImeWindow); + + // Verify imeMenuDialog can be focused window if the next IME target requests IME visible. + final WindowState imeAppTarget = + createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "imeAppTarget"); + mDisplayContent.setImeLayeringTarget(imeAppTarget); + spyOn(imeAppTarget); + doReturn(true).when(imeAppTarget).getRequestedVisibility(ITYPE_IME); + assertEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); + + // Verify imeMenuDialog doesn't be focused window if the next IME target does not + // request IME visible. + final WindowState nextImeAppTarget = + createWindow(null, TYPE_BASE_APPLICATION, mDisplayContent, "nextImeAppTarget"); + mDisplayContent.setImeLayeringTarget(nextImeAppTarget); + assertNotEquals(imeMenuDialog, mDisplayContent.findFocusedWindow()); + } + private void removeRootTaskTests(Runnable runnable) { final TaskDisplayArea taskDisplayArea = mRootWindowContainer.getDefaultTaskDisplayArea(); final Task rootTask1 = taskDisplayArea.createRootTask(WINDOWING_MODE_FULLSCREEN,