From 5b848f27e47b75605e81d54420cddf85c79c88bb Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Thu, 28 Oct 2021 15:47:01 -0700 Subject: [PATCH] Implement InputConnection task cancellation This CL introduces the task cancellation mechanism into RemoteInputConnection and RemoteInputConnectionImpl. The code idea is to ignore InputConnection tasks if the session IDs do not match. By incrementing the session ID upon every "interruption", we can effectively cancel any pending events issued before the "interruption" happened. Note that even with this CL RemoteInputConnection and RemoteInputConnectionImpl still uses "0" as the session ID. Hence there should be no user observable behavior change yet. Fix: 203086369 Test: presubmit Change-Id: I383c3958d2ac1a8d217706509fa12a92b381bbb3 --- .../inputmethod/IInputContextInvoker.java | 3 +- .../InputConnectionCommandHeader.java | 18 ++- .../RemoteInputConnectionImpl.java | 123 ++++++++++++++++++ 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java index 001e304ca1110..efdf483563ec1 100644 --- a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java +++ b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java @@ -59,7 +59,8 @@ public final class IInputContextInvoker { @NonNull InputConnectionCommandHeader createHeader() { - return new InputConnectionCommandHeader(); + // TODO(b/203086369): Propagate session ID for interruption + return new InputConnectionCommandHeader(0 /* sessionId */); } /** diff --git a/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java b/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java index 4bd8d0cc578b5..d9141bd7a8fa7 100644 --- a/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java +++ b/core/java/com/android/internal/inputmethod/InputConnectionCommandHeader.java @@ -25,7 +25,19 @@ import android.os.Parcelable; * {@link android.inputmethodservice.RemoteInputConnection}. */ public final class InputConnectionCommandHeader implements Parcelable { - public InputConnectionCommandHeader() { + /** + * An identifier that is to be used when multiplexing multiple sessions into a single + * {@link com.android.internal.view.IInputContext}. + * + *

This ID is considered to belong to an implicit namespace defined for each + * {@link com.android.internal.view.IInputContext} instance. Uniqueness of the session ID + * across multiple instances of {@link com.android.internal.view.IInputContext} is not + * guaranteed unless explicitly noted in a higher layer.

+ */ + public final int mSessionId; + + public InputConnectionCommandHeader(int sessionId) { + mSessionId = sessionId; } @Override @@ -38,7 +50,8 @@ public final class InputConnectionCommandHeader implements Parcelable { new Parcelable.Creator() { @NonNull public InputConnectionCommandHeader createFromParcel(Parcel in) { - return new InputConnectionCommandHeader(); + final int sessionId = in.readInt(); + return new InputConnectionCommandHeader(sessionId); } @NonNull @@ -49,5 +62,6 @@ public final class InputConnectionCommandHeader implements Parcelable { @Override public void writeToParcel(@NonNull Parcel dest, int flags) { + dest.writeInt(mSessionId); } } diff --git a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java index 3867afdf0faaa..21358abe243f3 100644 --- a/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java +++ b/core/java/com/android/internal/inputmethod/RemoteInputConnectionImpl.java @@ -23,6 +23,8 @@ import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildG import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetTextAfterCursorProto; import static com.android.internal.inputmethod.InputConnectionProtoDumper.buildGetTextBeforeCursorProto; +import static java.lang.annotation.RetentionPolicy.SOURCE; + import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; @@ -45,7 +47,9 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.infra.AndroidFuture; import com.android.internal.view.IInputContext; +import java.lang.annotation.Retention; import java.lang.ref.WeakReference; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Function; import java.util.function.Supplier; @@ -62,6 +66,11 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { private static final String TAG = "RemoteInputConnectionImpl"; private static final boolean DEBUG = false; + @Retention(SOURCE) + private @interface Dispatching { + boolean cancellable(); + } + @GuardedBy("mLock") @Nullable private InputConnection mInputConnection; @@ -77,6 +86,9 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { private final InputMethodManager mParentInputMethodManager; private final WeakReference mServedView; + // TODO(b/203086369): This is to be used when interruption is implemented. + private final AtomicInteger mCurrentSessionId = new AtomicInteger(0); + public RemoteInputConnectionImpl(@NonNull Looper looper, @NonNull InputConnection inputConnection, @NonNull InputMethodManager inputMethodManager, @Nullable View servedView) { @@ -120,6 +132,7 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { * *

Multiple invocations will be simply ignored.

*/ + @Dispatching(cancellable = false) public void deactivate() { if (isFinished()) { // This is a small performance optimization. Still only the 1st call of @@ -214,6 +227,7 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { * @param enabled the parameter to be passed to * {@link InputConnection#reportFullscreenMode(boolean)}. */ + @Dispatching(cancellable = false) public void dispatchReportFullscreenMode(boolean enabled) { dispatch(() -> { final InputConnection ic = getInputConnection(); @@ -224,10 +238,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void getTextAfterCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getTextAfterCursor", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); @@ -242,10 +260,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }, useImeTracing() ? result -> buildGetTextAfterCursorProto(length, flags, result) : null); } + @Dispatching(cancellable = true) @Override public void getTextBeforeCursor(InputConnectionCommandHeader header, int length, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getTextBeforeCursor", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); @@ -260,10 +282,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }, useImeTracing() ? result -> buildGetTextBeforeCursorProto(length, flags, result) : null); } + @Dispatching(cancellable = true) @Override public void getSelectedText(InputConnectionCommandHeader header, int flags, AndroidFuture future /* T=CharSequence */) { dispatchWithTracing("getSelectedText", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getSelectedText on inactive InputConnection"); @@ -278,10 +304,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }, useImeTracing() ? result -> buildGetSelectedTextProto(flags, result) : null); } + @Dispatching(cancellable = true) @Override public void getSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength, int flags, AndroidFuture future /* T=SurroundingText */) { dispatchWithTracing("getSurroundingText", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getSurroundingText on inactive InputConnection"); @@ -302,10 +332,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { beforeLength, afterLength, flags, result) : null); } + @Dispatching(cancellable = true) @Override public void getCursorCapsMode(InputConnectionCommandHeader header, int reqModes, AndroidFuture future /* T=Integer */) { dispatchWithTracing("getCursorCapsMode", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return 0; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); @@ -315,10 +349,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }, useImeTracing() ? result -> buildGetCursorCapsModeProto(reqModes, result) : null); } + @Dispatching(cancellable = true) @Override public void getExtractedText(InputConnectionCommandHeader header, ExtractedTextRequest request, int flags, AndroidFuture future /* T=ExtractedText */) { dispatchWithTracing("getExtractedText", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return null; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "getExtractedText on inactive InputConnection"); @@ -328,10 +366,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }, useImeTracing() ? result -> buildGetExtractedTextProto(request, flags, result) : null); } + @Dispatching(cancellable = true) @Override public void commitText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition) { dispatchWithTracing("commitText", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitText on inactive InputConnection"); @@ -341,9 +383,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void commitCompletion(InputConnectionCommandHeader header, CompletionInfo text) { dispatchWithTracing("commitCompletion", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitCompletion on inactive InputConnection"); @@ -353,9 +399,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void commitCorrection(InputConnectionCommandHeader header, CorrectionInfo info) { dispatchWithTracing("commitCorrection", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitCorrection on inactive InputConnection"); @@ -369,9 +419,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void setSelection(InputConnectionCommandHeader header, int start, int end) { dispatchWithTracing("setSelection", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setSelection on inactive InputConnection"); @@ -381,9 +435,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void performEditorAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performEditorAction", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performEditorAction on inactive InputConnection"); @@ -393,9 +451,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void performContextMenuAction(InputConnectionCommandHeader header, int id) { dispatchWithTracing("performContextMenuAction", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performContextMenuAction on inactive InputConnection"); @@ -405,9 +467,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void setComposingRegion(InputConnectionCommandHeader header, int start, int end) { dispatchWithTracing("setComposingRegion", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setComposingRegion on inactive InputConnection"); @@ -421,10 +487,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void setComposingText(InputConnectionCommandHeader header, CharSequence text, int newCursorPosition) { dispatchWithTracing("setComposingText", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setComposingText on inactive InputConnection"); @@ -439,7 +509,9 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { * *

This method is intended to be called only from {@link InputMethodManager}.

*/ + @Dispatching(cancellable = true) public void finishComposingTextFromImm() { + final int currentSessionId = mCurrentSessionId.get(); dispatchWithTracing("finishComposingTextFromImm", () -> { if (isFinished()) { // In this case, #finishComposingText() is guaranteed to be called already. @@ -449,6 +521,9 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { } return; } + if (currentSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); // Note we do NOT check isActive() here, because this is safe // for an IME to call at any time, and we need to allow it @@ -462,6 +537,7 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void finishComposingText(InputConnectionCommandHeader header) { dispatchWithTracing("finishComposingText", () -> { @@ -473,6 +549,9 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { } return; } + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); // Note we do NOT check isActive() here, because this is safe // for an IME to call at any time, and we need to allow it @@ -486,9 +565,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void sendKeyEvent(InputConnectionCommandHeader header, KeyEvent event) { dispatchWithTracing("sendKeyEvent", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "sendKeyEvent on inactive InputConnection"); @@ -498,9 +581,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void clearMetaKeyStates(InputConnectionCommandHeader header, int states) { dispatchWithTracing("clearMetaKeyStates", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); @@ -510,10 +597,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void deleteSurroundingText(InputConnectionCommandHeader header, int beforeLength, int afterLength) { dispatchWithTracing("deleteSurroundingText", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); @@ -523,10 +614,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void deleteSurroundingTextInCodePoints(InputConnectionCommandHeader header, int beforeLength, int afterLength) { dispatchWithTracing("deleteSurroundingTextInCodePoints", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); @@ -540,9 +635,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void beginBatchEdit(InputConnectionCommandHeader header) { dispatchWithTracing("beginBatchEdit", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "beginBatchEdit on inactive InputConnection"); @@ -552,9 +651,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void endBatchEdit(InputConnectionCommandHeader header) { dispatchWithTracing("endBatchEdit", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "endBatchEdit on inactive InputConnection"); @@ -564,9 +667,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void performSpellCheck(InputConnectionCommandHeader header) { dispatchWithTracing("performSpellCheck", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performSpellCheck on inactive InputConnection"); @@ -576,10 +683,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void performPrivateCommand(InputConnectionCommandHeader header, String action, Bundle data) { dispatchWithTracing("performPrivateCommand", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performPrivateCommand on inactive InputConnection"); @@ -589,10 +700,14 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode, int imeDisplayId, AndroidFuture future /* T=Boolean */) { dispatchWithTracing("requestCursorUpdates", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return false; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); @@ -611,11 +726,15 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void commitContent(InputConnectionCommandHeader header, InputContentInfo inputContentInfo, int flags, Bundle opts, AndroidFuture future /* T=Boolean */) { dispatchWithTracing("commitContent", future, () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return false; // cancelled + } final InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitContent on inactive InputConnection"); @@ -634,9 +753,13 @@ public final class RemoteInputConnectionImpl extends IInputContext.Stub { }); } + @Dispatching(cancellable = true) @Override public void setImeConsumesInput(InputConnectionCommandHeader header, boolean imeConsumesInput) { dispatchWithTracing("setImeConsumesInput", () -> { + if (header.mSessionId != mCurrentSessionId.get()) { + return; // cancelled + } InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setImeConsumesInput on inactive InputConnection");