903 lines
33 KiB
Java
903 lines
33 KiB
Java
/*
|
|
* Copyright (C) 2021 The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package android.inputmethodservice;
|
|
|
|
import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED;
|
|
|
|
import android.annotation.AnyThread;
|
|
import android.annotation.CallbackExecutor;
|
|
import android.annotation.NonNull;
|
|
import android.annotation.Nullable;
|
|
import android.graphics.RectF;
|
|
import android.os.Bundle;
|
|
import android.os.CancellationSignal;
|
|
import android.os.CancellationSignalBeamer;
|
|
import android.os.IBinder;
|
|
import android.os.RemoteException;
|
|
import android.os.ResultReceiver;
|
|
import android.view.KeyEvent;
|
|
import android.view.inputmethod.CompletionInfo;
|
|
import android.view.inputmethod.CorrectionInfo;
|
|
import android.view.inputmethod.ExtractedText;
|
|
import android.view.inputmethod.ExtractedTextRequest;
|
|
import android.view.inputmethod.HandwritingGesture;
|
|
import android.view.inputmethod.InputConnection;
|
|
import android.view.inputmethod.InputContentInfo;
|
|
import android.view.inputmethod.ParcelableHandwritingGesture;
|
|
import android.view.inputmethod.SurroundingText;
|
|
import android.view.inputmethod.TextAttribute;
|
|
import android.view.inputmethod.TextBoundsInfo;
|
|
import android.view.inputmethod.TextBoundsInfoResult;
|
|
|
|
import com.android.internal.infra.AndroidFuture;
|
|
import com.android.internal.inputmethod.IRemoteInputConnection;
|
|
import com.android.internal.inputmethod.InputConnectionCommandHeader;
|
|
|
|
import java.util.Objects;
|
|
import java.util.concurrent.Executor;
|
|
import java.util.function.Consumer;
|
|
import java.util.function.IntConsumer;
|
|
|
|
/**
|
|
* A stateless wrapper of {@link com.android.internal.inputmethod.IRemoteInputConnection} to
|
|
* encapsulate boilerplate code around {@link AndroidFuture} and {@link RemoteException}.
|
|
*/
|
|
final class IRemoteInputConnectionInvoker {
|
|
|
|
@NonNull
|
|
private final IRemoteInputConnection mConnection;
|
|
private final int mSessionId;
|
|
private CancellationSignalBeamer.Sender mBeamer;
|
|
|
|
private IRemoteInputConnectionInvoker(@NonNull IRemoteInputConnection inputConnection,
|
|
int sessionId) {
|
|
mConnection = inputConnection;
|
|
mSessionId = sessionId;
|
|
}
|
|
|
|
private abstract static class OnceResultReceiver<C> extends ResultReceiver {
|
|
@Nullable
|
|
private C mConsumer;
|
|
@Nullable
|
|
private Executor mExecutor;
|
|
|
|
protected OnceResultReceiver(@NonNull Executor executor, @NonNull C consumer) {
|
|
super(null);
|
|
Objects.requireNonNull(executor);
|
|
Objects.requireNonNull(consumer);
|
|
mExecutor = executor;
|
|
mConsumer = consumer;
|
|
}
|
|
|
|
@Override
|
|
protected final void onReceiveResult(int resultCode, Bundle resultData) {
|
|
final Executor executor;
|
|
final C consumer;
|
|
synchronized (this) {
|
|
executor = mExecutor;
|
|
consumer = mConsumer;
|
|
mExecutor = null;
|
|
mConsumer = null;
|
|
}
|
|
if (executor != null && consumer != null) {
|
|
dispatch(executor, consumer, resultCode, resultData);
|
|
}
|
|
}
|
|
|
|
protected abstract void dispatch(@NonNull Executor executor, @NonNull C consumer, int code,
|
|
Bundle data);
|
|
}
|
|
|
|
/**
|
|
* Subclass of {@link ResultReceiver} used by
|
|
* {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)} for providing
|
|
* callback.
|
|
*/
|
|
private static final class IntResultReceiver extends OnceResultReceiver<IntConsumer> {
|
|
IntResultReceiver(@NonNull Executor executor, @NonNull IntConsumer consumer) {
|
|
super(executor, consumer);
|
|
}
|
|
|
|
@Override
|
|
protected void dispatch(@NonNull Executor executor, @NonNull IntConsumer consumer, int code,
|
|
Bundle data) {
|
|
executor.execute(() -> consumer.accept(code));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Subclass of {@link ResultReceiver} used by
|
|
* {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
|
|
* callback.
|
|
*/
|
|
private static final class TextBoundsInfoResultReceiver extends
|
|
OnceResultReceiver<Consumer<TextBoundsInfoResult>> {
|
|
TextBoundsInfoResultReceiver(@NonNull Executor executor,
|
|
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
|
super(executor, consumer);
|
|
}
|
|
|
|
@Override
|
|
protected void dispatch(@NonNull Executor executor,
|
|
@NonNull Consumer<TextBoundsInfoResult> consumer, int code, Bundle data) {
|
|
final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
|
|
code, TextBoundsInfo.createFromBundle(data));
|
|
executor.execute(() -> consumer.accept(textBoundsInfoResult));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given
|
|
* {@link IRemoteInputConnection}.
|
|
*
|
|
* @param connection {@link IRemoteInputConnection} to be wrapped.
|
|
* @return A new instance of {@link IRemoteInputConnectionInvoker}.
|
|
*/
|
|
public static IRemoteInputConnectionInvoker create(@NonNull IRemoteInputConnection connection) {
|
|
Objects.requireNonNull(connection);
|
|
return new IRemoteInputConnectionInvoker(connection, 0);
|
|
}
|
|
|
|
/**
|
|
* Creates a new instance of {@link IRemoteInputConnectionInvoker} with the given
|
|
* {@code sessionId}.
|
|
*
|
|
* @param sessionId the new session ID to be used.
|
|
* @return A new instance of {@link IRemoteInputConnectionInvoker}.
|
|
*/
|
|
@NonNull
|
|
public IRemoteInputConnectionInvoker cloneWithSessionId(int sessionId) {
|
|
return new IRemoteInputConnectionInvoker(mConnection, sessionId);
|
|
}
|
|
|
|
/**
|
|
* @param connection {@code IRemoteInputConnection} to be compared with
|
|
* @return {@code true} if the underlying {@code IRemoteInputConnection} is the same.
|
|
* {@code false} if {@code inputContext} is {@code null}.
|
|
*/
|
|
@AnyThread
|
|
public boolean isSameConnection(@NonNull IRemoteInputConnection connection) {
|
|
if (connection == null) {
|
|
return false;
|
|
}
|
|
return mConnection.asBinder() == connection.asBinder();
|
|
}
|
|
|
|
@NonNull
|
|
InputConnectionCommandHeader createHeader() {
|
|
return new InputConnectionCommandHeader(mSessionId);
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#getTextAfterCursor(InputConnectionCommandHeader, int,
|
|
* int, AndroidFuture)}.
|
|
*
|
|
* @param length {@code length} parameter to be passed.
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<CharSequence> getTextAfterCursor(int length, int flags) {
|
|
final AndroidFuture<CharSequence> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getTextAfterCursor(createHeader(), length, flags, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#getTextBeforeCursor(InputConnectionCommandHeader, int,
|
|
* int, AndroidFuture)}.
|
|
*
|
|
* @param length {@code length} parameter to be passed.
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<CharSequence> getTextBeforeCursor(int length, int flags) {
|
|
final AndroidFuture<CharSequence> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getTextBeforeCursor(createHeader(), length, flags, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#getSelectedText(InputConnectionCommandHeader, int,
|
|
* AndroidFuture)}.
|
|
*
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @return {@link AndroidFuture<CharSequence>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<CharSequence> getSelectedText(int flags) {
|
|
final AndroidFuture<CharSequence> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getSelectedText(createHeader(), flags, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#getSurroundingText(InputConnectionCommandHeader, int, int, int,
|
|
* AndroidFuture)}.
|
|
*
|
|
* @param beforeLength {@code beforeLength} parameter to be passed.
|
|
* @param afterLength {@code afterLength} parameter to be passed.
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @return {@link AndroidFuture<SurroundingText>} that can be used to retrieve the
|
|
* invocation result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<SurroundingText> getSurroundingText(int beforeLength, int afterLength,
|
|
int flags) {
|
|
final AndroidFuture<SurroundingText> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getSurroundingText(createHeader(), beforeLength, afterLength, flags,
|
|
future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#getCursorCapsMode(InputConnectionCommandHeader, int,
|
|
* AndroidFuture)}.
|
|
*
|
|
* @param reqModes {@code reqModes} parameter to be passed.
|
|
* @return {@link AndroidFuture<Integer>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<Integer> getCursorCapsMode(int reqModes) {
|
|
final AndroidFuture<Integer> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getCursorCapsMode(createHeader(), reqModes, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#getExtractedText(InputConnectionCommandHeader,
|
|
* ExtractedTextRequest, int, AndroidFuture)}.
|
|
*
|
|
* @param request {@code request} parameter to be passed.
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @return {@link AndroidFuture<ExtractedText>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<ExtractedText> getExtractedText(ExtractedTextRequest request,
|
|
int flags) {
|
|
final AndroidFuture<ExtractedText> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.getExtractedText(createHeader(), request, flags, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#commitText(InputConnectionCommandHeader, CharSequence, int)}.
|
|
*
|
|
* @param text {@code text} parameter to be passed.
|
|
* @param newCursorPosition {@code newCursorPosition} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean commitText(CharSequence text, int newCursorPosition) {
|
|
try {
|
|
mConnection.commitText(createHeader(), text, newCursorPosition);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#commitTextWithTextAttribute(
|
|
* InputConnectionCommandHeader, int, CharSequence)}.
|
|
*
|
|
* @param text {@code text} parameter to be passed.
|
|
* @param newCursorPosition {@code newCursorPosition} parameter to be passed.
|
|
* @param textAttribute The extra information about the text.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean commitText(CharSequence text, int newCursorPosition,
|
|
@Nullable TextAttribute textAttribute) {
|
|
try {
|
|
mConnection.commitTextWithTextAttribute(
|
|
createHeader(), text, newCursorPosition, textAttribute);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#commitCompletion(InputConnectionCommandHeader,
|
|
* CompletionInfo)}.
|
|
*
|
|
* @param text {@code text} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean commitCompletion(CompletionInfo text) {
|
|
try {
|
|
mConnection.commitCompletion(createHeader(), text);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#commitCorrection(InputConnectionCommandHeader,
|
|
* CorrectionInfo)}.
|
|
*
|
|
* @param correctionInfo {@code correctionInfo} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean commitCorrection(CorrectionInfo correctionInfo) {
|
|
try {
|
|
mConnection.commitCorrection(createHeader(), correctionInfo);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#setSelection(InputConnectionCommandHeader, int, int)}.
|
|
*
|
|
* @param start {@code start} parameter to be passed.
|
|
* @param end {@code start} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setSelection(int start, int end) {
|
|
try {
|
|
mConnection.setSelection(createHeader(), start, end);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#performEditorAction(InputConnectionCommandHeader, int)}.
|
|
*
|
|
* @param actionCode {@code start} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean performEditorAction(int actionCode) {
|
|
try {
|
|
mConnection.performEditorAction(createHeader(), actionCode);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#performContextMenuAction(InputConnectionCommandHeader, int)}.
|
|
*
|
|
* @param id {@code id} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean performContextMenuAction(int id) {
|
|
try {
|
|
mConnection.performContextMenuAction(createHeader(), id);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#setComposingRegion(InputConnectionCommandHeader, int, int)}.
|
|
*
|
|
* @param start {@code id} parameter to be passed.
|
|
* @param end {@code id} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setComposingRegion(int start, int end) {
|
|
try {
|
|
mConnection.setComposingRegion(createHeader(), start, end);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#setComposingRegionWithTextAttribute(
|
|
* InputConnectionCommandHeader, int, int, TextAttribute)}.
|
|
*
|
|
* @param start {@code id} parameter to be passed.
|
|
* @param end {@code id} parameter to be passed.
|
|
* @param textAttribute The extra information about the text.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setComposingRegion(int start, int end, @Nullable TextAttribute textAttribute) {
|
|
try {
|
|
mConnection.setComposingRegionWithTextAttribute(
|
|
createHeader(), start, end, textAttribute);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#setComposingText(InputConnectionCommandHeader,
|
|
* CharSequence, int)}.
|
|
*
|
|
* @param text {@code text} parameter to be passed.
|
|
* @param newCursorPosition {@code newCursorPosition} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setComposingText(CharSequence text, int newCursorPosition) {
|
|
try {
|
|
mConnection.setComposingText(createHeader(), text, newCursorPosition);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#setComposingTextWithTextAttribute(
|
|
* InputConnectionCommandHeader, CharSequence, int, TextAttribute)}.
|
|
*
|
|
* @param text {@code text} parameter to be passed.
|
|
* @param newCursorPosition {@code newCursorPosition} parameter to be passed.
|
|
* @param textAttribute The extra information about the text.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setComposingText(CharSequence text, int newCursorPosition,
|
|
@Nullable TextAttribute textAttribute) {
|
|
try {
|
|
mConnection.setComposingTextWithTextAttribute(
|
|
createHeader(), text, newCursorPosition, textAttribute);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#finishComposingText(InputConnectionCommandHeader)}.
|
|
*
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean finishComposingText() {
|
|
try {
|
|
mConnection.finishComposingText(createHeader());
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#beginBatchEdit(InputConnectionCommandHeader)}.
|
|
*
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean beginBatchEdit() {
|
|
try {
|
|
mConnection.beginBatchEdit(createHeader());
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#endBatchEdit(InputConnectionCommandHeader)}.
|
|
*
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean endBatchEdit() {
|
|
try {
|
|
mConnection.endBatchEdit(createHeader());
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#sendKeyEvent(InputConnectionCommandHeader, KeyEvent)}.
|
|
*
|
|
* @param event {@code event} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean sendKeyEvent(KeyEvent event) {
|
|
try {
|
|
mConnection.sendKeyEvent(createHeader(), event);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#clearMetaKeyStates(InputConnectionCommandHeader, int)}.
|
|
*
|
|
* @param states {@code states} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean clearMetaKeyStates(int states) {
|
|
try {
|
|
mConnection.clearMetaKeyStates(createHeader(), states);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#deleteSurroundingText(InputConnectionCommandHeader, int, int)}.
|
|
*
|
|
* @param beforeLength {@code beforeLength} parameter to be passed.
|
|
* @param afterLength {@code afterLength} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean deleteSurroundingText(int beforeLength, int afterLength) {
|
|
try {
|
|
mConnection.deleteSurroundingText(createHeader(), beforeLength, afterLength);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#deleteSurroundingTextInCodePoints(
|
|
* InputConnectionCommandHeader, int, int)}.
|
|
*
|
|
* @param beforeLength {@code beforeLength} parameter to be passed.
|
|
* @param afterLength {@code afterLength} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) {
|
|
try {
|
|
mConnection.deleteSurroundingTextInCodePoints(createHeader(), beforeLength,
|
|
afterLength);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#performSpellCheck(InputConnectionCommandHeader)}.
|
|
*
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean performSpellCheck() {
|
|
try {
|
|
mConnection.performSpellCheck(createHeader());
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#performPrivateCommand(InputConnectionCommandHeader,
|
|
* String, Bundle)}.
|
|
*
|
|
* @param action {@code action} parameter to be passed.
|
|
* @param data {@code data} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean performPrivateCommand(String action, Bundle data) {
|
|
try {
|
|
mConnection.performPrivateCommand(createHeader(), action, data);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#performHandwritingGesture(
|
|
* InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
|
|
*/
|
|
@AnyThread
|
|
public void performHandwritingGesture(@NonNull HandwritingGesture gesture,
|
|
@Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
|
|
ResultReceiver resultReceiver = null;
|
|
if (consumer != null) {
|
|
Objects.requireNonNull(executor);
|
|
resultReceiver = new IntResultReceiver(executor, consumer);
|
|
}
|
|
try {
|
|
try (var ignored = getCancellationSignalBeamer().beamScopeIfNeeded(gesture)) {
|
|
mConnection.performHandwritingGesture(createHeader(),
|
|
ParcelableHandwritingGesture.of(gesture),
|
|
resultReceiver);
|
|
}
|
|
} catch (RemoteException e) {
|
|
if (consumer != null && executor != null) {
|
|
executor.execute(() -> consumer.accept(
|
|
InputConnection.HANDWRITING_GESTURE_RESULT_CANCELLED));
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes one of {@link IRemoteInputConnection#previewHandwritingGesture(
|
|
* InputConnectionCommandHeader, HandwritingGesture, IBinder)}
|
|
*/
|
|
@AnyThread
|
|
public boolean previewHandwritingGesture(
|
|
@NonNull HandwritingGesture gesture,
|
|
@Nullable CancellationSignal cancellationSignal) {
|
|
try {
|
|
try (var csToken = beam(cancellationSignal)) {
|
|
mConnection.previewHandwritingGesture(createHeader(),
|
|
ParcelableHandwritingGesture.of(gesture),
|
|
csToken);
|
|
}
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
@Nullable
|
|
CancellationSignalBeamer.Sender.CloseableToken beam(CancellationSignal cs) {
|
|
if (cs == null) {
|
|
return null;
|
|
}
|
|
return getCancellationSignalBeamer().beam(cs);
|
|
}
|
|
|
|
private CancellationSignalBeamer.Sender getCancellationSignalBeamer() {
|
|
if (mBeamer != null) {
|
|
return mBeamer;
|
|
}
|
|
mBeamer = new CancellationSignalBeamer.Sender() {
|
|
@Override
|
|
public void onCancel(IBinder token) {
|
|
try {
|
|
mConnection.cancelCancellationSignal(token);
|
|
} catch (RemoteException e) {
|
|
// Remote process likely died, ignore.
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onForget(IBinder token) {
|
|
try {
|
|
mConnection.forgetCancellationSignal(token);
|
|
} catch (RemoteException e) {
|
|
// Remote process likely died, ignore.
|
|
}
|
|
}
|
|
};
|
|
|
|
return mBeamer;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#requestCursorUpdates(InputConnectionCommandHeader, int,
|
|
* int, AndroidFuture)}.
|
|
*
|
|
* @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed.
|
|
* @param imeDisplayId the display ID that is associated with the IME.
|
|
* @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int imeDisplayId) {
|
|
final AndroidFuture<Boolean> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.requestCursorUpdates(createHeader(), cursorUpdateMode, imeDisplayId,
|
|
future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#requestCursorUpdatesWithFilter(
|
|
* InputConnectionCommandHeader, int, int, int, AndroidFuture)}.
|
|
*
|
|
* @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed.
|
|
* @param cursorUpdateFilter {@code cursorUpdateFilter} parameter to be passed.
|
|
* @param imeDisplayId the display ID that is associated with the IME.
|
|
* @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<Boolean> requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter,
|
|
int imeDisplayId) {
|
|
final AndroidFuture<Boolean> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.requestCursorUpdatesWithFilter(createHeader(), cursorUpdateMode,
|
|
cursorUpdateFilter, imeDisplayId, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
|
|
* RectF, ResultReceiver)}
|
|
* @param bounds {@code rectF} parameter to be passed.
|
|
* @param executor {@code Executor} parameter to be passed.
|
|
* @param consumer {@code Consumer} parameter to be passed.
|
|
*/
|
|
@AnyThread
|
|
public void requestTextBoundsInfo(
|
|
@NonNull RectF bounds, @NonNull @CallbackExecutor Executor executor,
|
|
@NonNull Consumer<TextBoundsInfoResult> consumer) {
|
|
Objects.requireNonNull(executor);
|
|
Objects.requireNonNull(consumer);
|
|
|
|
final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
|
|
try {
|
|
mConnection.requestTextBoundsInfo(createHeader(), bounds, resultReceiver);
|
|
} catch (RemoteException e) {
|
|
executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader,
|
|
* InputContentInfo, int, Bundle, AndroidFuture)}.
|
|
*
|
|
* @param inputContentInfo {@code inputContentInfo} parameter to be passed.
|
|
* @param flags {@code flags} parameter to be passed.
|
|
* @param opts {@code opts} parameter to be passed.
|
|
* @return {@link AndroidFuture<Boolean>} that can be used to retrieve the invocation
|
|
* result. {@link RemoteException} will be treated as an error.
|
|
*/
|
|
@AnyThread
|
|
@NonNull
|
|
public AndroidFuture<Boolean> commitContent(InputContentInfo inputContentInfo, int flags,
|
|
Bundle opts) {
|
|
final AndroidFuture<Boolean> future = new AndroidFuture<>();
|
|
try {
|
|
mConnection.commitContent(createHeader(), inputContentInfo, flags, opts, future);
|
|
} catch (RemoteException e) {
|
|
future.completeExceptionally(e);
|
|
}
|
|
return future;
|
|
}
|
|
|
|
/**
|
|
* Invokes
|
|
* {@link IRemoteInputConnection#setImeConsumesInput(InputConnectionCommandHeader, boolean)}.
|
|
*
|
|
* @param imeConsumesInput {@code imeConsumesInput} parameter to be passed.
|
|
* @return {@code true} if the invocation is completed without {@link RemoteException}.
|
|
* {@code false} otherwise.
|
|
*/
|
|
@AnyThread
|
|
public boolean setImeConsumesInput(boolean imeConsumesInput) {
|
|
try {
|
|
mConnection.setImeConsumesInput(createHeader(), imeConsumesInput);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Replaces the specific range in the current input field with suggested text.
|
|
*
|
|
* @param start the character index where the replacement should start.
|
|
* @param end the character index where the replacement should end.
|
|
* @param newCursorPosition the new cursor position around the text. If > 0, this is relative to
|
|
* the end of the text - 1; if <= 0, this is relative to the start of the text. So a value
|
|
* of 1 will always advance you to the position after the full text being inserted. Note
|
|
* that this means you can't position the cursor within the text.
|
|
* @param text the text to replace. This may include styles.
|
|
* @param textAttribute The extra information about the text. This value may be null.
|
|
* @return {@code true} if the specific range is replaced successfully, {@code false} otherwise.
|
|
* @see android.view.inputmethod.InputConnection#replaceText(int, int, CharSequence, int,
|
|
* TextAttribute)
|
|
*/
|
|
@AnyThread
|
|
public boolean replaceText(
|
|
int start,
|
|
int end,
|
|
@NonNull CharSequence text,
|
|
int newCursorPosition,
|
|
@Nullable TextAttribute textAttribute) {
|
|
try {
|
|
mConnection.replaceText(
|
|
createHeader(), start, end, text, newCursorPosition, textAttribute);
|
|
return true;
|
|
} catch (RemoteException e) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|