From 0653b692d1f420e799962efeeb8a26e8c57b579e Mon Sep 17 00:00:00 2001 From: Ioana Stefan Date: Fri, 13 Nov 2020 17:21:04 +0000 Subject: [PATCH] Add InputConnection app and service dump This change dumps information through IME tracing for the getter methods exposed by the InputConnection interface. The dump is done through the ImeTracing interface and is triggered by events in the wrapper classes used to handle InputConnection implementations corresponding to: - different apps - InputMethodService The new data is available under inputConnectionCall in the clients output proto. Bug: 154348613 Test: flash a device start IME tracing by calling "adb shell ime tracing start" end IME tracing by calling "adb shell ime tracing stop" pull generated trace files and visualize in Winscope or start tracing directly through ADB Connect and visualize traces Change-Id: Iabd6af1b858803030848a0ef5e7dd9ecfc562716 --- .../AbstractInputMethodService.java | 3 +- .../InputMethodService.java | 33 ++- .../android/util/imetracing/ImeTracing.java | 9 +- .../util/imetracing/ImeTracingClientImpl.java | 10 +- .../util/imetracing/ImeTracingServerImpl.java | 6 +- .../imetracing/InputConnectionHelper.java | 231 ++++++++++++++++++ core/java/android/view/InsetsController.java | 14 +- core/java/android/view/ViewRootImpl.java | 6 +- .../view/inputmethod/InputMethodManager.java | 25 +- .../view/IInputConnectionWrapper.java | 44 +++- .../internal/view/InputConnectionWrapper.java | 75 +++++- .../widget/EditableInputConnection.java | 4 +- .../inputmethodservice.proto | 2 + .../view/inputmethod/inputconnection.proto | 64 +++++ .../inputmethod/inputmethodeditortrace.proto | 1 + 15 files changed, 484 insertions(+), 43 deletions(-) create mode 100644 core/java/android/util/imetracing/InputConnectionHelper.java diff --git a/core/java/android/inputmethodservice/AbstractInputMethodService.java b/core/java/android/inputmethodservice/AbstractInputMethodService.java index 7cf0b10031acc..3cd13a212a4b8 100644 --- a/core/java/android/inputmethodservice/AbstractInputMethodService.java +++ b/core/java/android/inputmethodservice/AbstractInputMethodService.java @@ -199,10 +199,11 @@ public abstract class AbstractInputMethodService extends Service * Dumps the internal state of IME to a protocol buffer output stream. * * @param proto ProtoOutputStream to dump data to. + * @param icProto {@link InputConnection} call data in proto format. * @hide */ @SuppressWarnings("HiddenAbstractMethod") - public abstract void dumpProtoInternal(ProtoOutputStream proto); + public abstract void dumpProtoInternal(ProtoOutputStream proto, ProtoOutputStream icProto); /** * Implement this to handle {@link android.os.Binder#dump Binder.dump()} diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 67e75d205f977..5576857d1f6b9 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -25,6 +25,7 @@ import static android.inputmethodservice.InputMethodServiceProto.EXTRACTED_TOKEN import static android.inputmethodservice.InputMethodServiceProto.EXTRACT_VIEW_HIDDEN; import static android.inputmethodservice.InputMethodServiceProto.FULLSCREEN_APPLIED; import static android.inputmethodservice.InputMethodServiceProto.INPUT_BINDING; +import static android.inputmethodservice.InputMethodServiceProto.INPUT_CONNECTION_CALL; import static android.inputmethodservice.InputMethodServiceProto.INPUT_EDITOR_INFO; import static android.inputmethodservice.InputMethodServiceProto.INPUT_STARTED; import static android.inputmethodservice.InputMethodServiceProto.INPUT_VIEW_STARTED; @@ -742,7 +743,8 @@ public class InputMethodService extends AbstractInputMethodService { return; } ImeTracing.getInstance().triggerServiceDump( - "InputMethodService.InputMethodImpl#hideSoftInput", InputMethodService.this); + "InputMethodService.InputMethodImpl#hideSoftInput", InputMethodService.this, + null /* icProto */); final boolean wasVisible = isInputViewShown(); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.hideSoftInput"); @@ -798,7 +800,8 @@ public class InputMethodService extends AbstractInputMethodService { } Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showSoftInput"); ImeTracing.getInstance().triggerServiceDump( - "InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this); + "InputMethodService.InputMethodImpl#showSoftInput", InputMethodService.this, + null /* icProto */); final boolean wasVisible = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { @@ -2182,7 +2185,8 @@ public class InputMethodService extends AbstractInputMethodService { return; } - ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#showWindow", this, + null /* icProto */); Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMS.showWindow"); mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; @@ -2260,7 +2264,8 @@ public class InputMethodService extends AbstractInputMethodService { */ private void applyVisibilityInInsetsConsumerIfNecessary(boolean setVisible) { ImeTracing.getInstance().triggerServiceDump( - "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", this); + "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", this, + null /* icProto */); mPrivOps.applyImeVisibility(setVisible ? mCurShowInputToken : mCurHideInputToken, setVisible); } @@ -2285,7 +2290,8 @@ public class InputMethodService extends AbstractInputMethodService { public void hideWindow() { if (DEBUG) Log.v(TAG, "CALL: hideWindow"); - ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#hideWindow", this, + null /* icProto */); mWindowVisible = false; finishViews(false /* finishingInput */); if (mDecorViewVisible) { @@ -2356,7 +2362,8 @@ public class InputMethodService extends AbstractInputMethodService { void doFinishInput() { if (DEBUG) Log.v(TAG, "CALL: doFinishInput"); - ImeTracing.getInstance().triggerServiceDump("InputMethodService#doFinishInput", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#doFinishInput", this, + null /* icProto */); finishViews(true /* finishingInput */); if (mInputStarted) { mInlineSuggestionSessionController.notifyOnFinishInput(); @@ -2372,7 +2379,8 @@ public class InputMethodService extends AbstractInputMethodService { if (!restarting && mInputStarted) { doFinishInput(); } - ImeTracing.getInstance().triggerServiceDump("InputMethodService#doStartInput", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#doStartInput", this, + null /* icProto */); mInputStarted = true; mStartedInputConnection = ic; mInputEditorInfo = attribute; @@ -2531,7 +2539,8 @@ public class InputMethodService extends AbstractInputMethodService { * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestHideSelf", this, + null /* icProto */); mPrivOps.hideMySoftInput(flags); } @@ -2544,7 +2553,8 @@ public class InputMethodService extends AbstractInputMethodService { * @param flags Provides additional operating flags. */ public final void requestShowSelf(int flags) { - ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", this); + ImeTracing.getInstance().triggerServiceDump("InputMethodService#requestShowSelf", this, + null /* icProto */); mPrivOps.showMySoftInput(flags); } @@ -3364,7 +3374,7 @@ public class InputMethodService extends AbstractInputMethodService { * @hide */ @Override - public final void dumpProtoInternal(ProtoOutputStream proto) { + public final void dumpProtoInternal(ProtoOutputStream proto, ProtoOutputStream icProto) { final long token = proto.start(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE); mWindow.dumpDebug(proto, SOFT_INPUT_WINDOW); proto.write(VIEWS_CREATED, mViewsCreated); @@ -3393,6 +3403,9 @@ public class InputMethodService extends AbstractInputMethodService { proto.write(STATUS_ICON, mStatusIcon); mTmpInsets.dumpDebug(proto, LAST_COMPUTED_INSETS); proto.write(SETTINGS_OBSERVER, Objects.toString(mSettingsObserver)); + if (icProto != null) { + proto.write(INPUT_CONNECTION_CALL, icProto.getBytes()); + } proto.end(token); } } diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java index 4058eef3e2a37..723f1dd15e238 100644 --- a/core/java/android/util/imetracing/ImeTracing.java +++ b/core/java/android/util/imetracing/ImeTracing.java @@ -110,15 +110,20 @@ public abstract class ImeTracing { * * @param where Place where the trace was triggered. * @param immInstance The {@link InputMethodManager} instance to dump. + * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format. */ - public abstract void triggerClientDump(String where, InputMethodManager immInstance); + public abstract void triggerClientDump(String where, InputMethodManager immInstance, + ProtoOutputStream icProto); /** * Starts a proto dump of the currently connected InputMethodService information. * * @param where Place where the trace was triggered. + * @param service The {@link android.inputmethodservice.InputMethodService} to be dumped. + * @param icProto {@link android.view.inputmethod.InputConnection} call data in proto format. */ - public abstract void triggerServiceDump(String where, AbstractInputMethodService service); + public abstract void triggerServiceDump(String where, AbstractInputMethodService service, + ProtoOutputStream icProto); /** * Starts a proto dump of the InputMethodManagerService information. diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java index 904b44da97d74..6cc652d942ccb 100644 --- a/core/java/android/util/imetracing/ImeTracingClientImpl.java +++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java @@ -45,7 +45,8 @@ class ImeTracingClientImpl extends ImeTracing { } @Override - public void triggerClientDump(String where, @NonNull InputMethodManager immInstance) { + public void triggerClientDump(String where, @NonNull InputMethodManager immInstance, + ProtoOutputStream icProto) { if (!isEnabled() || !isAvailable()) { return; } @@ -59,7 +60,7 @@ class ImeTracingClientImpl extends ImeTracing { try { ProtoOutputStream proto = new ProtoOutputStream(); - immInstance.dumpDebug(proto); + immInstance.dumpDebug(proto, icProto); sendToService(proto.getBytes(), IME_TRACING_FROM_CLIENT, where); } catch (RemoteException e) { Log.e(TAG, "Exception while sending ime-related client dump to server", e); @@ -69,7 +70,8 @@ class ImeTracingClientImpl extends ImeTracing { } @Override - public void triggerServiceDump(String where, @NonNull AbstractInputMethodService service) { + public void triggerServiceDump(String where, @NonNull AbstractInputMethodService service, + ProtoOutputStream icProto) { if (!isEnabled() || !isAvailable()) { return; } @@ -83,7 +85,7 @@ class ImeTracingClientImpl extends ImeTracing { try { ProtoOutputStream proto = new ProtoOutputStream(); - service.dumpProtoInternal(proto); + service.dumpProtoInternal(proto, icProto); sendToService(proto.getBytes(), IME_TRACING_FROM_IMS, where); } catch (RemoteException e) { Log.e(TAG, "Exception while sending ime-related service dump to server", e); diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java index d758d77fb2f26..e793c280afbc8 100644 --- a/core/java/android/util/imetracing/ImeTracingServerImpl.java +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -133,12 +133,14 @@ class ImeTracingServerImpl extends ImeTracing { } @Override - public void triggerClientDump(String where, InputMethodManager immInstance) { + public void triggerClientDump(String where, InputMethodManager immInstance, + ProtoOutputStream icProto) { // Intentionally left empty, this is implemented in ImeTracingClientImpl } @Override - public void triggerServiceDump(String where, AbstractInputMethodService service) { + public void triggerServiceDump(String where, AbstractInputMethodService service, + ProtoOutputStream icProto) { // Intentionally left empty, this is implemented in ImeTracingClientImpl } diff --git a/core/java/android/util/imetracing/InputConnectionHelper.java b/core/java/android/util/imetracing/InputConnectionHelper.java new file mode 100644 index 0000000000000..39f1e01eb4a9f --- /dev/null +++ b/core/java/android/util/imetracing/InputConnectionHelper.java @@ -0,0 +1,231 @@ +/* + * 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.util.imetracing; + +import static android.view.inputmethod.InputConnectionCallProto.GET_CURSOR_CAPS_MODE; +import static android.view.inputmethod.InputConnectionCallProto.GET_EXTRACTED_TEXT; +import static android.view.inputmethod.InputConnectionCallProto.GET_SELECTED_TEXT; +import static android.view.inputmethod.InputConnectionCallProto.GET_SURROUNDING_TEXT; +import static android.view.inputmethod.InputConnectionCallProto.GET_TEXT_AFTER_CURSOR; +import static android.view.inputmethod.InputConnectionCallProto.GET_TEXT_BEFORE_CURSOR; +import static android.view.inputmethod.InputConnectionCallProto.GetExtractedText.REQUEST; + +import android.annotation.IntRange; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.util.proto.ProtoOutputStream; +import android.view.inputmethod.ExtractedText; +import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.InputConnectionCallProto.GetCursorCapsMode; +import android.view.inputmethod.InputConnectionCallProto.GetExtractedText; +import android.view.inputmethod.InputConnectionCallProto.GetSelectedText; +import android.view.inputmethod.InputConnectionCallProto.GetSurroundingText; +import android.view.inputmethod.InputConnectionCallProto.GetTextAfterCursor; +import android.view.inputmethod.InputConnectionCallProto.GetTextBeforeCursor; +import android.view.inputmethod.SurroundingText; + +/** + * Helper class for constructing {@link android.view.inputmethod.InputConnection} dumps, which are + * integrated into {@link ImeTracing}. + * @hide + */ +public class InputConnectionHelper { + static final String TAG = "InputConnectionHelper"; + public static final boolean DUMP_TEXT = false; + + private InputConnectionHelper() {} + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getTextAfterCursor(int, int)} data. + * + * @param length The expected length of the text. This must be non-negative. + * @param flags Supplies additional options controlling how the text is + * returned. May be either {@code 0} or + * {@link android.view.inputmethod.InputConnection#GET_TEXT_WITH_STYLES}. + * @param result The text after the cursor position; the length of the + * returned text might be less than length. + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetTextAfterCursorProto(@IntRange(from = 0) int length, + int flags, @Nullable CharSequence result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_TEXT_AFTER_CURSOR); + proto.write(GetTextAfterCursor.LENGTH, length); + proto.write(GetTextAfterCursor.FLAGS, flags); + if (result == null) { + proto.write(GetTextAfterCursor.RESULT, "null result"); + } else if (DUMP_TEXT) { + proto.write(GetTextAfterCursor.RESULT, result.toString()); + } + proto.end(token); + return proto; + } + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getTextBeforeCursor(int, int)} data. + * + * @param length The expected length of the text. This must be non-negative. + * @param flags Supplies additional options controlling how the text is + * returned. May be either {@code 0} or + * {@link android.view.inputmethod.InputConnection#GET_TEXT_WITH_STYLES}. + * @param result The text before the cursor position; the length of the + * returned text might be less than length. + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetTextBeforeCursorProto(@IntRange(from = 0) int length, + int flags, @Nullable CharSequence result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_TEXT_BEFORE_CURSOR); + proto.write(GetTextBeforeCursor.LENGTH, length); + proto.write(GetTextBeforeCursor.FLAGS, flags); + if (result == null) { + proto.write(GetTextBeforeCursor.RESULT, "null result"); + } else if (DUMP_TEXT) { + proto.write(GetTextBeforeCursor.RESULT, result.toString()); + } + proto.end(token); + return proto; + } + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getSelectedText(int)} data. + * + * @param flags Supplies additional options controlling how the text is + * returned. May be either {@code 0} or + * {@link android.view.inputmethod.InputConnection#GET_TEXT_WITH_STYLES}. + * @param result the text that is currently selected, if any, or null if + * no text is selected. In {@link android.os.Build.VERSION_CODES#N} and + * later, returns false when the target application does not implement + * this method. + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetSelectedTextProto(int flags, + @Nullable CharSequence result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_SELECTED_TEXT); + proto.write(GetSelectedText.FLAGS, flags); + if (result == null) { + proto.write(GetSelectedText.RESULT, "null result"); + } else if (DUMP_TEXT) { + proto.write(GetSelectedText.RESULT, result.toString()); + } + proto.end(token); + return proto; + } + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getSurroundingText(int, int, int)} data. + * + * @param beforeLength The expected length of the text before the cursor. + * @param afterLength The expected length of the text after the cursor. + * @param flags Supplies additional options controlling how the text is + * returned. May be either {@code 0} or + * {@link android.view.inputmethod.InputConnection#GET_TEXT_WITH_STYLES}. + * @param result an {@link android.view.inputmethod.SurroundingText} object describing the + * surrounding text and state of selection, or null if the input connection is no longer valid, + * or the editor can't comply with the request for some reason, or the application does not + * implement this method. The length of the returned text might be less than the sum of + * beforeLength and afterLength . + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetSurroundingTextProto(@IntRange(from = 0) + int beforeLength, @IntRange(from = 0) int afterLength, int flags, + @Nullable SurroundingText result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_SURROUNDING_TEXT); + proto.write(GetSurroundingText.BEFORE_LENGTH, beforeLength); + proto.write(GetSurroundingText.AFTER_LENGTH, afterLength); + proto.write(GetSurroundingText.FLAGS, flags); + if (result == null) { + final long token_result = proto.start(GetSurroundingText.RESULT); + proto.write(GetSurroundingText.SurroundingText.TEXT, "null result"); + proto.end(token_result); + } else if (DUMP_TEXT) { + final long token_result = proto.start(GetSurroundingText.RESULT); + proto.write(GetSurroundingText.SurroundingText.TEXT, result.getText().toString()); + proto.write(GetSurroundingText.SurroundingText.SELECTION_START, + result.getSelectionStart()); + proto.write(GetSurroundingText.SurroundingText.SELECTION_END, + result.getSelectionEnd()); + proto.write(GetSurroundingText.SurroundingText.OFFSET, result.getOffset()); + proto.end(token_result); + } + proto.end(token); + return proto; + } + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getCursorCapsMode(int)} data. + * + * @param reqModes The desired modes to retrieve, as defined by + * {@link android.text.TextUtils#getCapsMode TextUtils.getCapsMode}. + * @param result the caps mode flags that are in effect at the current + * cursor position. See TYPE_TEXT_FLAG_CAPS_* in {@link android.text.InputType}. + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetCursorCapsModeProto(int reqModes, int result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_CURSOR_CAPS_MODE); + proto.write(GetCursorCapsMode.REQ_MODES, reqModes); + if (DUMP_TEXT) { + proto.write(GetCursorCapsMode.RESULT, result); + } + proto.end(token); + return proto; + } + + /** + * Builder for InputConnectionCallProto to hold + * {@link android.view.inputmethod.InputConnection#getExtractedText(ExtractedTextRequest, int)} + * data. + * + * @param request Description of how the text should be returned. + * {@link android.view.inputmethod.ExtractedTextRequest} + * @param flags Additional options to control the client, either {@code 0} or + * {@link android.view.inputmethod.InputConnection#GET_EXTRACTED_TEXT_MONITOR}. + * @param result an {@link android.view.inputmethod.ExtractedText} + * object describing the state of the text view and containing the + * extracted text itself, or null if the input connection is no + * longer valid of the editor can't comply with the request for + * some reason. + * @return ProtoOutputStream holding the InputConnectionCallProto data. + */ + public static ProtoOutputStream buildGetExtractedTextProto(@NonNull ExtractedTextRequest + request, int flags, @Nullable ExtractedText result) { + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(GET_EXTRACTED_TEXT); + final long token_request = proto.start(REQUEST); + proto.write(GetExtractedText.ExtractedTextRequest.TOKEN, request.token); + proto.write(GetExtractedText.ExtractedTextRequest.FLAGS, request.flags); + proto.write(GetExtractedText.ExtractedTextRequest.HINT_MAX_LINES, request.hintMaxLines); + proto.write(GetExtractedText.ExtractedTextRequest.HINT_MAX_CHARS, request.hintMaxChars); + proto.end(token_request); + proto.write(GetExtractedText.FLAGS, flags); + if (result == null) { + proto.write(GetExtractedText.RESULT, "null result"); + } else if (DUMP_TEXT) { + proto.write(GetExtractedText.RESULT, result.text.toString()); + } + proto.end(token); + return proto; + } +} diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 1c82619a61ade..c4f32c433598c 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -829,7 +829,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#show", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0); Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.showRequestFromIme", 0); } else { @@ -886,7 +886,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation void hide(@InsetsType int types, boolean fromIme) { if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#hide", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromIme", 0); } else { Trace.asyncTraceBegin(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0); @@ -928,7 +928,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (fromIme) { ImeTracing.getInstance().triggerClientDump( "InsetsController#controlWindowInsetsAnimation", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); } controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, @@ -1022,7 +1022,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation animationType, mHost.getTranslator()); if ((typesReady & WindowInsets.Type.ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); } mRunningAnimations.add(new RunningAnimation(runner, animationType)); if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: " @@ -1200,7 +1200,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (types.valueAt(j) == ITYPE_IME) { ImeTracing.getInstance().triggerClientDump( "InsetsSourceConsumer#notifyAnimationFinished", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); } stateChanged |= getSourceConsumer(types.valueAt(j)).notifyAnimationFinished(); } @@ -1345,7 +1345,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation boolean fromIme) { if ((types & ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); } final ArraySet internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { @@ -1361,7 +1361,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void showDirectly(@InsetsType int types, boolean fromIme) { if ((types & ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsController#showDirectly", - mHost.getInputMethodManager()); + mHost.getInputMethodManager(), null /* icProto */); } final ArraySet internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 2bea0d6b4b04c..8321f2b28771e 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -9225,7 +9225,8 @@ public final class ViewRootImpl implements ViewParent, final ViewRootImpl viewAncestor = mViewAncestor.get(); if (fromIme) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets", - viewAncestor.getInsetsController().getHost().getInputMethodManager()); + viewAncestor.getInsetsController().getHost().getInputMethodManager(), + null /* icProto */); } if (viewAncestor != null) { viewAncestor.showInsets(types, fromIme); @@ -9238,7 +9239,8 @@ public final class ViewRootImpl implements ViewParent, final ViewRootImpl viewAncestor = mViewAncestor.get(); if (fromIme) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets", - viewAncestor.getInsetsController().getHost().getInputMethodManager()); + viewAncestor.getInsetsController().getHost().getInputMethodManager(), + null /* icProto */); } if (viewAncestor != null) { viewAncestor.hideInsets(types, fromIme); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 8d2c2d96637fd..907b5b085b590 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -23,6 +23,7 @@ import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodCl 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_CONNECTION_CALL; 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; @@ -579,7 +580,8 @@ public final class InputMethodManager { int windowFlags) { final View servedView; ImeTracing.getInstance().triggerClientDump( - "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this); + "InputMethodManager.DelegateImpl#startInput", InputMethodManager.this, + null /* icProto */); synchronized (mH) { mCurrentTextBoxAttribute = null; mCompletions = null; @@ -1016,6 +1018,11 @@ public final class InputMethodManager { return mParentInputMethodManager.mActive && !isFinished(); } + @Override + public InputMethodManager getIMM() { + return mParentInputMethodManager; + } + void deactivate() { if (isFinished()) { // This is a small performance optimization. Still only the 1st call of @@ -1713,7 +1720,8 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { - ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this, + null /* icProto */); // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { @@ -1822,7 +1830,7 @@ public final class InputMethodManager { public boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver) { ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow", - this); + this, null /* icProto */); checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); @@ -2228,7 +2236,8 @@ public final class InputMethodManager { * @hide */ public void notifyImeHidden(IBinder windowToken) { - ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this, + null /* icProto */); synchronized (mH) { try { if (mCurMethod != null && mCurRootView != null @@ -3296,7 +3305,7 @@ public final class InputMethodManager { for (String arg : args) { if (arg.equals(PROTO_ARG)) { final ProtoOutputStream proto = new ProtoOutputStream(fd); - dumpDebug(proto); + dumpDebug(proto, null /* icProto */); proto.flush(); return true; } @@ -3309,10 +3318,11 @@ public final class InputMethodManager { * {@link ProtoOutputStream}. * * @param proto The proto stream to which the dumps are written. + * @param icProto {@link InputConnection} call data in proto format. * @hide */ @GuardedBy("mH") - public void dumpDebug(ProtoOutputStream proto) { + public void dumpDebug(ProtoOutputStream proto, ProtoOutputStream icProto) { if (mCurMethod == null) { return; } @@ -3337,6 +3347,9 @@ public final class InputMethodManager { if (mServedInputConnectionWrapper != null) { mServedInputConnectionWrapper.dumpDebug(proto, INPUT_CONNECTION); } + if (icProto != null) { + proto.write(INPUT_CONNECTION_CALL, icProto.getBytes()); + } } } } diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index 4deb40a0d772e..1d5935db63225 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -27,6 +27,9 @@ import android.os.Message; import android.os.RemoteException; import android.os.Trace; import android.util.Log; +import android.util.imetracing.ImeTracing; +import android.util.imetracing.InputConnectionHelper; +import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -36,6 +39,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputConnectionInspector; import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; import android.view.inputmethod.InputContentInfo; +import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.SurroundingText; import com.android.internal.annotations.GuardedBy; @@ -124,7 +128,9 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } } - abstract protected boolean isActive(); + protected abstract boolean isActive(); + + protected abstract InputMethodManager getIMM(); public void getTextAfterCursor(int length, int flags, ICharSequenceResultCallback callback) { dispatchMessage(mH.obtainMessage(DO_GET_TEXT_AFTER_CURSOR, length, flags, callback)); @@ -264,6 +270,7 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } void executeMessage(Message msg) { + ProtoOutputStream icProto; switch (msg.what) { case DO_GET_TEXT_AFTER_CURSOR: { Trace.traceBegin(Trace.TRACE_TAG_INPUT, "InputConnection#getTextAfterCursor"); @@ -278,6 +285,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getTextAfterCursor(msg.arg1, msg.arg2); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetTextAfterCursorProto(msg.arg1, + msg.arg2, result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getTextAfterCursor", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { @@ -302,6 +315,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getTextBeforeCursor(msg.arg1, msg.arg2); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetTextBeforeCursorProto(msg.arg1, + msg.arg2, result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getTextBeforeCursor", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { @@ -326,6 +345,11 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getSelectedText(msg.arg1); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetSelectedTextProto(msg.arg1, result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getSelectedText", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { @@ -354,6 +378,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getSurroundingText(beforeLength, afterLength, flags); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetSurroundingTextProto(beforeLength, + afterLength, flags, result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getSurroundingText", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { @@ -378,6 +408,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getCursorCapsMode(msg.arg1); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetCursorCapsModeProto(msg.arg1, + result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getCursorCapsMode", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { @@ -404,6 +440,12 @@ public abstract class IInputConnectionWrapper extends IInputContext.Stub { } else { result = ic.getExtractedText(request, msg.arg1); } + if (ImeTracing.getInstance().isEnabled()) { + icProto = InputConnectionHelper.buildGetExtractedTextProto(request, + msg.arg1, result); + ImeTracing.getInstance().triggerClientDump( + TAG + "#getExtractedText", getIMM(), icProto); + } try { callback.onResult(result); } catch (RemoteException e) { diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 8c763a6efe549..b70348a953841 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -24,6 +24,9 @@ import android.inputmethodservice.AbstractInputMethodService; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; +import android.util.imetracing.ImeTracing; +import android.util.imetracing.InputConnectionHelper; +import android.util.proto.ProtoOutputStream; import android.view.KeyEvent; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.CorrectionInfo; @@ -87,8 +90,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return null; } - return Completable.getResultOrNull( + CharSequence result = Completable.getResultOrNull( value, TAG, "getTextAfterCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetTextAfterCursorProto(length, + flags, result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getTextAfterCursor", + inputMethodService, icProto); + } + + return result; } /** @@ -107,8 +120,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return null; } - return Completable.getResultOrNull( + CharSequence result = Completable.getResultOrNull( value, TAG, "getTextBeforeCursor()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetTextBeforeCursorProto(length, + flags, result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getTextBeforeCursor", + inputMethodService, icProto); + } + + return result; } @AnyThread @@ -127,8 +150,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return null; } - return Completable.getResultOrNull( + CharSequence result = Completable.getResultOrNull( value, TAG, "getSelectedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetSelectedTextProto(flags, + result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getSelectedText", + inputMethodService, icProto); + } + + return result; } /** @@ -161,8 +194,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return null; } - return Completable.getResultOrNull( + SurroundingText result = Completable.getResultOrNull( value, TAG, "getSurroundingText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetSurroundingTextProto( + beforeLength, afterLength, flags, result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getSurroundingText", + inputMethodService, icProto); + } + + return result; } @AnyThread @@ -177,8 +220,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return 0; } - return Completable.getResultOrZero( + int result = Completable.getResultOrZero( value, TAG, "getCursorCapsMode()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetCursorCapsModeProto( + reqModes, result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getCursorCapsMode", + inputMethodService, icProto); + } + + return result; } @AnyThread @@ -193,8 +246,18 @@ public class InputConnectionWrapper implements InputConnection { } catch (RemoteException e) { return null; } - return Completable.getResultOrNull( + ExtractedText result = Completable.getResultOrNull( value, TAG, "getExtractedText()", mCancellationGroup, MAX_WAIT_TIME_MILLIS); + + final AbstractInputMethodService inputMethodService = mInputMethodService.get(); + if (inputMethodService != null && ImeTracing.getInstance().isEnabled()) { + ProtoOutputStream icProto = InputConnectionHelper.buildGetExtractedTextProto( + request, flags, result); + ImeTracing.getInstance().triggerServiceDump(TAG + "#getExtractedText", + inputMethodService, icProto); + } + + return result; } @AnyThread diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index d30d662806d49..6cdf0df17b65c 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -28,6 +28,7 @@ import android.text.Editable; import android.text.Selection; import android.text.method.KeyListener; import android.util.Log; +import android.util.imetracing.InputConnectionHelper; import android.util.proto.ProtoOutputStream; import android.view.inputmethod.BaseInputConnection; import android.view.inputmethod.CompletionInfo; @@ -45,7 +46,6 @@ import android.widget.TextView; 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; @@ -243,7 +243,7 @@ public class EditableInputConnection extends BaseInputConnection final long token = proto.start(fieldId); CharSequence editableText = mTextView.getText(); CharSequence selectedText = getSelectedText(0 /* flags */); - if (DUMP_TEXT) { + if (InputConnectionHelper.DUMP_TEXT) { if (editableText != null) { proto.write(EDITABLE_TEXT, editableText.toString()); } diff --git a/core/proto/android/inputmethodservice/inputmethodservice.proto b/core/proto/android/inputmethodservice/inputmethodservice.proto index e5d1713616953..1f68fb4b513da 100644 --- a/core/proto/android/inputmethodservice/inputmethodservice.proto +++ b/core/proto/android/inputmethodservice/inputmethodservice.proto @@ -18,6 +18,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/inputmethodservice/softinputwindow.proto"; import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; +import "frameworks/base/core/proto/android/view/inputmethod/inputconnection.proto"; package android.inputmethodservice; @@ -51,6 +52,7 @@ message InputMethodServiceProto { optional int32 status_icon = 25; optional InsetsProto last_computed_insets = 26; optional string settings_observer = 27; + optional .android.view.inputmethod.InputConnectionCallProto input_connection_call = 28; message InsetsProto { optional int32 content_top_insets = 1; diff --git a/core/proto/android/view/inputmethod/inputconnection.proto b/core/proto/android/view/inputmethod/inputconnection.proto index ad9a95aa95e6a..d1f257ff2c5c4 100644 --- a/core/proto/android/view/inputmethod/inputconnection.proto +++ b/core/proto/android/view/inputmethod/inputconnection.proto @@ -31,4 +31,68 @@ message InputConnectionProto { optional int32 selected_text_start = 3; optional int32 selected_text_end = 4; optional int32 cursor_caps_mode = 5; +} + +/** + * Shows information about parameters and result for method calls to + * {@link android.view.inputmethod.InputConnection}. + */ +message InputConnectionCallProto { + oneof method_call { + GetTextBeforeCursor get_text_before_cursor = 1; + GetTextAfterCursor get_text_after_cursor = 2; + GetSelectedText get_selected_text = 3; + GetSurroundingText get_surrounding_text = 4; + GetCursorCapsMode get_cursor_caps_mode = 5; + GetExtractedText get_extracted_text = 6; + } + + message GetTextBeforeCursor { + optional int32 length = 1; + optional int32 flags = 2; + optional string result = 3 [(.android.privacy).dest = DEST_LOCAL]; + } + + message GetTextAfterCursor { + optional int32 length = 1; + optional int32 flags = 2; + optional string result = 3 [(.android.privacy).dest = DEST_LOCAL]; + } + + message GetSelectedText { + optional int32 flags = 1; + optional string result = 2 [(.android.privacy).dest = DEST_LOCAL]; + } + + message GetSurroundingText { + optional int32 before_length = 1; + optional int32 after_length = 2; + optional int32 flags = 3; + optional SurroundingText result = 4; + + message SurroundingText { + optional string text = 1 [(.android.privacy).dest = DEST_LOCAL]; + optional int32 selection_start = 2; + optional int32 selection_end = 3; + optional int32 offset = 4; + } + } + + message GetCursorCapsMode { + optional int32 req_modes = 1; + optional int32 result = 2; + } + + message GetExtractedText { + optional ExtractedTextRequest request = 1; + optional int32 flags = 2; + optional string result = 3 [(.android.privacy).dest = DEST_LOCAL]; + + message ExtractedTextRequest { + optional int32 token = 1; + optional int32 flags = 2; + optional int32 hint_max_lines = 3; + optional int32 hint_max_chars = 4; + } + } } \ 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 c1dce6f2d093b..8e4377ca124cf 100644 --- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto +++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto @@ -72,6 +72,7 @@ message InputMethodClientsTraceProto { optional EditorInfoProto editor_info = 6; optional ImeFocusControllerProto ime_focus_controller = 7; optional InputConnectionProto input_connection = 8; + optional InputConnectionCallProto input_connection_call = 9; } }