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

Commit c93a60b0 authored by Vaibhav Devmurari's avatar Vaibhav Devmurari
Browse files

Capture Ctrl+Space shortcut in InputMethodService

Brief explanation:
We have these stages
- PWM interceptkeys
- Appside pre IME stage
- Appside IME stage
- Appside onKeyEvent handling
- PWM fallback

Currently Ctrl+space is handled in PWM fallback.
So, if app handles the key in onKeyEvent() then we never handle the shortcut. Some apps like browser are capturing all keys in onKeyEvent() for performance reasons or for scrolling. And this blocks the shortcut handling and makes it look flaky for the user.

With the change: Ctrl+Space will take priority over app onKeyEvent capturing since it happens in Appside IME stage. This is only done when IME is visible i.e. user is actively typing.

- User scenario#1: Ctrl+Space in browser while typing (i.e. IME is visible/active) -> language switch [This wasn't hapenning without the change]
- User scenario#2: Ctrl+Space is browser while not in text field -> No language switch since browser captures it in onKeyEvent() stage.
- User scenario#3: Ctrl+Space in apps that don't capture keyevents -> language switch due to PWM fallback

This change is just fixing the User scenario #1 other things are still the same. But it seems that's the most important use case where we want to prioritize IME capturing the shortcut over app.Also, we still allow apps to block IME capturing the shortcut by overriding dispatchPreIme() which allos apps to capture key events in Pre Ime stage, allowing some apps like Minecraft, and other games that want full control on keys and has their own input handling for typing, etc.

Test: atest InputMethodServiceTest
Bug: 301969005
Change-Id: I2098ef61e1c2a4d53a46497a87a7840bf287ab92
parent 927dfc77
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -3363,6 +3363,13 @@ public class InputMethodService extends AbstractInputMethodService {
                return true;
            }
            return false;
        } else if (event.getKeyCode() == KeyEvent.KEYCODE_SPACE && KeyEvent.metaStateHasModifiers(
                event.getMetaState() & ~KeyEvent.META_SHIFT_MASK, KeyEvent.META_CTRL_ON)) {
            if (mDecorViewVisible && mWindowVisible) {
                int direction = (event.getMetaState() & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                mPrivOps.switchKeyboardLayoutAsync(direction);
                return true;
            }
        }
        return doMovementKey(keyCode, event, MOVEMENT_DOWN);
    }
+1 −0
Original line number Diff line number Diff line
@@ -46,4 +46,5 @@ oneway interface IInputMethodPrivilegedOperations {
            in @nullable ImeTracker.Token statsToken);
    void onStylusHandwritingReady(int requestId, int pid);
    void resetStylusHandwriting(int requestId);
    void switchKeyboardLayoutAsync(int direction);
}
+16 −0
Original line number Diff line number Diff line
@@ -431,4 +431,20 @@ public final class InputMethodPrivilegedOperations {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
     * Calls {@link IInputMethodPrivilegedOperations#switchKeyboardLayoutAsync(int)}.
     */
    @AnyThread
    public void switchKeyboardLayoutAsync(int direction) {
        final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull();
        if (ops == null) {
            return;
        }
        try {
            ops.switchKeyboardLayoutAsync(direction);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }
}
+53 −32
Original line number Diff line number Diff line
@@ -5529,6 +5529,42 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        return canAccess;
    }

    @GuardedBy("ImfLock.class")
    private void switchKeyboardLayoutLocked(int direction) {
        final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
        if (currentImi == null) {
            return;
        }
        final InputMethodSubtypeHandle currentSubtypeHandle =
                InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
        final InputMethodSubtypeHandle nextSubtypeHandle =
                mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
                        direction > 0);
        if (nextSubtypeHandle == null) {
            return;
        }
        final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
        if (nextImi == null) {
            return;
        }

        final int subtypeCount = nextImi.getSubtypeCount();
        if (subtypeCount == 0) {
            if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
                setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
            }
            return;
        }

        for (int i = 0; i < subtypeCount; ++i) {
            if (nextSubtypeHandle.equals(
                    InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
                setInputMethodLocked(nextImi.getId(), i);
                return;
            }
        }
    }

    private void publishLocalService() {
        LocalServices.addService(InputMethodManagerInternal.class, new LocalServiceImpl());
    }
@@ -5734,38 +5770,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        @Override
        public void switchKeyboardLayout(int direction) {
            synchronized (ImfLock.class) {
                final InputMethodInfo currentImi = mMethodMap.get(getSelectedMethodIdLocked());
                if (currentImi == null) {
                    return;
                }
                final InputMethodSubtypeHandle currentSubtypeHandle =
                        InputMethodSubtypeHandle.of(currentImi, mCurrentSubtype);
                final InputMethodSubtypeHandle nextSubtypeHandle =
                        mHardwareKeyboardShortcutController.onSubtypeSwitch(currentSubtypeHandle,
                                direction > 0);
                if (nextSubtypeHandle == null) {
                    return;
                }
                final InputMethodInfo nextImi = mMethodMap.get(nextSubtypeHandle.getImeId());
                if (nextImi == null) {
                    return;
                }

                final int subtypeCount = nextImi.getSubtypeCount();
                if (subtypeCount == 0) {
                    if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) {
                        setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_ID);
                    }
                    return;
                }

                for (int i = 0; i < subtypeCount; ++i) {
                    if (nextSubtypeHandle.equals(
                            InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) {
                        setInputMethodLocked(nextImi.getId(), i);
                        return;
                    }
                }
                switchKeyboardLayoutLocked(direction);
            }
        }

@@ -6767,5 +6772,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
        public void resetStylusHandwriting(int requestId) {
            mImms.resetStylusHandwriting(requestId);
        }

        @BinderThread
        @Override
        public void switchKeyboardLayoutAsync(int direction) {
            synchronized (ImfLock.class) {
                if (!mImms.calledWithValidTokenLocked(mToken)) {
                    return;
                }
                final long ident = Binder.clearCallingIdentity();
                try {
                    mImms.switchKeyboardLayoutLocked(direction);
                } finally {
                    Binder.restoreCallingIdentity(ident);
                }
            }
        }
    }
}
+23 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
import android.support.test.uiautomator.Until;
import android.util.Log;
import android.view.KeyEvent;
import android.view.WindowManagerGlobal;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -598,6 +599,28 @@ public class InputMethodServiceTest {
                false /* orientationPortrait */);
    }

    @Test
    public void switchesKeyboardLayout_withShortcut_onlyIfImeVisible() throws Exception {
        setShowImeWithHardKeyboard(true /* enabled */);

        assertThat(mInputMethodService.isInputViewShown()).isFalse();
        assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
                new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
                        KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
                        KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isFalse();

        verifyInputViewStatusOnMainSync(
                () -> assertThat(mActivity.showImeWithInputMethodManager(0 /* flags */)).isTrue(),
                true /* expected */,
                true /* inputViewStarted */);

        assertThat(mInputMethodService.isInputViewShown()).isTrue();
        assertThat(mInputMethodService.onKeyDown(KeyEvent.KEYCODE_SPACE,
                new KeyEvent(0 /* downTime */, 0 /* eventTime */, KeyEvent.ACTION_DOWN,
                        KeyEvent.KEYCODE_SPACE, 0 /* repeat */,
                        KeyEvent.META_CTRL_LEFT_ON | KeyEvent.META_CTRL_ON))).isTrue();
    }

    /**
     * This checks that when the system navigation bar is not created (e.g. emulator),
     * then the IME caption bar is also not created.