/* * 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 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 { 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> { TextBoundsInfoResultReceiver(@NonNull Executor executor, @NonNull Consumer consumer) { super(executor, consumer); } @Override protected void dispatch(@NonNull Executor executor, @NonNull Consumer 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getTextAfterCursor(int length, int flags) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getTextBeforeCursor(int length, int flags) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getSelectedText(int flags) { final AndroidFuture 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} that can be used to retrieve the * invocation result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getSurroundingText(int beforeLength, int afterLength, int flags) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getCursorCapsMode(int reqModes) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture getExtractedText(ExtractedTextRequest request, int flags) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture requestCursorUpdates(int cursorUpdateMode, int imeDisplayId) { final AndroidFuture 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture requestCursorUpdates(int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId) { final AndroidFuture 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 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} that can be used to retrieve the invocation * result. {@link RemoteException} will be treated as an error. */ @AnyThread @NonNull public AndroidFuture commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { final AndroidFuture 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; } } }