diff options
6 files changed, 104 insertions, 2 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 5b9f1ef3408a..bf93860f58db 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -21070,6 +21070,7 @@ package android.inputmethodservice { method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodImpl onCreateInputMethodInterface(); method public abstract android.inputmethodservice.AbstractInputMethodService.AbstractInputMethodSessionImpl onCreateInputMethodSessionInterface(); method public boolean onGenericMotionEvent(android.view.MotionEvent); + method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent); method public boolean onTrackballEvent(android.view.MotionEvent); } @@ -21087,6 +21088,7 @@ package android.inputmethodservice { method public void dispatchTrackballEvent(int, android.view.MotionEvent, android.view.inputmethod.InputMethodSession.EventCallback); method public boolean isEnabled(); method public boolean isRevoked(); + method @FlaggedApi("android.view.inputmethod.verify_key_event") public boolean onShouldVerifyKeyEvent(@NonNull android.view.KeyEvent); method public void revokeSelf(); method public void setEnabled(boolean); } diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 4bc5bd2427ea..26308f69cfbe 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -16,6 +16,9 @@ package android.inputmethodservice; +import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; + +import android.annotation.FlaggedApi; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; @@ -193,6 +196,12 @@ public abstract class AbstractInputMethodService extends WindowProviderService } } + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + @Override + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return AbstractInputMethodService.this.onShouldVerifyKeyEvent(event); + } + /** * Take care of dispatching incoming trackball events to the appropriate * callbacks on the service, and tell the client when this is done. @@ -308,6 +317,14 @@ public abstract class AbstractInputMethodService extends WindowProviderService return false; } + /** + * @see InputMethodService#onShouldVerifyKeyEvent(KeyEvent) + */ + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return false; + } + /** @hide */ @Override public final int getWindowType() { diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 62b131af74fe..9b37533f5b02 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -16,12 +16,16 @@ package android.inputmethodservice; +import static android.view.inputmethod.Flags.verifyKeyEvent; + import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.graphics.Rect; +import android.hardware.input.InputManager; import android.os.Bundle; import android.os.Looper; import android.os.Message; +import android.os.SystemClock; import android.util.Log; import android.util.SparseArray; import android.view.InputChannel; @@ -41,6 +45,8 @@ import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.os.HandlerCaller; import com.android.internal.os.SomeArgs; +import java.util.Objects; + class IInputMethodSessionWrapper extends IInputMethodSession.Stub implements HandlerCaller.Callback { private static final String TAG = "InputMethodWrapper"; @@ -56,6 +62,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_REMOVE_IME_SURFACE = 130; private static final int DO_FINISH_INPUT = 140; private static final int DO_INVALIDATE_INPUT = 150; + private final Context mContext; @UnsupportedAppUsage @@ -66,6 +73,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub public IInputMethodSessionWrapper(Context context, InputMethodSession inputMethodSession, InputChannel channel) { + mContext = context; mCaller = new HandlerCaller(context, null, this, true /*asyncHandler*/); mInputMethodSession = inputMethodSession; @@ -233,6 +241,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub } private final class ImeInputEventReceiver extends InputEventReceiver implements InputMethodSession.EventCallback { + // Time after which a KeyEvent is invalid + private static final long KEY_EVENT_ALLOW_PERIOD_MS = 100L; private final SparseArray<InputEvent> mPendingEvents = new SparseArray<InputEvent>(); public ImeInputEventReceiver(InputChannel inputChannel, Looper looper) { @@ -247,10 +257,23 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub return; } + if (event instanceof KeyEvent keyEvent && needsVerification(keyEvent)) { + // any KeyEvent with modifiers (e.g. Ctrl/Alt/Fn) must be verified that + // they originated from system. + InputManager im = mContext.getSystemService(InputManager.class); + Objects.requireNonNull(im); + final long age = SystemClock.uptimeMillis() - keyEvent.getEventTime(); + if (age >= KEY_EVENT_ALLOW_PERIOD_MS && im.verifyInputEvent(keyEvent) == null) { + Log.w(TAG, "Unverified or Invalid KeyEvent injected into IME. Dropping " + + keyEvent); + finishInputEvent(event, false /* handled */); + return; + } + } + final int seq = event.getSequenceNumber(); mPendingEvents.put(seq, event); - if (event instanceof KeyEvent) { - KeyEvent keyEvent = (KeyEvent)event; + if (event instanceof KeyEvent keyEvent) { mInputMethodSession.dispatchKeyEvent(seq, keyEvent, this); } else { MotionEvent motionEvent = (MotionEvent)event; @@ -271,5 +294,21 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub finishInputEvent(event, handled); } } + + private boolean hasKeyModifiers(KeyEvent event) { + if (event.hasNoModifiers()) { + return false; + } + return event.hasModifiers(KeyEvent.META_CTRL_ON) + || event.hasModifiers(KeyEvent.META_ALT_ON) + || event.hasModifiers(KeyEvent.KEYCODE_FUNCTION); + } + + private boolean needsVerification(KeyEvent event) { + //TODO(b/331730488): Handle a11y events as well. + return verifyKeyEvent() + && (hasKeyModifiers(event) + || mInputMethodSession.onShouldVerifyKeyEvent(event)); + } } } diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index dadb5c386b76..a8fde4a3f7c6 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -56,6 +56,7 @@ import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECT import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; import static android.view.inputmethod.Flags.FLAG_CONNECTIONLESS_HANDWRITING; import static android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API; +import static android.view.inputmethod.Flags.FLAG_VERIFY_KEY_EVENT; import static android.view.inputmethod.Flags.ctrlShiftShortcut; import static android.view.inputmethod.Flags.predictiveBackIme; @@ -3735,6 +3736,23 @@ public class InputMethodService extends AbstractInputMethodService { } /** + * Received by the IME before dispatch to {@link #onKeyDown(int, KeyEvent)} to let the system + * know if the {@link KeyEvent} needs to be verified that it originated from the system. + * {@link KeyEvent}s may originate from outside of the system and any sensitive keys should be + * marked for verification. One example of this could be using key shortcuts for switching to + * another IME. + * + * @param keyEvent the event that may need verification. + * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch, + * {@code false} otherwise. + */ + @FlaggedApi(FLAG_VERIFY_KEY_EVENT) + @Override + public boolean onShouldVerifyKeyEvent(@NonNull KeyEvent keyEvent) { + return false; + } + + /** * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle * the event). diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index 4f48cb684e8c..1806a8369d01 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -16,6 +16,7 @@ package android.view.inputmethod; +import android.annotation.NonNull; import android.graphics.Rect; import android.inputmethodservice.InputMethodService; import android.os.Bundle; @@ -125,6 +126,23 @@ public interface InputMethodSession { public void dispatchKeyEvent(int seq, KeyEvent event, EventCallback callback); /** + * Received by the IME before dispatch to {@link InputMethodService#onKeyDown(int, KeyEvent)} + * to let the system know if the {@link KeyEvent} needs to be verified that it originated from + * the system. {@link KeyEvent}s may originate from outside of the system and any sensitive keys + * should be marked for verification. One example of this could be using key shortcuts for + * switching to another IME. + * + * @param event the event that may need verification. + * @return {@code true} if {@link KeyEvent} should have its HMAC verified before dispatch, + * {@code false} otherwise. + * + * @hide + */ + default boolean onShouldVerifyKeyEvent(@NonNull KeyEvent event) { + return false; + } + + /** * This method is called when there is a track ball event. * * <p> diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig index deaf95797127..73abc472be6d 100644 --- a/core/java/android/view/inputmethod/flags.aconfig +++ b/core/java/android/view/inputmethod/flags.aconfig @@ -184,3 +184,11 @@ flag { bug: "350047836" is_fixed_read_only: true } + +flag { + name: "verify_key_event" + namespace: "input_method" + description: "Verify KeyEvents in IME" + bug: "331730488" + is_fixed_read_only: true +} |