From 14f2c733b1861670c39274be9af23b8d4dc55861 Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Tue, 27 Jul 2021 14:57:50 +0800 Subject: [PATCH] Fix ANR when switching the app task but IME selection dialog focused As CL [1] that we don't give the IME child window focus when switching to the next app task, we also need to handle IME selection dialog focus and dismiss the IME selection dialog when the IME control target will be changed in later. To prevent the flickering when focusing to the next app task and see the IME selection dialg dismising during switching the IME client, add "imeParentChanged" parameter in InputMethodManagerInternal#reportImeControl to ensure IME selection dialog can be dissmissed before the IME control actually changed. [1]: I72618745542f33bcbb1001bd3b812e79f7652dda Fix: 194214768 Test: manual as issue steps: 1. Launch an app to show soft-keyboard 2. Tap IME icon from navBar to show "Select input method" dialog 3. Start settings or other apps from SystemUI StatusBar 4. Press back key wait for a monment, make sure ANR won't happen Test: atest DisplayContentTests#\ testImeMenuDialogFocusWhenImeLayeringTargetChanges Merged-In: Iae3dd713b1e980067d28debea4e0a03707aa7938 Change-Id: Iae3dd713b1e980067d28debea4e0a03707aa7938 --- .../InputMethodManagerInternal.java | 7 +++-- .../InputMethodManagerService.java | 12 ++++++--- .../MultiClientInputMethodManagerService.java | 3 ++- .../com/android/server/wm/DisplayContent.java | 23 +++++++++++----- .../server/wm/DisplayContentTests.java | 26 +++++++++++++++++++ 5 files changed, 58 insertions(+), 13 deletions(-) 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 a72d9aa9ec6be..e55ce9ebba925 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -728,12 +728,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; @@ -3977,7 +3984,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(); } @@ -3985,7 +3994,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 d498d4663d782..dfec289b1b264 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java @@ -49,6 +49,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; @@ -2175,6 +2176,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,