diff --git a/core/java/android/view/inputmethod/DumpableInputConnection.java b/core/java/android/view/inputmethod/DumpableInputConnection.java new file mode 100644 index 0000000000000..9819a57344914 --- /dev/null +++ b/core/java/android/view/inputmethod/DumpableInputConnection.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2020 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import android.annotation.NonNull; +import android.util.proto.ProtoOutputStream; + +/** @hide */ +public interface DumpableInputConnection { + + /** + * Method used to dump state of InputConnection implementations of interest. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of DumpableInputConnection as defined in the parent message + */ + void dumpDebug(@NonNull ProtoOutputStream proto, long fieldId); +} diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 315d4461692cb..c3d3985b5ac38 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -22,6 +22,7 @@ import static android.util.imetracing.ImeTracing.PROTO_ARG; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_METHOD_MANAGER; import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.VIEW_ROOT_IMPL; import static android.view.inputmethod.InputMethodManagerProto.ACTIVE; @@ -998,9 +999,9 @@ public final class InputMethodManager { private final InputMethodManager mParentInputMethodManager; private final WeakReference mServedView; - ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn, + ControlledInputConnectionWrapper(Looper icLooper, InputConnection conn, InputMethodManager inputMethodManager, View servedView) { - super(mainLooper, conn); + super(icLooper, conn); mParentInputMethodManager = inputMethodManager; mServedView = new WeakReference<>(servedView); } @@ -1046,6 +1047,18 @@ public final class InputMethodManager { + " mServedView=" + mServedView.get() + "}"; } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + // Check that the call is initiated in the main thread of the current InputConnection + // {@link InputConnection#getHandler} since the messages to IInputConnectionWrapper are + // executed on this thread. Otherwise the messages are dispatched to the correct thread + // in IInputConnectionWrapper, but this is not wanted while dumpng, for performance + // reasons. + if (getInputConnection() instanceof DumpableInputConnection && Looper.myLooper() + == getLooper()) { + ((DumpableInputConnection) getInputConnection()).dumpDebug(proto, fieldId); + } + } } private static class ImeThreadFactory implements ThreadFactory { @@ -2207,6 +2220,7 @@ public final class InputMethodManager { * @hide */ public void notifyImeHidden(IBinder windowToken) { + ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this); synchronized (mH) { try { if (mCurMethod != null && mCurRootView != null @@ -3312,6 +3326,9 @@ public final class InputMethodManager { if (mImeInsetsConsumer != null) { mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER); } + if (mServedInputConnectionWrapper != null) { + mServedInputConnectionWrapper.dumpDebug(proto, INPUT_CONNECTION); + } } } } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 33ebe43cb23ae..4deb40a0d772e 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -25,6 +25,7 @@ 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.view.KeyEvent; import android.view.inputmethod.CompletionInfo; @@ -111,6 +112,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } } + protected Looper getLooper() { + synchronized (mMainLooper) { + return mMainLooper; + } + } + protected boolean isFinished() { synchronized (mLock) { return mFinished; @@ -259,61 +266,80 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { void executeMessage(Message msg) { switch (msg.what) { case DO_GET_TEXT_AFTER_CURSOR: { - final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; - 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); - } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getTextAfterCursor"); try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getTextAfterCursor()." + final ICharSequenceResultCallback callback = + (ICharSequenceResultCallback) msg.obj; + 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); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getTextAfterCursor()." + " result=" + result, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } case DO_GET_TEXT_BEFORE_CURSOR: { - final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; - 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); - } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getTextBeforeCursor"); try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getTextBeforeCursor()." + final ICharSequenceResultCallback callback = + (ICharSequenceResultCallback) msg.obj; + 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); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getTextBeforeCursor()." + " result=" + result, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } case DO_GET_SELECTED_TEXT: { - final ICharSequenceResultCallback callback = (ICharSequenceResultCallback) msg.obj; - 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); - } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getSelectedText"); try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getSelectedText()." - + " result=" + result, e); + final ICharSequenceResultCallback callback = + (ICharSequenceResultCallback) msg.obj; + 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); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getSelectedText()." + + " result=" + result, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } case DO_GET_SURROUNDING_TEXT: { final SomeArgs args = (SomeArgs) msg.obj; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getSurroundingText"); try { int beforeLength = (int) args.arg1; int afterLength = (int) args.arg2; @@ -335,30 +361,37 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { + " result=" + result, e); } } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); args.recycle(); } return; } case DO_GET_CURSOR_CAPS_MODE: { - final IIntResultCallback callback = (IIntResultCallback) msg.obj; - 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); - } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getCursorCapsMode"); try { - callback.onResult(result); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to getCursorCapsMode()." + final IIntResultCallback callback = (IIntResultCallback) msg.obj; + 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); + } + try { + callback.onResult(result); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to getCursorCapsMode()." + " result=" + result, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } case DO_GET_EXTRACTED_TEXT: { final SomeArgs args = (SomeArgs) msg.obj; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getExtractedText"); try { final ExtractedTextRequest request = (ExtractedTextRequest) args.arg1; final IExtractedTextResultCallback callback = @@ -378,159 +411,237 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { + " result=" + result, e); } } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); args.recycle(); } return; } case DO_COMMIT_TEXT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "commitText on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitText"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "commitText on inactive InputConnection"); + return; + } + ic.commitText((CharSequence) msg.obj, msg.arg1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.commitText((CharSequence)msg.obj, msg.arg1); return; } case DO_SET_SELECTION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "setSelection on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setSelection"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setSelection on inactive InputConnection"); + return; + } + ic.setSelection(msg.arg1, msg.arg2); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.setSelection(msg.arg1, msg.arg2); return; } case DO_PERFORM_EDITOR_ACTION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "performEditorAction on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performEditorAction"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performEditorAction on inactive InputConnection"); + return; + } + ic.performEditorAction(msg.arg1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.performEditorAction(msg.arg1); return; } case DO_PERFORM_CONTEXT_MENU_ACTION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "performContextMenuAction on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performContextMenuAction"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "performContextMenuAction on inactive InputConnection"); + return; + } + ic.performContextMenuAction(msg.arg1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.performContextMenuAction(msg.arg1); return; } case DO_COMMIT_COMPLETION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "commitCompletion on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitCompletion"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "commitCompletion on inactive InputConnection"); + return; + } + ic.commitCompletion((CompletionInfo) msg.obj); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.commitCompletion((CompletionInfo)msg.obj); return; } case DO_COMMIT_CORRECTION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "commitCorrection on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitCorrection"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "commitCorrection on inactive InputConnection"); + return; + } + ic.commitCorrection((CorrectionInfo) msg.obj); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.commitCorrection((CorrectionInfo)msg.obj); return; } case DO_SET_COMPOSING_TEXT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "setComposingText on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setComposingText"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setComposingText on inactive InputConnection"); + return; + } + ic.setComposingText((CharSequence) msg.obj, msg.arg1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.setComposingText((CharSequence)msg.obj, msg.arg1); return; } case DO_SET_COMPOSING_REGION: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "setComposingRegion on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#setComposingRegion"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "setComposingRegion on inactive InputConnection"); + return; + } + ic.setComposingRegion(msg.arg1, msg.arg2); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.setComposingRegion(msg.arg1, msg.arg2); return; } case DO_FINISH_COMPOSING_TEXT: { - if (isFinished()) { - // In this case, #finishComposingText() is guaranteed to be called already. - // There should be no negative impact if we ignore this call silently. - if (DEBUG) { - Log.w(TAG, "Bug 35301295: Redundant finishComposingText."); + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#finishComposingText"); + try { + if (isFinished()) { + // In this case, #finishComposingText() is guaranteed to be called already. + // There should be no negative impact if we ignore this call silently. + if (DEBUG) { + Log.w(TAG, "Bug 35301295: Redundant finishComposingText."); + } + return; } - return; + 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) { + Log.w(TAG, "finishComposingText on inactive InputConnection"); + return; + } + ic.finishComposingText(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - 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) { - Log.w(TAG, "finishComposingText on inactive InputConnection"); - return; - } - ic.finishComposingText(); return; } case DO_SEND_KEY_EVENT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "sendKeyEvent on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#sendKeyEvent"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "sendKeyEvent on inactive InputConnection"); + return; + } + ic.sendKeyEvent((KeyEvent) msg.obj); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.sendKeyEvent((KeyEvent)msg.obj); return; } case DO_CLEAR_META_KEY_STATES: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#clearMetaKeyStates"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "clearMetaKeyStates on inactive InputConnection"); + return; + } + ic.clearMetaKeyStates(msg.arg1); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.clearMetaKeyStates(msg.arg1); return; } case DO_DELETE_SURROUNDING_TEXT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#deleteSurroundingText"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "deleteSurroundingText on inactive InputConnection"); + return; + } + ic.deleteSurroundingText(msg.arg1, msg.arg2); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.deleteSurroundingText(msg.arg1, msg.arg2); return; } case DO_DELETE_SURROUNDING_TEXT_IN_CODE_POINTS: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, + "InputConnection#deleteSurroundingTextInCodePoints"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "deleteSurroundingTextInCodePoints on inactive InputConnection"); + return; + } + ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.deleteSurroundingTextInCodePoints(msg.arg1, msg.arg2); return; } case DO_BEGIN_BATCH_EDIT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "beginBatchEdit on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#beginBatchEdit"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "beginBatchEdit on inactive InputConnection"); + return; + } + ic.beginBatchEdit(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.beginBatchEdit(); return; } case DO_END_BATCH_EDIT: { - InputConnection ic = getInputConnection(); - if (ic == null || !isActive()) { - Log.w(TAG, "endBatchEdit on inactive InputConnection"); - return; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#endBatchEdit"); + try { + InputConnection ic = getInputConnection(); + if (ic == null || !isActive()) { + Log.w(TAG, "endBatchEdit on inactive InputConnection"); + return; + } + ic.endBatchEdit(); + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } - ic.endBatchEdit(); return; } case DO_PERFORM_PRIVATE_COMMAND: { final SomeArgs args = (SomeArgs) msg.obj; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#performPrivateCommand"); try { final String action = (String) args.arg1; final Bundle data = (Bundle) args.arg2; @@ -541,25 +652,31 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } ic.performPrivateCommand(action, data); } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); args.recycle(); } return; } case DO_REQUEST_UPDATE_CURSOR_ANCHOR_INFO: { - final IIntResultCallback callback = (IIntResultCallback) msg.obj; - 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); - } + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#requestCursorUpdates"); try { - callback.onResult(result ? 1 : 0); - } catch (RemoteException e) { - Log.w(TAG, "Failed to return the result to requestCursorUpdates()." - + " result=" + result, e); + final IIntResultCallback callback = (IIntResultCallback) msg.obj; + 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 ? 1 : 0); + } catch (RemoteException e) { + Log.w(TAG, "Failed to return the result to requestCursorUpdates()." + + " result=" + result, e); + } + } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); } return; } @@ -571,6 +688,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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 @@ -590,12 +708,14 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { 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; + Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#commitContent"); try { final IIntResultCallback callback = (IIntResultCallback) args.arg3; final InputConnection ic = getInputConnection(); @@ -620,6 +740,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { + " result=" + result, e); } } finally { + Trace.traceEnd(Trace.TRACE_TAG_INPUT); args.recycle(); } return; diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index ff3543c837ebc..d30d662806d49 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -16,21 +16,36 @@ package com.android.internal.widget; +import static android.view.inputmethod.InputConnectionProto.CURSOR_CAPS_MODE; +import static android.view.inputmethod.InputConnectionProto.EDITABLE_TEXT; +import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT; +import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_END; +import static android.view.inputmethod.InputConnectionProto.SELECTED_TEXT_START; + import android.compat.annotation.UnsupportedAppUsage; import android.os.Bundle; import android.text.Editable; +import android.text.Selection; import android.text.method.KeyListener; import android.util.Log; +import android.util.proto.ProtoOutputStream; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; +import android.view.inputmethod.DumpableInputConnection; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.widget.TextView; -public class EditableInputConnection extends BaseInputConnection { +/** + * Base class for an editable InputConnection instance. This is created by {@link TextView} or + * {@link EditText}. + */ +public class EditableInputConnection extends BaseInputConnection + implements DumpableInputConnection { private static final boolean DEBUG = false; + private static final boolean DUMP_TEXT = false; private static final String TAG = "EditableInputConnection"; private final TextView mTextView; @@ -222,4 +237,28 @@ public class EditableInputConnection extends BaseInputConnection { } return true; } + + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + CharSequence editableText = mTextView.getText(); + CharSequence selectedText = getSelectedText(0 /* flags */); + if (DUMP_TEXT) { + if (editableText != null) { + proto.write(EDITABLE_TEXT, editableText.toString()); + } + if (selectedText != null) { + proto.write(SELECTED_TEXT, selectedText.toString()); + } + } + final Editable content = getEditable(); + if (content != null) { + int start = Selection.getSelectionStart(content); + int end = Selection.getSelectionEnd(content); + proto.write(SELECTED_TEXT_START, start); + proto.write(SELECTED_TEXT_END, end); + } + proto.write(CURSOR_CAPS_MODE, getCursorCapsMode(0)); + proto.end(token); + } } diff --git a/core/proto/android/view/inputmethod/inputconnection.proto b/core/proto/android/view/inputmethod/inputconnection.proto new file mode 100644 index 0000000000000..ad9a95aa95e6a --- /dev/null +++ b/core/proto/android/view/inputmethod/inputconnection.proto @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2020 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. + */ + +syntax = "proto2"; + +import "frameworks/base/core/proto/android/privacy.proto"; + +package android.view.inputmethod; + +option java_multiple_files = true; + +/** + * Represents a {@link android.view.inputmethod.InputConnection} object. + */ +message InputConnectionProto { + optional string editable_text = 1 [(.android.privacy).dest = DEST_LOCAL]; + optional string selected_text = 2 [(.android.privacy).dest = DEST_LOCAL]; + optional int32 selected_text_start = 3; + optional int32 selected_text_end = 4; + optional int32 cursor_caps_mode = 5; +} \ No newline at end of file diff --git a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto index 5c0f341cb9e46..c1dce6f2d093b 100644 --- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto +++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto @@ -24,6 +24,7 @@ import "frameworks/base/core/proto/android/view/viewrootimpl.proto"; import "frameworks/base/core/proto/android/view/insetscontroller.proto"; import "frameworks/base/core/proto/android/view/imeinsetssourceconsumer.proto"; import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; +import "frameworks/base/core/proto/android/view/inputmethod/inputconnection.proto"; import "frameworks/base/core/proto/android/view/imefocuscontroller.proto"; import "frameworks/base/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto"; @@ -70,6 +71,7 @@ message InputMethodClientsTraceProto { optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5; optional EditorInfoProto editor_info = 6; optional ImeFocusControllerProto ime_focus_controller = 7; + optional InputConnectionProto input_connection = 8; } }