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
This commit is contained in:
Yohei Yukawa
2021-10-28 15:47:01 -07:00
parent 40a525deaf
commit 5b848f27e4
3 changed files with 141 additions and 3 deletions

View File

@@ -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 */);
}
/**

View File

@@ -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}.
*
* <p>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.</p>
*/
public final int mSessionId;
public InputConnectionCommandHeader(int sessionId) {
mSessionId = sessionId;
}
@Override
@@ -38,7 +50,8 @@ public final class InputConnectionCommandHeader implements Parcelable {
new Parcelable.Creator<InputConnectionCommandHeader>() {
@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);
}
}

View File

@@ -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<View> 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 {
*
* <p>Multiple invocations will be simply ignored.</p>
*/
@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 {
*
* <p>This method is intended to be called only from {@link InputMethodManager}.</p>
*/
@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");