diff --git a/core/java/com/android/internal/inputmethod/CallbackUtils.java b/core/java/com/android/internal/inputmethod/CallbackUtils.java index 1933c54d267f0..adeb46a48439d 100644 --- a/core/java/com/android/internal/inputmethod/CallbackUtils.java +++ b/core/java/com/android/internal/inputmethod/CallbackUtils.java @@ -17,7 +17,11 @@ package com.android.internal.inputmethod; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.RemoteException; +import android.util.Log; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.SurroundingText; import java.util.function.BooleanSupplier; import java.util.function.IntSupplier; @@ -137,4 +141,143 @@ public final class CallbackUtils { callback.onResult(result); } catch (RemoteException ignored) { } } + + /** + * A utility method to reply associated with {@link InputConnectionCommand}. + * + * @param command {@link InputConnectionCommand} to be replied. + * @param result a {@link String} value to be replied. + * @param tag tag name to be used for debug output when the invocation fails. + */ + public static void onResult(@NonNull InputConnectionCommand command, boolean result, + @Nullable String tag) { + if (command.mResultCallbackType != InputConnectionCommand.ResultCallbackType.BOOLEAN) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result + " due to callback type mismatch." + + " expected=String actual=" + command.mResultCallbackType); + } + return; + } + try { + IBooleanResultCallback.Stub.asInterface(command.mResultCallback).onResult(result); + } catch (Throwable e) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result, e); + } + } + } + + /** + * A utility method to reply associated with {@link InputConnectionCommand}. + * + * @param command {@link InputConnectionCommand} to be replied. + * @param result an int result value to be replied. + * @param tag tag name to be used for debug output when the invocation fails. + */ + public static void onResult(@NonNull InputConnectionCommand command, int result, + @Nullable String tag) { + if (command.mResultCallbackType != InputConnectionCommand.ResultCallbackType.INT) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result + " due to callback type mismatch." + + " expected=int actual=" + command.mResultCallbackType); + } + return; + } + try { + IIntResultCallback.Stub.asInterface(command.mResultCallback).onResult(result); + } catch (Throwable e) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result, e); + } + } + } + + /** + * A utility method to reply associated with {@link InputConnectionCommand}. + * + * @param command {@link InputConnectionCommand} to be replied. + * @param result a {@link CharSequence} result value to be replied. + * @param tag tag name to be used for debug output when the invocation fails. + */ + public static void onResult(@NonNull InputConnectionCommand command, + @Nullable CharSequence result, @Nullable String tag) { + if (command.mResultCallbackType + != InputConnectionCommand.ResultCallbackType.CHAR_SEQUENCE) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result + " due to callback type mismatch." + + " expected=CharSequence actual=" + command.mResultCallbackType); + } + return; + } + try { + ICharSequenceResultCallback.Stub.asInterface(command.mResultCallback).onResult(result); + } catch (Throwable e) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result, e); + } + } + } + + /** + * A utility method to reply associated with {@link InputConnectionCommand}. + * + * @param command {@link InputConnectionCommand} to be replied. + * @param result a {@link ExtractedText} result value to be replied. + * @param tag tag name to be used for debug output when the invocation fails. + */ + public static void onResult(@NonNull InputConnectionCommand command, + @Nullable ExtractedText result, @Nullable String tag) { + if (command.mResultCallbackType + != InputConnectionCommand.ResultCallbackType.EXTRACTED_TEXT) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result + " due to callback type mismatch." + + " expected=ExtractedText actual=" + command.mResultCallbackType); + } + return; + } + try { + IExtractedTextResultCallback.Stub.asInterface(command.mResultCallback).onResult(result); + } catch (Throwable e) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result, e); + } + } + } + + /** + * A utility method to reply associated with {@link InputConnectionCommand}. + * + * @param command {@link InputConnectionCommand} to be replied. + * @param result a {@link SurroundingText} result value to be replied. + * @param tag tag name to be used for debug output when the invocation fails. + */ + public static void onResult(@NonNull InputConnectionCommand command, + @Nullable SurroundingText result, @Nullable String tag) { + if (command.mResultCallbackType + != InputConnectionCommand.ResultCallbackType.SURROUNDING_TEXT) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result + " due to callback type mismatch." + + " expected=SurroundingText actual=" + command.mResultCallbackType); + } + return; + } + try { + ISurroundingTextResultCallback.Stub.asInterface(command.mResultCallback) + .onResult(result); + } catch (Throwable e) { + if (tag != null) { + Log.e(tag, InputMethodDebug.inputConnectionCommandTypeToString(command.mCommandType) + + ": Failed to return result=" + result, e); + } + } + } } diff --git a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java index 977f9a5a51105..7a43a55040a53 100644 --- a/core/java/com/android/internal/inputmethod/IInputContextInvoker.java +++ b/core/java/com/android/internal/inputmethod/IInputContextInvoker.java @@ -18,6 +18,7 @@ package com.android.internal.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.os.Bundle; import android.os.RemoteException; import android.view.KeyEvent; @@ -32,7 +33,7 @@ import java.util.Objects; /** * A stateless wrapper of {@link com.android.internal.view.IInputContext} to encapsulate boilerplate - * code around {@link Completable} and {@link RemoteException}. + * code around {@link InputConnectionCommand}, {@link Completable} and {@link RemoteException}. */ public final class IInputContextInvoker { @@ -55,8 +56,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#getTextAfterCursor(int, int, - * com.android.internal.inputmethod.ICharSequenceResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#getTextAfterCursor(int, int)}. * * @param length {@code length} parameter to be passed. * @param flags {@code flags} parameter to be passed. @@ -67,8 +67,9 @@ public final class IInputContextInvoker { @NonNull public Completable.CharSequence getTextAfterCursor(int length, int flags) { final Completable.CharSequence value = Completable.createCharSequence(); + final InputConnectionCommand command = Commands.getTextAfterCursor(length, flags, value); try { - mIInputContext.getTextAfterCursor(length, flags, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -76,7 +77,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#getTextBeforeCursor(int, int, ICharSequenceResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#getTextBeforeCursor(int, int)}. * * @param length {@code length} parameter to be passed. * @param flags {@code flags} parameter to be passed. @@ -87,8 +88,9 @@ public final class IInputContextInvoker { @NonNull public Completable.CharSequence getTextBeforeCursor(int length, int flags) { final Completable.CharSequence value = Completable.createCharSequence(); + final InputConnectionCommand command = Commands.getTextBeforeCursor(length, flags, value); try { - mIInputContext.getTextBeforeCursor(length, flags, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -96,7 +98,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#getSelectedText(int, ICharSequenceResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#getSelectedText(int)}. * * @param flags {@code flags} parameter to be passed. * @return {@link Completable.CharSequence} that can be used to retrieve the invocation result. @@ -106,8 +108,9 @@ public final class IInputContextInvoker { @NonNull public Completable.CharSequence getSelectedText(int flags) { final Completable.CharSequence value = Completable.createCharSequence(); + final InputConnectionCommand command = Commands.getSelectedText(flags, value); try { - mIInputContext.getSelectedText(flags, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -115,8 +118,8 @@ public final class IInputContextInvoker { } /** - * Invokes - * {@link IInputContext#getSurroundingText(int, int, int, ISurroundingTextResultCallback)}. + * Implements + * {@link android.view.inputmethod.InputConnection#getSurroundingText(int, int, int)}. * * @param beforeLength {@code beforeLength} parameter to be passed. * @param afterLength {@code afterLength} parameter to be passed. @@ -129,9 +132,10 @@ public final class IInputContextInvoker { public Completable.SurroundingText getSurroundingText(int beforeLength, int afterLength, int flags) { final Completable.SurroundingText value = Completable.createSurroundingText(); + final InputConnectionCommand command = + Commands.getSurroundingText(beforeLength, afterLength, flags, value); try { - mIInputContext.getSurroundingText(beforeLength, afterLength, flags, - ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -139,7 +143,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#getCursorCapsMode(int, IIntResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#getCursorCapsMode(int)}. * * @param reqModes {@code reqModes} parameter to be passed. * @return {@link Completable.Int} that can be used to retrieve the invocation result. @@ -149,8 +153,9 @@ public final class IInputContextInvoker { @NonNull public Completable.Int getCursorCapsMode(int reqModes) { final Completable.Int value = Completable.createInt(); + final InputConnectionCommand command = Commands.getCursorCapsMode(reqModes, value); try { - mIInputContext.getCursorCapsMode(reqModes, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -158,8 +163,8 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#getExtractedText(ExtractedTextRequest, int, - * IExtractedTextResultCallback)}. + * Implements + * {@link android.view.inputmethod.InputConnection#getExtractedText(ExtractedTextRequest, int)}. * * @param request {@code request} parameter to be passed. * @param flags {@code flags} parameter to be passed. @@ -170,8 +175,9 @@ public final class IInputContextInvoker { @NonNull public Completable.ExtractedText getExtractedText(ExtractedTextRequest request, int flags) { final Completable.ExtractedText value = Completable.createExtractedText(); + final InputConnectionCommand command = Commands.getExtractedText(request, flags, value); try { - mIInputContext.getExtractedText(request, flags, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -179,7 +185,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#commitText(CharSequence, int)}. + * Implements {@link android.view.inputmethod.InputConnection#commitText(CharSequence, int)}. * * @param text {@code text} parameter to be passed. * @param newCursorPosition {@code newCursorPosition} parameter to be passed. @@ -188,8 +194,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean commitText(CharSequence text, int newCursorPosition) { + final InputConnectionCommand command = Commands.commitText(text, newCursorPosition); try { - mIInputContext.commitText(text, newCursorPosition); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -197,7 +204,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#commitCompletion(CompletionInfo)}. + * Implements {@link android.view.inputmethod.InputConnection#commitCompletion(CompletionInfo)}. * * @param text {@code text} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -205,8 +212,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean commitCompletion(CompletionInfo text) { + final InputConnectionCommand command = Commands.commitCompletion(text); try { - mIInputContext.commitCompletion(text); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -214,7 +222,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#commitCorrection(CorrectionInfo)}. + * Implements {@link android.view.inputmethod.InputConnection#commitCorrection(CorrectionInfo)}. * * @param correctionInfo {@code correctionInfo} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -222,8 +230,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean commitCorrection(CorrectionInfo correctionInfo) { + final InputConnectionCommand command = Commands.commitCorrection(correctionInfo); try { - mIInputContext.commitCorrection(correctionInfo); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -231,7 +240,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#setSelection(int, int)}. + * Implements {@link android.view.inputmethod.InputConnection#setSelection(int, int)}. * * @param start {@code start} parameter to be passed. * @param end {@code start} parameter to be passed. @@ -240,8 +249,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean setSelection(int start, int end) { + final InputConnectionCommand command = Commands.setSelection(start, end); try { - mIInputContext.setSelection(start, end); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -249,7 +259,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#performEditorAction(int)}. + * Implements {@link android.view.inputmethod.InputConnection#performEditorAction(int)}. * * @param actionCode {@code start} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -257,8 +267,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean performEditorAction(int actionCode) { + final InputConnectionCommand command = Commands.performEditorAction(actionCode); try { - mIInputContext.performEditorAction(actionCode); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -266,7 +277,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#performContextMenuAction(id)}. + * Implements {@link android.view.inputmethod.InputConnection#performContextMenuAction(int)}. * * @param id {@code id} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -274,8 +285,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean performContextMenuAction(int id) { + final InputConnectionCommand command = Commands.performContextMenuAction(id); try { - mIInputContext.performContextMenuAction(id); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -283,7 +295,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#setComposingRegion(int, int)}. + * Implements {@link android.view.inputmethod.InputConnection#setComposingRegion(int, int)}. * * @param start {@code id} parameter to be passed. * @param end {@code id} parameter to be passed. @@ -292,8 +304,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean setComposingRegion(int start, int end) { + final InputConnectionCommand command = Commands.setComposingRegion(start, end); try { - mIInputContext.setComposingRegion(start, end); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -301,7 +314,8 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#setComposingText(CharSequence, int)}. + * Implements + * {@link android.view.inputmethod.InputConnection#setComposingText(CharSequence, int)}. * * @param text {@code text} parameter to be passed. * @param newCursorPosition {@code newCursorPosition} parameter to be passed. @@ -310,8 +324,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean setComposingText(CharSequence text, int newCursorPosition) { + final InputConnectionCommand command = Commands.setComposingText(text, newCursorPosition); try { - mIInputContext.setComposingText(text, newCursorPosition); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -319,15 +334,16 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#finishComposingText()}. + * Implements {@link android.view.inputmethod.InputConnection#finishComposingText()}. * * @return {@code true} if the invocation is completed without {@link RemoteException}. * {@code false} otherwise. */ @AnyThread public boolean finishComposingText() { + final InputConnectionCommand command = Commands.finishComposingText(); try { - mIInputContext.finishComposingText(); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -335,15 +351,16 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#beginBatchEdit()}. + * Implements {@link android.view.inputmethod.InputConnection#beginBatchEdit()}. * * @return {@code true} if the invocation is completed without {@link RemoteException}. * {@code false} otherwise. */ @AnyThread public boolean beginBatchEdit() { + final InputConnectionCommand command = Commands.beginBatchEdit(); try { - mIInputContext.beginBatchEdit(); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -351,15 +368,16 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#endBatchEdit()}. + * Implements {@link android.view.inputmethod.InputConnection#endBatchEdit()}. * * @return {@code true} if the invocation is completed without {@link RemoteException}. * {@code false} otherwise. */ @AnyThread public boolean endBatchEdit() { + final InputConnectionCommand command = Commands.endBatchEdit(); try { - mIInputContext.endBatchEdit(); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -367,7 +385,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#sendKeyEvent(KeyEvent)}. + * Implements {@link android.view.inputmethod.InputConnection#sendKeyEvent(KeyEvent)}. * * @param event {@code event} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -375,8 +393,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean sendKeyEvent(KeyEvent event) { + final InputConnectionCommand command = Commands.sendKeyEvent(event); try { - mIInputContext.sendKeyEvent(event); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -384,7 +403,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#clearMetaKeyStates(int)}. + * Implements {@link android.view.inputmethod.InputConnection#clearMetaKeyStates(int)}. * * @param states {@code states} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -392,8 +411,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean clearMetaKeyStates(int states) { + final InputConnectionCommand command = Commands.clearMetaKeyStates(states); try { - mIInputContext.clearMetaKeyStates(states); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -401,7 +421,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#deleteSurroundingText(int, int)}. + * Implements {@link android.view.inputmethod.InputConnection#deleteSurroundingText(int, int)}. * * @param beforeLength {@code beforeLength} parameter to be passed. * @param afterLength {@code afterLength} parameter to be passed. @@ -410,8 +430,10 @@ public final class IInputContextInvoker { */ @AnyThread public boolean deleteSurroundingText(int beforeLength, int afterLength) { + final InputConnectionCommand command = + Commands.deleteSurroundingText(beforeLength, afterLength); try { - mIInputContext.deleteSurroundingText(beforeLength, afterLength); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -419,7 +441,8 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#deleteSurroundingTextInCodePoints(int, int)}. + * Implements + * {@link android.view.inputmethod.InputConnection#deleteSurroundingTextInCodePoints(int, int)}. * * @param beforeLength {@code beforeLength} parameter to be passed. * @param afterLength {@code afterLength} parameter to be passed. @@ -428,8 +451,10 @@ public final class IInputContextInvoker { */ @AnyThread public boolean deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { + final InputConnectionCommand command = + Commands.deleteSurroundingTextInCodePoints(beforeLength, afterLength); try { - mIInputContext.deleteSurroundingTextInCodePoints(beforeLength, afterLength); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -437,15 +462,16 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#performSpellCheck()}. + * Implements {@link android.view.inputmethod.InputConnection#performSpellCheck()}. * * @return {@code true} if the invocation is completed without {@link RemoteException}. * {@code false} otherwise. */ @AnyThread public boolean performSpellCheck() { + final InputConnectionCommand command = Commands.performSpellCheck(); try { - mIInputContext.performSpellCheck(); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -453,7 +479,8 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#performPrivateCommand(String, Bundle)}. + * Implements + * {@link android.view.inputmethod.InputConnection#performPrivateCommand(String, Bundle)}. * * @param action {@code action} parameter to be passed. * @param data {@code data} parameter to be passed. @@ -462,8 +489,9 @@ public final class IInputContextInvoker { */ @AnyThread public boolean performPrivateCommand(String action, Bundle data) { + final InputConnectionCommand command = Commands.performPrivateCommand(action, data); try { - mIInputContext.performPrivateCommand(action, data); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; @@ -471,7 +499,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#requestCursorUpdates(int, IIntResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#requestCursorUpdates(int)}. * * @param cursorUpdateMode {@code cursorUpdateMode} parameter to be passed. * @return {@link Completable.Boolean} that can be used to retrieve the invocation result. @@ -481,8 +509,10 @@ public final class IInputContextInvoker { @NonNull public Completable.Boolean requestCursorUpdates(int cursorUpdateMode) { final Completable.Boolean value = Completable.createBoolean(); + final InputConnectionCommand command = + Commands.requestCursorUpdates(cursorUpdateMode, value); try { - mIInputContext.requestCursorUpdates(cursorUpdateMode, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -490,8 +520,8 @@ public final class IInputContextInvoker { } /** - * Invokes - * {@link IInputContext#commitContent(InputContentInfo, int, Bundle, IIntResultCallback)}. + * Implements {@link android.view.inputmethod.InputConnection#commitContent(InputContentInfo, + * int, Bundle)}. * * @param inputContentInfo {@code inputContentInfo} parameter to be passed. * @param flags {@code flags} parameter to be passed. @@ -504,8 +534,10 @@ public final class IInputContextInvoker { public Completable.Boolean commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts) { final Completable.Boolean value = Completable.createBoolean(); + final InputConnectionCommand command = + Commands.commitContent(inputContentInfo, flags, opts, value); try { - mIInputContext.commitContent(inputContentInfo, flags, opts, ResultCallbacks.of(value)); + mIInputContext.doEdit(command); } catch (RemoteException e) { value.onError(ThrowableHolder.of(e)); } @@ -513,7 +545,7 @@ public final class IInputContextInvoker { } /** - * Invokes {@link IInputContext#setImeConsumesInput(boolean)}. + * Implements {@link android.view.inputmethod.InputConnection#setImeConsumesInput(boolean)}. * * @param imeConsumesInput {@code imeConsumesInput} parameter to be passed. * @return {@code true} if the invocation is completed without {@link RemoteException}. @@ -521,11 +553,341 @@ public final class IInputContextInvoker { */ @AnyThread public boolean setImeConsumesInput(boolean imeConsumesInput) { + final InputConnectionCommand command = Commands.setImeConsumesInput(imeConsumesInput); try { - mIInputContext.setImeConsumesInput(imeConsumesInput); + mIInputContext.doEdit(command); return true; } catch (RemoteException e) { return false; } } + + /** + * Defines the data packing rules from {@link android.view.inputmethod.InputConnection} API + * params into {@link InputConnectionCommand} fields. + * + * Rules need to be in sync with {@link com.android.internal.view.IInputConnectionWrapper} and + * {@link InputMethodDebug#dumpInputConnectionCommand(InputConnectionCommand)}. + */ + private static final class Commands { + /** + * Not intended to be instantiated. + */ + private Commands() { } + + @AnyThread + @NonNull + static InputConnectionCommand getTextAfterCursor(int n, int flags, + @NonNull Completable.CharSequence returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_TEXT_AFTER_CURSOR, + n, + 0, + flags, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand getTextBeforeCursor(int n, int flags, + @NonNull Completable.CharSequence returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_TEXT_BEFORE_CURSOR, + n, + 0, + flags, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand getSelectedText(int flags, + @NonNull Completable.CharSequence returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_SELECTED_TEXT, + 0, + 0, + flags, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand getSurroundingText(int beforeLength, int afterLength, + int flags, @NonNull Completable.SurroundingText returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_SURROUNDING_TEXT, + beforeLength, + afterLength, + flags, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand getCursorCapsMode(int reqModes, + @NonNull Completable.Int returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_CURSOR_CAPS_MODE, + reqModes, + 0, + 0, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand getExtractedText(@Nullable ExtractedTextRequest request, + int flags, @NonNull Completable.ExtractedText returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.GET_EXTRACTED_TEXT, + 0, + 0, + flags, + null, + null, + null, + InputConnectionCommand.ParcelableType.EXTRACTED_TEXT_REQUEST, + request, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand commitText(@Nullable CharSequence text, + int newCursorPosition) { + return InputConnectionCommand.create( + InputConnectionCommandType.COMMIT_TEXT, + newCursorPosition, + 0, + 0, + text); + } + + @AnyThread + @NonNull + static InputConnectionCommand commitCompletion(@Nullable CompletionInfo text) { + return InputConnectionCommand.create( + InputConnectionCommandType.COMMIT_COMPLETION, + 0, + 0, + 0, + null, + null, + null, + InputConnectionCommand.ParcelableType.COMPLETION_INFO, + text); + } + + @AnyThread + @NonNull + static InputConnectionCommand commitCorrection(@Nullable CorrectionInfo correctionInfo) { + return InputConnectionCommand.create( + InputConnectionCommandType.COMMIT_CORRECTION, + 0, + 0, + 0, + null, + null, + null, + InputConnectionCommand.ParcelableType.CORRECTION_INFO, + correctionInfo); + } + + @AnyThread + @NonNull + static InputConnectionCommand setSelection(int start, int end) { + return InputConnectionCommand.create( + InputConnectionCommandType.SET_SELECTION, + start, + end); + } + + @AnyThread + @NonNull + static InputConnectionCommand performEditorAction(int actionCode) { + return InputConnectionCommand.create( + InputConnectionCommandType.PERFORM_EDITOR_ACTION, + actionCode); + } + + @AnyThread + @NonNull + static InputConnectionCommand performContextMenuAction(int id) { + return InputConnectionCommand.create( + InputConnectionCommandType.PERFORM_CONTEXT_MENU_ACTION, + id); + } + + @AnyThread + @NonNull + static InputConnectionCommand setComposingRegion(int start, int end) { + return InputConnectionCommand.create( + InputConnectionCommandType.SET_COMPOSING_REGION, + start, + end); + } + + @AnyThread + @NonNull + static InputConnectionCommand setComposingText(@Nullable CharSequence text, + int newCursorPosition) { + return InputConnectionCommand.create( + InputConnectionCommandType.SET_COMPOSING_TEXT, + newCursorPosition, + 0, + 0, + text); + } + + @AnyThread + @NonNull + static InputConnectionCommand finishComposingText() { + return InputConnectionCommand.create( + InputConnectionCommandType.FINISH_COMPOSING_TEXT); + } + + @AnyThread + @NonNull + static InputConnectionCommand beginBatchEdit() { + return InputConnectionCommand.create( + InputConnectionCommandType.BEGIN_BATCH_EDIT); + } + + @AnyThread + @NonNull + static InputConnectionCommand endBatchEdit() { + return InputConnectionCommand.create( + InputConnectionCommandType.END_BATCH_EDIT); + } + + @AnyThread + @NonNull + static InputConnectionCommand sendKeyEvent(@Nullable KeyEvent event) { + return InputConnectionCommand.create( + InputConnectionCommandType.SEND_KEY_EVENT, + 0, + 0, + 0, + null, + null, + null, + InputConnectionCommand.ParcelableType.KEY_EVENT, + event); + } + + @AnyThread + @NonNull + static InputConnectionCommand clearMetaKeyStates(int states) { + return InputConnectionCommand.create( + InputConnectionCommandType.CLEAR_META_KEY_STATES, + states); + } + + @AnyThread + @NonNull + static InputConnectionCommand deleteSurroundingText(int beforeLength, int afterLength) { + return InputConnectionCommand.create( + InputConnectionCommandType.DELETE_SURROUNDING_TEXT, + beforeLength, + afterLength); + } + + @AnyThread + @NonNull + static InputConnectionCommand deleteSurroundingTextInCodePoints(int beforeLength, + int afterLength) { + return InputConnectionCommand.create( + InputConnectionCommandType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS, + beforeLength, + afterLength); + } + + @AnyThread + @NonNull + static InputConnectionCommand performSpellCheck() { + return InputConnectionCommand.create(InputConnectionCommandType.PERFORM_SPELL_CHECK); + } + + @AnyThread + @NonNull + static InputConnectionCommand performPrivateCommand(@Nullable String action, + @Nullable Bundle data) { + return InputConnectionCommand.create( + InputConnectionCommandType.PERFORM_PRIVATE_COMMAND, + 0, + 0, + 0, + null, + action, + data); + } + + @AnyThread + @NonNull + static InputConnectionCommand requestCursorUpdates(int cursorUpdateMode, + @NonNull Completable.Boolean returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.REQUEST_CURSOR_UPDATES, + cursorUpdateMode, + 0, + 0, + null, + null, + null, + InputConnectionCommand.ParcelableType.NULL, + null, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand commitContent(@Nullable InputContentInfo inputContentInfo, + int flags, @Nullable Bundle opts, @NonNull Completable.Boolean returnValue) { + return InputConnectionCommand.create( + InputConnectionCommandType.COMMIT_CONTENT, + 0, + 0, + flags, + null, + null, + opts, + InputConnectionCommand.ParcelableType.INPUT_CONTENT_INFO, + inputContentInfo, + returnValue); + } + + @AnyThread + @NonNull + static InputConnectionCommand setImeConsumesInput(boolean imeConsumesInput) { + return InputConnectionCommand.create( + InputConnectionCommandType.SET_IME_CONSUMES_INPUT, + imeConsumesInput ? 1 : 0); + } + } } diff --git a/core/java/com/android/internal/inputmethod/InputConnectionCommand.aidl b/core/java/com/android/internal/inputmethod/InputConnectionCommand.aidl new file mode 100644 index 0000000000000..c7c2ad87a58cb --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputConnectionCommand.aidl @@ -0,0 +1,19 @@ +/* + * 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 com.android.internal.inputmethod; + +parcelable InputConnectionCommand; diff --git a/core/java/com/android/internal/inputmethod/InputConnectionCommand.java b/core/java/com/android/internal/inputmethod/InputConnectionCommand.java new file mode 100644 index 0000000000000..4116b028fbbf9 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputConnectionCommand.java @@ -0,0 +1,460 @@ +/* + * 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 com.android.internal.inputmethod; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.AnyThread; +import android.annotation.IntDef; +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.os.Bundle; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; +import android.view.KeyEvent; +import android.view.inputmethod.CompletionInfo; +import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputContentInfo; + +import java.lang.annotation.Retention; + +/** + * Defines the command message to be used for IMEs to remotely invoke + * {@link android.view.inputmethod.InputConnection} APIs in the IME client process then receive + * results. + */ +public final class InputConnectionCommand implements Parcelable { + private static final String TAG = "InputConnectionCommand"; + + @Retention(SOURCE) + @IntDef(value = { + ResultCallbackType.NULL, + ResultCallbackType.BOOLEAN, + ResultCallbackType.INT, + ResultCallbackType.CHAR_SEQUENCE, + ResultCallbackType.EXTRACTED_TEXT, + ResultCallbackType.SURROUNDING_TEXT, + }) + @interface ResultCallbackType { + int NULL = 0; + int BOOLEAN = 1; + int INT = 2; + int CHAR_SEQUENCE = 3; + int EXTRACTED_TEXT = 4; + int SURROUNDING_TEXT = 5; + } + + @Retention(SOURCE) + @IntDef(value = { + ParcelableType.NULL, + ParcelableType.EXTRACTED_TEXT_REQUEST, + ParcelableType.COMPLETION_INFO, + ParcelableType.CORRECTION_INFO, + ParcelableType.KEY_EVENT, + ParcelableType.INPUT_CONTENT_INFO, + }) + @interface ParcelableType { + int NULL = 0; + int EXTRACTED_TEXT_REQUEST = 1; + int COMPLETION_INFO = 2; + int CORRECTION_INFO = 3; + int KEY_EVENT = 4; + int INPUT_CONTENT_INFO = 5; + } + + @Retention(SOURCE) + @IntDef(flag = true, value = { + FieldMask.INT_ARG0, + FieldMask.INT_ARG1, + FieldMask.FLAGS, + FieldMask.CHAR_SEQUENCE, + FieldMask.STRING, + FieldMask.BUNDLE, + FieldMask.PARCELABLE, + FieldMask.CALLBACK, + }) + @interface FieldMask { + int INT_ARG0 = 1 << 0; + int INT_ARG1 = 1 << 1; + int FLAGS = 1 << 2; + int CHAR_SEQUENCE = 1 << 3; + int STRING = 1 << 4; + int BUNDLE = 1 << 5; + int PARCELABLE = 1 << 6; + int CALLBACK = 1 << 7; + } + + @IntRange(from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType + public final int mCommandType; + public final int mIntArg0; + public final int mIntArg1; + public final int mFlags; + public final CharSequence mCharSequence; + public final String mString; + public final Bundle mBundle; + @ParcelableType + public final int mParcelableType; + public final Parcelable mParcelable; + @ResultCallbackType + public final int mResultCallbackType; + public final IBinder mResultCallback; + + private InputConnectionCommand( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, int intArg0, int intArg1, int flags, + @Nullable CharSequence charSequence, @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @ResultCallbackType int resultCallbackType, @Nullable IBinder resultCallback) { + if (type < InputConnectionCommandType.FIRST_COMMAND + || InputConnectionCommandType.LAST_COMMAND < type) { + throw new IllegalArgumentException("Unknown type=" + type); + } + mCommandType = type; + mIntArg0 = intArg0; + mIntArg1 = intArg1; + mFlags = flags; + mCharSequence = charSequence; + mString = string; + mBundle = bundle; + mParcelableType = parcelableType; + mParcelable = parcelable; + mResultCallbackType = resultCallbackType; + mResultCallback = resultCallback; + } + + /** + * Creates {@link InputConnectionCommand} with the given {@link InputConnectionCommandType}. + * + * @param type {@link InputConnectionCommandType} to be set. + * @return An {@link InputConnectionCommand} that is initialized with {@code type}. + */ + @NonNull + public static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type) { + return create(type, 0); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, int intArg0) { + return create(type, intArg0, 0); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, int intArg0, int intArg1) { + return create(type, intArg0, intArg1, 0, null); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, int intArg0, + int intArg1, int flags, @Nullable CharSequence charSequence) { + return create(type, intArg0, intArg1, flags, charSequence, null, null); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle) { + return create(type, intArg0, intArg1, flags, charSequence, string, + bundle, ParcelableType.NULL, null); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, ResultCallbackType.NULL, null); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @NonNull Completable.Boolean returnValue) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, + ResultCallbackType.BOOLEAN, ResultCallbacks.of(returnValue)); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @NonNull Completable.Int returnValue) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, + ResultCallbackType.INT, ResultCallbacks.of(returnValue)); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @NonNull Completable.CharSequence returnValue) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, + ResultCallbackType.CHAR_SEQUENCE, ResultCallbacks.of(returnValue)); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @NonNull Completable.ExtractedText returnValue) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, + ResultCallbackType.EXTRACTED_TEXT, ResultCallbacks.of(returnValue)); + } + + @NonNull + static InputConnectionCommand create( + @IntRange( + from = InputConnectionCommandType.FIRST_COMMAND, + to = InputConnectionCommandType.LAST_COMMAND) + @InputConnectionCommandType int type, + int intArg0, int intArg1, int flags, @Nullable CharSequence charSequence, + @Nullable String string, @Nullable Bundle bundle, + @ParcelableType int parcelableType, @Nullable Parcelable parcelable, + @NonNull Completable.SurroundingText returnValue) { + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, + ResultCallbackType.SURROUNDING_TEXT, ResultCallbacks.of(returnValue)); + } + + /** + * {@inheritDoc} + */ + @AnyThread + @Override + public int describeContents() { + int result = 0; + if (mBundle != null) { + result |= mBundle.describeContents(); + } + if (mParcelable != null) { + result |= mParcelable.describeContents(); + } + // Here we assume other objects will never contain FDs to be parcelled. + return result; + } + + /** + * {@inheritDoc} + */ + @AnyThread + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(mCommandType); + + @FieldMask final int fieldMask = getFieldMask(); + dest.writeInt(fieldMask); + if ((fieldMask & FieldMask.INT_ARG0) != 0) { + dest.writeInt(mIntArg0); + } + if ((fieldMask & FieldMask.INT_ARG1) != 0) { + dest.writeInt(mIntArg1); + } + if ((fieldMask & FieldMask.FLAGS) != 0) { + dest.writeInt(mFlags); + } + if ((fieldMask & FieldMask.CHAR_SEQUENCE) != 0) { + TextUtils.writeToParcel(mCharSequence, dest, flags); + } + if ((fieldMask & FieldMask.STRING) != 0) { + dest.writeString(mString); + } + if ((fieldMask & FieldMask.BUNDLE) != 0) { + dest.writeBundle(mBundle); + } + if ((fieldMask & FieldMask.PARCELABLE) != 0) { + dest.writeInt(mParcelableType); + dest.writeTypedObject(mParcelable, flags); + } + if ((fieldMask & FieldMask.CALLBACK) != 0) { + dest.writeInt(mResultCallbackType); + dest.writeStrongBinder(mResultCallback); + } + } + + @FieldMask + @AnyThread + private int getFieldMask() { + return (mIntArg0 != 0 ? FieldMask.INT_ARG0 : 0) + | (mIntArg1 != 0 ? FieldMask.INT_ARG1 : 0) + | (mFlags != 0 ? FieldMask.FLAGS : 0) + | (mCharSequence != null ? FieldMask.CHAR_SEQUENCE : 0) + | (mString != null ? FieldMask.STRING : 0) + | (mBundle != null ? FieldMask.BUNDLE : 0) + | (mParcelableType != ParcelableType.NULL ? FieldMask.PARCELABLE : 0) + | (mResultCallbackType != ResultCallbackType.NULL ? FieldMask.CALLBACK : 0); + } + + @AnyThread + @Nullable + private static InputConnectionCommand createFromParcel(@NonNull Parcel source) { + final int type = source.readInt(); + if (type < InputConnectionCommandType.FIRST_COMMAND + || InputConnectionCommandType.LAST_COMMAND < type) { + Log.e(TAG, "Invalid InputConnectionCommand type=" + type); + return null; + } + + @FieldMask final int fieldMask = source.readInt(); + final int intArg0 = (fieldMask & FieldMask.INT_ARG0) != 0 ? source.readInt() : 0; + final int intArg1 = (fieldMask & FieldMask.INT_ARG1) != 0 ? source.readInt() : 0; + final int flags = (fieldMask & FieldMask.FLAGS) != 0 ? source.readInt() : 0; + final CharSequence charSequence = (fieldMask & FieldMask.CHAR_SEQUENCE) != 0 + ? TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source) : null; + final String string = (fieldMask & FieldMask.STRING) != 0 ? source.readString() : null; + final Bundle bundle = (fieldMask & FieldMask.BUNDLE) != 0 ? source.readBundle() : null; + + @ParcelableType final int parcelableType; + final Parcelable parcelable; + if ((fieldMask & FieldMask.PARCELABLE) != 0) { + parcelableType = source.readInt(); + switch (parcelableType) { + case ParcelableType.NULL: + Log.e(TAG, "Unexpected ParcelableType=NULL"); + return null; + case ParcelableType.EXTRACTED_TEXT_REQUEST: + parcelable = source.readTypedObject(ExtractedTextRequest.CREATOR); + break; + case ParcelableType.COMPLETION_INFO: + parcelable = source.readTypedObject(CompletionInfo.CREATOR); + break; + case ParcelableType.CORRECTION_INFO: + parcelable = source.readTypedObject(CorrectionInfo.CREATOR); + break; + case ParcelableType.KEY_EVENT: + parcelable = source.readTypedObject(KeyEvent.CREATOR); + break; + case ParcelableType.INPUT_CONTENT_INFO: + parcelable = source.readTypedObject(InputContentInfo.CREATOR); + break; + default: + Log.e(TAG, "Unknown ParcelableType=" + parcelableType); + return null; + } + } else { + parcelableType = ParcelableType.NULL; + parcelable = null; + } + @ResultCallbackType final int resultCallbackType; + final IBinder resultCallback; + if ((fieldMask & FieldMask.CALLBACK) != 0) { + resultCallbackType = source.readInt(); + switch (resultCallbackType) { + case ResultCallbackType.NULL: + Log.e(TAG, "Unexpected ResultCallbackType=NULL"); + return null; + case ResultCallbackType.BOOLEAN: + case ResultCallbackType.INT: + case ResultCallbackType.CHAR_SEQUENCE: + case ResultCallbackType.EXTRACTED_TEXT: + case ResultCallbackType.SURROUNDING_TEXT: + resultCallback = source.readStrongBinder(); + break; + default: + Log.e(TAG, "Unknown ResultCallbackType=" + resultCallbackType); + return null; + } + } else { + resultCallbackType = ResultCallbackType.NULL; + resultCallback = null; + } + return new InputConnectionCommand(type, intArg0, intArg1, flags, charSequence, string, + bundle, parcelableType, parcelable, resultCallbackType, resultCallback); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator CREATOR = + new Parcelable.Creator() { + @AnyThread + @Nullable + @Override + public InputConnectionCommand createFromParcel(Parcel source) { + try { + return InputConnectionCommand.createFromParcel(source); + } catch (Exception e) { + Log.e(TAG, "Returning null due to exception.", e); + return null; + } + } + + @AnyThread + @NonNull + @Override + public InputConnectionCommand[] newArray(int size) { + return new InputConnectionCommand[size]; + } + }; +} diff --git a/core/java/com/android/internal/inputmethod/InputConnectionCommandType.java b/core/java/com/android/internal/inputmethod/InputConnectionCommandType.java new file mode 100644 index 0000000000000..1eb0101115e85 --- /dev/null +++ b/core/java/com/android/internal/inputmethod/InputConnectionCommandType.java @@ -0,0 +1,84 @@ +/* + * 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 com.android.internal.inputmethod; + +import static java.lang.annotation.RetentionPolicy.SOURCE; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; + +@Retention(SOURCE) +@IntDef(value = { + InputConnectionCommandType.BEGIN_BATCH_EDIT, + InputConnectionCommandType.CLEAR_META_KEY_STATES, + InputConnectionCommandType.COMMIT_COMPLETION, + InputConnectionCommandType.COMMIT_CONTENT, + InputConnectionCommandType.COMMIT_CORRECTION, + InputConnectionCommandType.COMMIT_TEXT, + InputConnectionCommandType.DELETE_SURROUNDING_TEXT, + InputConnectionCommandType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS, + InputConnectionCommandType.END_BATCH_EDIT, + InputConnectionCommandType.FINISH_COMPOSING_TEXT, + InputConnectionCommandType.GET_CURSOR_CAPS_MODE, + InputConnectionCommandType.GET_EXTRACTED_TEXT, + InputConnectionCommandType.GET_SELECTED_TEXT, + InputConnectionCommandType.GET_SURROUNDING_TEXT, + InputConnectionCommandType.GET_TEXT_AFTER_CURSOR, + InputConnectionCommandType.GET_TEXT_BEFORE_CURSOR, + InputConnectionCommandType.PERFORM_CONTEXT_MENU_ACTION, + InputConnectionCommandType.PERFORM_EDITOR_ACTION, + InputConnectionCommandType.PERFORM_SPELL_CHECK, + InputConnectionCommandType.REQUEST_CURSOR_UPDATES, + InputConnectionCommandType.SEND_KEY_EVENT, + InputConnectionCommandType.SET_COMPOSING_REGION, + InputConnectionCommandType.SET_COMPOSING_TEXT, + InputConnectionCommandType.SET_IME_CONSUMES_INPUT, + InputConnectionCommandType.SET_SELECTION, +}) +public @interface InputConnectionCommandType { + int FIRST_COMMAND = 1; + + int BEGIN_BATCH_EDIT = FIRST_COMMAND; + int CLEAR_META_KEY_STATES = 2; + int COMMIT_COMPLETION = 3; + int COMMIT_CONTENT = 4; + int COMMIT_CORRECTION = 5; + int COMMIT_TEXT = 6; + int DELETE_SURROUNDING_TEXT = 7; + int DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 8; + int END_BATCH_EDIT = 9; + int FINISH_COMPOSING_TEXT = 10; + int GET_CURSOR_CAPS_MODE = 11; + int GET_EXTRACTED_TEXT = 12; + int GET_SELECTED_TEXT = 13; + int GET_SURROUNDING_TEXT = 14; + int GET_TEXT_AFTER_CURSOR = 15; + int GET_TEXT_BEFORE_CURSOR = 16; + int PERFORM_CONTEXT_MENU_ACTION = 17; + int PERFORM_EDITOR_ACTION = 18; + int PERFORM_SPELL_CHECK = 19; + int PERFORM_PRIVATE_COMMAND = 20; + int REQUEST_CURSOR_UPDATES = 21; + int SEND_KEY_EVENT = 22; + int SET_COMPOSING_REGION = 23; + int SET_COMPOSING_TEXT = 24; + int SET_IME_CONSUMES_INPUT = 25; + int SET_SELECTION = 26; + + int LAST_COMMAND = SET_SELECTION; +} diff --git a/core/java/com/android/internal/inputmethod/InputMethodDebug.java b/core/java/com/android/internal/inputmethod/InputMethodDebug.java index a00b993749a52..0475ed09bdd2d 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodDebug.java +++ b/core/java/com/android/internal/inputmethod/InputMethodDebug.java @@ -18,6 +18,7 @@ package com.android.internal.inputmethod; import android.annotation.AnyThread; import android.annotation.NonNull; +import android.annotation.Nullable; import android.view.WindowManager; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -241,6 +242,133 @@ public final class InputMethodDebug { } } + /** + * Converts {@link InputConnectionCommandType} to human readable {@link String}. + */ + public static String inputConnectionCommandTypeToString(@InputConnectionCommandType int type) { + switch (type) { + case InputConnectionCommandType.BEGIN_BATCH_EDIT: + return "beginBatchEdit"; + case InputConnectionCommandType.CLEAR_META_KEY_STATES: + return "clearMetaKeyStates"; + case InputConnectionCommandType.COMMIT_COMPLETION: + return "commitCompletion"; + case InputConnectionCommandType.COMMIT_CONTENT: + return "commitContent"; + case InputConnectionCommandType.COMMIT_CORRECTION: + return "commitCorrection"; + case InputConnectionCommandType.COMMIT_TEXT: + return "commitText"; + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT: + return "deleteSurroundingText"; + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: + return "deleteSurroundingTextInCodePoints"; + case InputConnectionCommandType.END_BATCH_EDIT: + return "endBatchEdit"; + case InputConnectionCommandType.FINISH_COMPOSING_TEXT: + return "finishComposingText"; + case InputConnectionCommandType.GET_CURSOR_CAPS_MODE: + return "getCursorCapsMode"; + case InputConnectionCommandType.GET_EXTRACTED_TEXT: + return "getExtractedText"; + case InputConnectionCommandType.GET_SELECTED_TEXT: + return "getSelectedText"; + case InputConnectionCommandType.GET_SURROUNDING_TEXT: + return "getSurroundingText"; + case InputConnectionCommandType.GET_TEXT_AFTER_CURSOR: + return "getTextAfterCursor"; + case InputConnectionCommandType.GET_TEXT_BEFORE_CURSOR: + return "getTextBeforeCursor"; + case InputConnectionCommandType.PERFORM_CONTEXT_MENU_ACTION: + return "performContextMenuAction"; + case InputConnectionCommandType.PERFORM_EDITOR_ACTION: + return "performEditorAction"; + case InputConnectionCommandType.PERFORM_SPELL_CHECK: + return "performSpellCheck"; + case InputConnectionCommandType.REQUEST_CURSOR_UPDATES: + return "requestCursorUpdates"; + case InputConnectionCommandType.SEND_KEY_EVENT: + return "sendKeyEvent"; + case InputConnectionCommandType.SET_COMPOSING_REGION: + return "setComposingRegion"; + case InputConnectionCommandType.SET_COMPOSING_TEXT: + return "setComposingText"; + case InputConnectionCommandType.SET_IME_CONSUMES_INPUT: + return "setImeConsumesInput"; + case InputConnectionCommandType.SET_SELECTION: + return "setSelection"; + default: + return "Unknown=" + type; + } + } + + /** + * Converts {@link InputConnectionCommand} to human readable {@link String}. + */ + @NonNull + public static String dumpInputConnectionCommand(@Nullable InputConnectionCommand command) { + if (command == null) { + return "null"; + } + switch (command.mCommandType) { + case InputConnectionCommandType.BEGIN_BATCH_EDIT: + return "beginBatchEdit()"; + case InputConnectionCommandType.CLEAR_META_KEY_STATES: + return "clearMetaKeyStates(" + command.mIntArg0 + ")"; + case InputConnectionCommandType.COMMIT_COMPLETION: + return "commitCompletion(" + command.mParcelable + ")"; + case InputConnectionCommandType.COMMIT_CONTENT: + return "commitContent(" + command.mParcelable + ", " + command.mFlags + ", " + + command.mBundle + ")"; + case InputConnectionCommandType.COMMIT_CORRECTION: + return "commitCorrection(" + command.mParcelable + ")"; + case InputConnectionCommandType.COMMIT_TEXT: + return "commitText(" + command.mCharSequence + ", " + command.mIntArg0 + ")"; + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT: + return "deleteSurroundingText(" + command.mIntArg0 + ", " + command.mIntArg1 + ")"; + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: + return "deleteSurroundingTextInCodePoints(" + command.mIntArg0 + ", " + + command.mIntArg1 + ")"; + case InputConnectionCommandType.END_BATCH_EDIT: + return "endBatchEdit()"; + case InputConnectionCommandType.FINISH_COMPOSING_TEXT: + return "finishComposingText()"; + case InputConnectionCommandType.GET_CURSOR_CAPS_MODE: + return "getCursorCapsMode(" + command.mIntArg0 + ")"; + case InputConnectionCommandType.GET_EXTRACTED_TEXT: + return "getExtractedText(" + command.mParcelable + ", " + command.mFlags + ")"; + case InputConnectionCommandType.GET_SELECTED_TEXT: + return "getSelectedText(" + command.mFlags + ")"; + case InputConnectionCommandType.GET_SURROUNDING_TEXT: + return "getSurroundingText(" + command.mIntArg0 + ", " + command.mIntArg1 + ", " + + command.mFlags + ")"; + case InputConnectionCommandType.GET_TEXT_AFTER_CURSOR: + return "getTextAfterCursor(" + command.mIntArg0 + ", " + command.mFlags + ")"; + case InputConnectionCommandType.GET_TEXT_BEFORE_CURSOR: + return "getTextBeforeCursor(" + command.mIntArg0 + ", " + command.mFlags + ")"; + case InputConnectionCommandType.PERFORM_CONTEXT_MENU_ACTION: + return "performContextMenuAction(" + command.mIntArg0 + ")"; + case InputConnectionCommandType.PERFORM_EDITOR_ACTION: + return "performEditorAction(" + command.mIntArg0 + ")"; + case InputConnectionCommandType.PERFORM_SPELL_CHECK: + return "performSpellCheck()"; + case InputConnectionCommandType.REQUEST_CURSOR_UPDATES: + return "requestCursorUpdates(" + command.mIntArg0 + ")"; + case InputConnectionCommandType.SEND_KEY_EVENT: + return "sendKeyEvent(" + command.mParcelable + ")"; + case InputConnectionCommandType.SET_COMPOSING_REGION: + return "setComposingRegion(" + command.mIntArg0 + ", " + command.mIntArg1 + ")"; + case InputConnectionCommandType.SET_COMPOSING_TEXT: + return "setComposingText(" + command.mCharSequence + ", " + command.mIntArg0 + ")"; + case InputConnectionCommandType.SET_IME_CONSUMES_INPUT: + return "setImeConsumesInput(" + (command.mIntArg0 != 0) + ")"; + case InputConnectionCommandType.SET_SELECTION: + return "setSelection(" + command.mIntArg0 + ", " + command.mIntArg1 + ")"; + default: + return "unknown(type=" + command.mCommandType + ")"; + } + } + /** * Return a fixed size string of the object. * TODO(b/151575861): Take & return with StringBuilder to make more memory efficient. diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 46071296d0bf4..fdbbf974ffa1c 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -16,13 +16,14 @@ package com.android.internal.view; +import android.annotation.AnyThread; +import android.annotation.BinderThread; import android.annotation.NonNull; import android.annotation.Nullable; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; -import android.os.RemoteException; import android.os.Trace; import android.util.Log; import android.util.proto.ProtoOutputStream; @@ -41,14 +42,12 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.SurroundingText; import com.android.internal.annotations.GuardedBy; -import com.android.internal.inputmethod.IBooleanResultCallback; -import com.android.internal.inputmethod.ICharSequenceResultCallback; -import com.android.internal.inputmethod.IExtractedTextResultCallback; -import com.android.internal.inputmethod.IIntResultCallback; -import com.android.internal.inputmethod.ISurroundingTextResultCallback; +import com.android.internal.inputmethod.CallbackUtils; import com.android.internal.inputmethod.ImeTracing; +import com.android.internal.inputmethod.InputConnectionCommand; +import com.android.internal.inputmethod.InputConnectionCommandType; import com.android.internal.inputmethod.InputConnectionProtoDumper; -import com.android.internal.os.SomeArgs; +import com.android.internal.inputmethod.InputMethodDebug; import java.lang.ref.WeakReference; @@ -56,34 +55,8 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { private static final String TAG = "IInputConnectionWrapper"; private static final boolean DEBUG = false; - private static final int DO_GET_TEXT_AFTER_CURSOR = 10; - private static final int DO_GET_TEXT_BEFORE_CURSOR = 20; - private static final int DO_GET_SELECTED_TEXT = 25; - private static final int DO_GET_CURSOR_CAPS_MODE = 30; - private static final int DO_GET_EXTRACTED_TEXT = 40; - private static final int DO_COMMIT_TEXT = 50; - private static final int DO_COMMIT_COMPLETION = 55; - private static final int DO_COMMIT_CORRECTION = 56; - private static final int DO_SET_SELECTION = 57; - private static final int DO_PERFORM_EDITOR_ACTION = 58; - private static final int DO_PERFORM_CONTEXT_MENU_ACTION = 59; - private static final int DO_SET_COMPOSING_TEXT = 60; - private static final int DO_SET_COMPOSING_REGION = 63; - private static final int DO_FINISH_COMPOSING_TEXT = 65; - private static final int DO_SEND_KEY_EVENT = 70; - private static final int DO_DELETE_SURROUNDING_TEXT = 80; - private static final int DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS = 81; - private static final int DO_BEGIN_BATCH_EDIT = 90; - private static final int DO_END_BATCH_EDIT = 95; - private static final int DO_PERFORM_SPELL_CHECK = 110; - private static final int DO_PERFORM_PRIVATE_COMMAND = 120; - private static final int DO_CLEAR_META_KEY_STATES = 130; - private static final int DO_REQUEST_CURSOR_UPDATES = 140; - private static final int DO_CLOSE_CONNECTION = 150; - private static final int DO_COMMIT_CONTENT = 160; - private static final int DO_GET_SURROUNDING_TEXT = 41; - private static final int DO_SET_IME_CONSUMES_INPUT = 170; - + private static final int DO_EDIT = 10; + private static final int DO_CLOSE_CONNECTION = 20; @GuardedBy("mLock") @Nullable @@ -147,7 +120,7 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { // reportFinish() will take effect. return; } - closeConnection(); + dispatchMessage(mH.obtainMessage(DO_CLOSE_CONNECTION)); // Notify the app that the InputConnection was closed. final View servedView = mServedView.get(); @@ -192,146 +165,33 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { } } - public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) { - dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback)); - } - - public void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback) { - dispatchMessage(mH.obtainMessage(DO_GET_TEXT_BEFORE_CURSOR, length, flags, callback)); - } - - public void getSelectedText(int flags, ICharSequenceResultCallback callback) { - dispatchMessage(mH.obtainMessage(DO_GET_SELECTED_TEXT, flags, 0 /* unused */, callback)); + @BinderThread + @Override + public void doEdit(@Nullable InputConnectionCommand command) { + if (command == null) { + // As long as everything is working as expected, we should never see any null object + // here. If we are seeing null object, it means that either the sender or + // InputConnectionCommand.CREATOR#createFromParcel() returned null for whatever + // unexpected reasons. Note that InputConnectionCommand.CREATOR#createFromParcel() does + // some data verifications. Hence failing to pass the verification is one of the + // reasons to see null here. + Log.w(TAG, "Ignoring invalid InputConnectionCommand."); + return; + } + if (DEBUG) { + Log.d(TAG, "incoming: " + InputMethodDebug.dumpInputConnectionCommand(command)); + } + dispatchMessage(mH.obtainMessage(DO_EDIT, command)); } /** - * Dispatches the request for retrieving surrounding text. - * - *

See {@link InputConnection#getSurroundingText(int, int, int)}. + * Exposed for {@link InputMethodManager} to trigger + * {@link InputConnection#finishComposingText()}. */ - public void getSurroundingText(int beforeLength, int afterLength, int flags, - ISurroundingTextResultCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = beforeLength; - args.arg2 = afterLength; - args.arg3 = flags; - args.arg4 = callback; - dispatchMessage(mH.obtainMessage(DO_GET_SURROUNDING_TEXT, flags, 0 /* unused */, args)); - } - - public void getCursorCapsMode(int reqModes, IIntResultCallback callback) { - dispatchMessage( - mH.obtainMessage(DO_GET_CURSOR_CAPS_MODE, reqModes, 0 /* unused */, callback)); - } - - public void getExtractedText(ExtractedTextRequest request, int flags, - IExtractedTextResultCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = request; - args.arg2 = callback; - dispatchMessage(mH.obtainMessage(DO_GET_EXTRACTED_TEXT, flags, 0 /* unused */, args)); - } - - public void commitText(CharSequence text, int newCursorPosition) { - dispatchMessage(obtainMessageIO(DO_COMMIT_TEXT, newCursorPosition, text)); - } - - public void commitCompletion(CompletionInfo text) { - dispatchMessage(obtainMessageO(DO_COMMIT_COMPLETION, text)); - } - - public void commitCorrection(CorrectionInfo info) { - dispatchMessage(obtainMessageO(DO_COMMIT_CORRECTION, info)); - } - - public void setSelection(int start, int end) { - dispatchMessage(obtainMessageII(DO_SET_SELECTION, start, end)); - } - - public void performEditorAction(int id) { - dispatchMessage(obtainMessageII(DO_PERFORM_EDITOR_ACTION, id, 0)); - } - - public void performContextMenuAction(int id) { - dispatchMessage(obtainMessageII(DO_PERFORM_CONTEXT_MENU_ACTION, id, 0)); - } - - public void setComposingRegion(int start, int end) { - dispatchMessage(obtainMessageII(DO_SET_COMPOSING_REGION, start, end)); - } - - public void setComposingText(CharSequence text, int newCursorPosition) { - dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text)); - } - + @AnyThread public void finishComposingText() { - dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT)); - } - - public void sendKeyEvent(KeyEvent event) { - dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event)); - } - - public void clearMetaKeyStates(int states) { - dispatchMessage(obtainMessageII(DO_CLEAR_META_KEY_STATES, states, 0)); - } - - public void deleteSurroundingText(int beforeLength, int afterLength) { - dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT, - beforeLength, afterLength)); - } - - public void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength) { - dispatchMessage(obtainMessageII(DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS, - beforeLength, afterLength)); - } - - public void beginBatchEdit() { - dispatchMessage(obtainMessage(DO_BEGIN_BATCH_EDIT)); - } - - public void endBatchEdit() { - dispatchMessage(obtainMessage(DO_END_BATCH_EDIT)); - } - - /** - * Dispatches the request for performing spell check. - * - * @see InputConnection#performSpellCheck() - */ - public void performSpellCheck() { - dispatchMessage(obtainMessage(DO_PERFORM_SPELL_CHECK)); - } - - public void performPrivateCommand(String action, Bundle data) { - dispatchMessage(obtainMessageOO(DO_PERFORM_PRIVATE_COMMAND, action, data)); - } - - public void requestCursorUpdates(int cursorUpdateMode, IBooleanResultCallback callback) { - dispatchMessage(mH.obtainMessage(DO_REQUEST_CURSOR_UPDATES, cursorUpdateMode, - 0 /* unused */, callback)); - } - - public void closeConnection() { - dispatchMessage(obtainMessage(DO_CLOSE_CONNECTION)); - } - - public void commitContent(InputContentInfo inputContentInfo, int flags, Bundle opts, - IBooleanResultCallback callback) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = inputContentInfo; - args.arg2 = opts; - args.arg3 = callback; - dispatchMessage(mH.obtainMessage(DO_COMMIT_CONTENT, flags, 0 /* unused */, args)); - } - - /** - * Dispatches the request for setting ime consumes input. - * - *

See {@link InputConnection#setImeConsumesInput(boolean)}. - */ - public void setImeConsumesInput(boolean imeConsumesInput) { - dispatchMessage(obtainMessageB(DO_SET_IME_CONSUMES_INPUT, imeConsumesInput)); + dispatchMessage(mH.obtainMessage(DO_EDIT, InputConnectionCommand.create( + InputConnectionCommandType.FINISH_COMPOSING_TEXT))); } void dispatchMessage(Message msg) { @@ -347,108 +207,131 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { mH.sendMessage(msg); } - void executeMessage(Message msg) { - byte[] icProto; + private void executeMessage(Message msg) { switch (msg.what) { - case DO_GET_TEXT_AFTER_CURSOR: { + case DO_EDIT: + doEditMain((InputConnectionCommand) msg.obj); + break; + case DO_CLOSE_CONNECTION: + // Note that we do not need to worry about race condition here, because 1) mFinished + // is updated only inside this block, and 2) the code here is running on a Handler + // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the + // same time. + if (isFinished()) { + return; + } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection"); + try { + 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 + // through to clean up our state after the IME has switched to + // another client. + if (ic == null) { + return; + } + @MissingMethodFlags final int missingMethods = + InputConnectionInspector.getMissingMethodFlags(ic); + if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) { + ic.closeConnection(); + } + } finally { + synchronized (mLock) { + mInputConnection = null; + mFinished = true; + } + Trace.traceEnd(Trace.TRACE_TAG_INPUT); + } + break; + } + } + + private void doEditMain(@NonNull InputConnectionCommand command) { + if (DEBUG) { + Log.d(TAG, "handling: " + InputMethodDebug.dumpInputConnectionCommand(command)); + } + byte[] icProto; + switch (command.mCommandType) { + case InputConnectionCommandType.GET_TEXT_AFTER_CURSOR: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getTextAfterCursor"); try { - final ICharSequenceResultCallback callback = - (ICharSequenceResultCallback) msg.obj; + final int n = command.mIntArg0; + final int flags = command.mFlags; final InputConnection ic = getInputConnection(); final CharSequence result; if (ic == null || !isActive()) { Log.w(TAG, "getTextAfterCursor on inactive InputConnection"); result = null; } else { - result = ic.getTextAfterCursor(msg.arg1, msg.arg2); + result = ic.getTextAfterCursor(n, flags); } if (ImeTracing.getInstance().isEnabled()) { - icProto = InputConnectionProtoDumper.buildGetTextAfterCursorProto(msg.arg1, - msg.arg2, result); + icProto = InputConnectionProtoDumper.buildGetTextAfterCursorProto(n, flags, + result); ImeTracing.getInstance().triggerClientDump( TAG + "#getTextAfterCursor", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getTextAfterCursor()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_GET_TEXT_BEFORE_CURSOR: { + case InputConnectionCommandType.GET_TEXT_BEFORE_CURSOR: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getTextBeforeCursor"); try { - final ICharSequenceResultCallback callback = - (ICharSequenceResultCallback) msg.obj; + final int n = command.mIntArg0; + final int flags = command.mFlags; final InputConnection ic = getInputConnection(); final CharSequence result; if (ic == null || !isActive()) { Log.w(TAG, "getTextBeforeCursor on inactive InputConnection"); result = null; } else { - result = ic.getTextBeforeCursor(msg.arg1, msg.arg2); + result = ic.getTextBeforeCursor(n, flags); } if (ImeTracing.getInstance().isEnabled()) { - icProto = InputConnectionProtoDumper.buildGetTextBeforeCursorProto(msg.arg1, - msg.arg2, result); + icProto = InputConnectionProtoDumper.buildGetTextBeforeCursorProto(n, flags, + result); ImeTracing.getInstance().triggerClientDump( TAG + "#getTextBeforeCursor", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getTextBeforeCursor()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_GET_SELECTED_TEXT: { + case InputConnectionCommandType.GET_SELECTED_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getSelectedText"); try { - final ICharSequenceResultCallback callback = - (ICharSequenceResultCallback) msg.obj; + final int flags = command.mFlags; final InputConnection ic = getInputConnection(); final CharSequence result; if (ic == null || !isActive()) { Log.w(TAG, "getSelectedText on inactive InputConnection"); result = null; } else { - result = ic.getSelectedText(msg.arg1); + result = ic.getSelectedText(flags); } if (ImeTracing.getInstance().isEnabled()) { - icProto = InputConnectionProtoDumper.buildGetSelectedTextProto(msg.arg1, + icProto = InputConnectionProtoDumper.buildGetSelectedTextProto(flags, result); ImeTracing.getInstance().triggerClientDump( TAG + "#getSelectedText", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getSelectedText()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_GET_SURROUNDING_TEXT: { - final SomeArgs args = (SomeArgs) msg.obj; + case InputConnectionCommandType.GET_SURROUNDING_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getSurroundingText"); try { - int beforeLength = (int) args.arg1; - int afterLength = (int) args.arg2; - int flags = (int) args.arg3; - final ISurroundingTextResultCallback callback = - (ISurroundingTextResultCallback) args.arg4; + final int beforeLength = command.mIntArg0; + final int afterLength = command.mIntArg1; + final int flags = command.mFlags; final InputConnection ic = getInputConnection(); final SurroundingText result; if (ic == null || !isActive()) { @@ -463,193 +346,186 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { ImeTracing.getInstance().triggerClientDump( TAG + "#getSurroundingText", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getSurroundingText()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); - args.recycle(); } return; } - case DO_GET_CURSOR_CAPS_MODE: { + case InputConnectionCommandType.GET_CURSOR_CAPS_MODE: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getCursorCapsMode"); try { - final IIntResultCallback callback = (IIntResultCallback) msg.obj; + final int reqModes = command.mIntArg0; final InputConnection ic = getInputConnection(); final int result; if (ic == null || !isActive()) { Log.w(TAG, "getCursorCapsMode on inactive InputConnection"); result = 0; } else { - result = ic.getCursorCapsMode(msg.arg1); + result = ic.getCursorCapsMode(reqModes); } if (ImeTracing.getInstance().isEnabled()) { - icProto = InputConnectionProtoDumper.buildGetCursorCapsModeProto(msg.arg1, + icProto = InputConnectionProtoDumper.buildGetCursorCapsModeProto(reqModes, result); ImeTracing.getInstance().triggerClientDump( TAG + "#getCursorCapsMode", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getCursorCapsMode()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_GET_EXTRACTED_TEXT: { - final SomeArgs args = (SomeArgs) msg.obj; + case InputConnectionCommandType.GET_EXTRACTED_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getExtractedText"); try { - final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1; - final IExtractedTextResultCallback callback = - (IExtractedTextResultCallback) args.arg2; + final ExtractedTextRequest request = (ExtractedTextRequest) command.mParcelable; + final int flags = command.mFlags; final InputConnection ic = getInputConnection(); final ExtractedText result; if (ic == null || !isActive()) { Log.w(TAG, "getExtractedText on inactive InputConnection"); result = null; } else { - result = ic.getExtractedText(request, msg.arg1); + result = ic.getExtractedText(request, flags); } if (ImeTracing.getInstance().isEnabled()) { icProto = InputConnectionProtoDumper.buildGetExtractedTextProto(request, - msg.arg1, result); + flags, result); ImeTracing.getInstance().triggerClientDump( TAG + "#getExtractedText", mParentInputMethodManager, icProto); } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getExtractedText()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); - args.recycle(); } return; } - case DO_COMMIT_TEXT: { + case InputConnectionCommandType.COMMIT_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitText"); try { + final CharSequence text = command.mCharSequence; + final int newCursorPosition = command.mIntArg0; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitText on inactive InputConnection"); return; } - ic.commitText((CharSequence) msg.obj, msg.arg1); + ic.commitText(text, newCursorPosition); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_SET_SELECTION: { + case InputConnectionCommandType.SET_SELECTION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setSelection"); try { + final int start = command.mIntArg0; + final int end = command.mIntArg1; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setSelection on inactive InputConnection"); return; } - ic.setSelection(msg.arg1, msg.arg2); + ic.setSelection(start, end); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_PERFORM_EDITOR_ACTION: { + case InputConnectionCommandType.PERFORM_EDITOR_ACTION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performEditorAction"); try { + final int editorAction = command.mIntArg0; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performEditorAction on inactive InputConnection"); return; } - ic.performEditorAction(msg.arg1); + ic.performEditorAction(editorAction); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_PERFORM_CONTEXT_MENU_ACTION: { + case InputConnectionCommandType.PERFORM_CONTEXT_MENU_ACTION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performContextMenuAction"); try { + final int id = command.mIntArg0; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performContextMenuAction on inactive InputConnection"); return; } - ic.performContextMenuAction(msg.arg1); + ic.performContextMenuAction(id); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_COMMIT_COMPLETION: { + case InputConnectionCommandType.COMMIT_COMPLETION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitCompletion"); try { + final CompletionInfo text = (CompletionInfo) command.mParcelable; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitCompletion on inactive InputConnection"); return; } - ic.commitCompletion((CompletionInfo) msg.obj); + ic.commitCompletion(text); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_COMMIT_CORRECTION: { + case InputConnectionCommandType.COMMIT_CORRECTION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitCorrection"); try { + final CorrectionInfo correctionInfo = (CorrectionInfo) command.mParcelable; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "commitCorrection on inactive InputConnection"); return; } - ic.commitCorrection((CorrectionInfo) msg.obj); + ic.commitCorrection(correctionInfo); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_SET_COMPOSING_TEXT: { + case InputConnectionCommandType.SET_COMPOSING_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setComposingText"); try { + final CharSequence text = command.mCharSequence; + final int newCursorPosition = command.mIntArg0; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setComposingText on inactive InputConnection"); return; } - ic.setComposingText((CharSequence) msg.obj, msg.arg1); + ic.setComposingText(text, newCursorPosition); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_SET_COMPOSING_REGION: { + case InputConnectionCommandType.SET_COMPOSING_REGION: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setComposingRegion"); try { + final int start = command.mIntArg0; + final int end = command.mIntArg1; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setComposingRegion on inactive InputConnection"); return; } - ic.setComposingRegion(msg.arg1, msg.arg2); + ic.setComposingRegion(start, end); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_FINISH_COMPOSING_TEXT: { + case InputConnectionCommandType.FINISH_COMPOSING_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#finishComposingText"); try { if (isFinished()) { @@ -675,64 +551,70 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { } return; } - case DO_SEND_KEY_EVENT: { + case InputConnectionCommandType.SEND_KEY_EVENT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#sendKeyEvent"); try { + final KeyEvent event = (KeyEvent) command.mParcelable; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "sendKeyEvent on inactive InputConnection"); return; } - ic.sendKeyEvent((KeyEvent) msg.obj); + ic.sendKeyEvent(event); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_CLEAR_META_KEY_STATES: { + case InputConnectionCommandType.CLEAR_META_KEY_STATES: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#clearMetaKeyStates"); try { + final int states = command.mIntArg0; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); return; } - ic.clearMetaKeyStates(msg.arg1); + ic.clearMetaKeyStates(states); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_DELETE_SURROUNDING_TEXT: { + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#deleteSurroundingText"); try { + final int beforeLength = command.mIntArg0; + final int afterLength = command.mIntArg1; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); return; } - ic.deleteSurroundingText(msg.arg1, msg.arg2); + ic.deleteSurroundingText(beforeLength, afterLength); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: { + case InputConnectionCommandType.DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#deleteSurroundingTextInCodePoints"); try { + final int beforeLength = command.mIntArg0; + final int afterLength = command.mIntArg1; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); return; } - ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2); + ic.deleteSurroundingTextInCodePoints(beforeLength, afterLength); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_BEGIN_BATCH_EDIT: { + case InputConnectionCommandType.BEGIN_BATCH_EDIT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#beginBatchEdit"); try { InputConnection ic = getInputConnection(); @@ -746,7 +628,7 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { } return; } - case DO_END_BATCH_EDIT: { + case InputConnectionCommandType.END_BATCH_EDIT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#endBatchEdit"); try { InputConnection ic = getInputConnection(); @@ -760,7 +642,7 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { } return; } - case DO_PERFORM_SPELL_CHECK: { + case InputConnectionCommandType.PERFORM_SPELL_CHECK: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performSpellCheck"); try { InputConnection ic = getInputConnection(); @@ -774,12 +656,11 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { } return; } - case DO_PERFORM_PRIVATE_COMMAND: { - final SomeArgs args = (SomeArgs) msg.obj; + case InputConnectionCommandType.PERFORM_PRIVATE_COMMAND: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performPrivateCommand"); try { - final String action = (String) args.arg1; - final Bundle data = (Bundle) args.arg2; + final String action = command.mString; + final Bundle data = command.mBundle; InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "performPrivateCommand on inactive InputConnection"); @@ -788,142 +669,71 @@ public final class IInputConnectionWrapper extends IInputContext.Stub { ic.performPrivateCommand(action, data); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); - args.recycle(); } return; } - case DO_REQUEST_CURSOR_UPDATES: { + case InputConnectionCommandType.REQUEST_CURSOR_UPDATES: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#requestCursorUpdates"); try { - final IBooleanResultCallback callback = (IBooleanResultCallback) msg.obj; + final int cursorUpdateMode = command.mIntArg0; final InputConnection ic = getInputConnection(); final boolean result; if (ic == null || !isActive()) { Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection"); result = false; } else { - result = ic.requestCursorUpdates(msg.arg1); - } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to requestCursorUpdates()." - + " result=" + result, e); + result = ic.requestCursorUpdates(cursorUpdateMode); } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } - case DO_CLOSE_CONNECTION: { - // Note that we do not need to worry about race condition here, because 1) mFinished - // is updated only inside this block, and 2) the code here is running on a Handler - // hence we assume multiple DO_CLOSE_CONNECTION messages will not be handled at the - // same time. - if (isFinished()) { - return; - } - Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#closeConnection"); - try { - 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 - // through to clean up our state after the IME has switched to - // another client. - if (ic == null) { - return; - } - @MissingMethodFlags - final int missingMethods = InputConnectionInspector.getMissingMethodFlags(ic); - if ((missingMethods & MissingMethodFlags.CLOSE_CONNECTION) == 0) { - ic.closeConnection(); - } - } finally { - synchronized (mLock) { - mInputConnection = null; - mFinished = true; - } - Trace.traceEnd(Trace.TRACE_TAG_INPUT); - } - return; - } - case DO_COMMIT_CONTENT: { - final int flags = msg.arg1; - SomeArgs args = (SomeArgs) msg.obj; + case InputConnectionCommandType.COMMIT_CONTENT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitContent"); try { - final IBooleanResultCallback callback = (IBooleanResultCallback) args.arg3; + final InputContentInfo inputContentInfo = + (InputContentInfo) command.mParcelable; + final int flags = command.mFlags; + final Bundle opts = command.mBundle; final InputConnection ic = getInputConnection(); final boolean result; if (ic == null || !isActive()) { Log.w(TAG, "commitContent on inactive InputConnection"); result = false; } else { - final InputContentInfo inputContentInfo = (InputContentInfo) args.arg1; if (inputContentInfo == null || !inputContentInfo.validate()) { Log.w(TAG, "commitContent with invalid inputContentInfo=" + inputContentInfo); result = false; } else { - result = ic.commitContent(inputContentInfo, flags, (Bundle) args.arg2); + result = ic.commitContent(inputContentInfo, flags, opts); } } - try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to commitContent()." - + " result=" + result, e); - } + CallbackUtils.onResult(command, result, TAG); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); - args.recycle(); } return; } - case DO_SET_IME_CONSUMES_INPUT: { + case InputConnectionCommandType.SET_IME_CONSUMES_INPUT: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setImeConsumesInput"); try { + final boolean imeConsumesInput = (command.mIntArg0 != 0); InputConnection ic = getInputConnection(); if (ic == null || !isActive()) { Log.w(TAG, "setImeConsumesInput on inactive InputConnection"); return; } - ic.setImeConsumesInput(msg.arg1 == 1); + ic.setImeConsumesInput(imeConsumesInput); } finally { Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } } - Log.w(TAG, "Unhandled message code: " + msg.what); - } - - Message obtainMessage(int what) { - return mH.obtainMessage(what); - } - - Message obtainMessageII(int what, int arg1, int arg2) { - return mH.obtainMessage(what, arg1, arg2); - } - - Message obtainMessageO(int what, Object arg1) { - return mH.obtainMessage(what, 0, 0, arg1); - } - - Message obtainMessageIO(int what, int arg1, Object arg2) { - return mH.obtainMessage(what, arg1, 0, arg2); - } - - Message obtainMessageOO(int what, Object arg1, Object arg2) { - final SomeArgs args = SomeArgs.obtain(); - args.arg1 = arg1; - args.arg2 = arg2; - return mH.obtainMessage(what, 0, 0, args); - } - - Message obtainMessageB(int what, boolean arg1) { - return mH.obtainMessage(what, arg1 ? 1 : 0, 0); } } diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index dd42c40edb493..095c0b42e008f 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -16,76 +16,13 @@ package com.android.internal.view; -import android.os.Bundle; -import android.view.KeyEvent; -import android.view.inputmethod.CompletionInfo; -import android.view.inputmethod.CorrectionInfo; -import android.view.inputmethod.ExtractedTextRequest; -import android.view.inputmethod.InputContentInfo; - -import com.android.internal.inputmethod.IBooleanResultCallback; -import com.android.internal.inputmethod.ICharSequenceResultCallback; -import com.android.internal.inputmethod.IExtractedTextResultCallback; -import com.android.internal.inputmethod.IIntResultCallback; -import com.android.internal.inputmethod.ISurroundingTextResultCallback; +import com.android.internal.inputmethod.InputConnectionCommand; /** * Interface from an input method to the application, allowing it to perform * edits on the current input field and other interactions with the application. * {@hide} */ - oneway interface IInputContext { - void getTextBeforeCursor(int length, int flags, ICharSequenceResultCallback callback); - - void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback); - - void getCursorCapsMode(int reqModes, IIntResultCallback callback); - - void getExtractedText(in ExtractedTextRequest request, int flags, - IExtractedTextResultCallback callback); - - void deleteSurroundingText(int beforeLength, int afterLength); - void deleteSurroundingTextInCodePoints(int beforeLength, int afterLength); - - void setComposingText(CharSequence text, int newCursorPosition); - - void finishComposingText(); - - void commitText(CharSequence text, int newCursorPosition); - - void commitCompletion(in CompletionInfo completion); - - void commitCorrection(in CorrectionInfo correction); - - void setSelection(int start, int end); - - void performEditorAction(int actionCode); - - void performContextMenuAction(int id); - - void beginBatchEdit(); - - void endBatchEdit(); - - void sendKeyEvent(in KeyEvent event); - - void clearMetaKeyStates(int states); - - void performSpellCheck(); - - void performPrivateCommand(String action, in Bundle data); - - void setComposingRegion(int start, int end); - - void getSelectedText(int flags, ICharSequenceResultCallback callback); - - void requestCursorUpdates(int cursorUpdateMode, IBooleanResultCallback callback); - - void commitContent(in InputContentInfo inputContentInfo, int flags, in Bundle opts, - IBooleanResultCallback callback); - - void getSurroundingText(int beforeLength, int afterLength, int flags, - ISurroundingTextResultCallback callback); - - void setImeConsumesInput(boolean imeConsumesInput); +oneway interface IInputContext { + void doEdit(in InputConnectionCommand command); } diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java index 32bfdcb7b2174..9de09d6632cd4 100644 --- a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java +++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodDebugTest.java @@ -73,4 +73,13 @@ public class InputMethodDebugTest { InputMethodDebug.softInputDisplayReasonToString( SoftInputShowHideReason.HIDE_REMOVE_CLIENT)); } + + @Test + public void testDumpInputConnectionCommand() { + // TODO: add more tests + assertEquals("null", InputMethodDebug.dumpInputConnectionCommand(null)); + assertEquals("endBatchEdit()", + InputMethodDebug.dumpInputConnectionCommand( + InputConnectionCommand.create(InputConnectionCommandType.END_BATCH_EDIT))); + } }