Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bd868b66 authored by Ming-Shin Lu's avatar Ming-Shin Lu
Browse files

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

Bug: 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

Change-Id: Iae3dd713b1e980067d28debea4e0a03707aa7938
parent eb66b48e
Loading
Loading
Loading
Loading
+5 −2
Original line number Diff line number Diff line
@@ -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
+9 −3
Original line number Diff line number Diff line
@@ -4934,13 +4934,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();
            }
        }
    }

@@ -4998,8 +5004,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
+16 −7
Original line number Diff line number Diff line
@@ -731,14 +731,21 @@ 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))) {
            if (w.mAttrs.type == TYPE_INPUT_METHOD_DIALOG) {
                return false;
            }

            if (w.isChildWindow()) {
                return false;
            }
        }

        final ActivityRecord activity = w.mActivityRecord;

        if (focusedApp == null) {
@@ -3991,7 +3998,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();
        }

@@ -3999,7 +4008,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)
        );
    }

+26 −0
Original line number Diff line number Diff line
@@ -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;
@@ -2177,6 +2178,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,