From cf9e5123ce04dfe1d03b942a6a5632ca1b9b27fd Mon Sep 17 00:00:00 2001 From: Anmol Gupta Date: Tue, 28 Apr 2020 06:33:33 -0700 Subject: [PATCH] Add proto-based client side dumping for IME tracing This CL implements a mechanism to dump IME related client states into a proto file which can later be imported to winscope to allow easy debugging. A new abstract class ImeTracing.java declares the methods related to scheduling, collecting and dumping logs. Two child class implement these methods for server and client separately. The Design Doc for the IME tracing project is: go/ime-tracing Bug: 154348613 Test: start trace by calling "adb shell ime tracing start" end trace by calling "adb shell ime tracing stop" pull trace using "adb pull /data/misc/wmtrace/ime_trace.pb ime_trace.pb" Change-Id: Ia89f11d5ef8a220ea7746191b18769cea5a8359d --- .../android/util/imetracing/ImeTracing.java | 114 +++++++++++++ .../util/imetracing/ImeTracingClientImpl.java | 71 ++++++++ .../util/imetracing/ImeTracingServerImpl.java | 154 ++++++++++++++++++ .../java/android/view/ImeFocusController.java | 15 ++ .../android/view/ImeInsetsSourceConsumer.java | 16 +- .../view/InsetsAnimationControlImpl.java | 29 ++++ .../view/InsetsAnimationControlRunner.java | 11 ++ .../InsetsAnimationThreadControlRunner.java | 7 + core/java/android/view/InsetsController.java | 33 ++++ core/java/android/view/InsetsSource.java | 16 ++ .../android/view/InsetsSourceConsumer.java | 29 ++++ .../android/view/InsetsSourceControl.java | 22 +++ core/java/android/view/InsetsState.java | 13 ++ core/java/android/view/ViewRootImpl.java | 61 +++++++ .../android/view/inputmethod/EditorInfo.java | 27 +++ .../view/inputmethod/InputMethodManager.java | 95 +++++++++++ .../internal/view/IInputMethodClient.aidl | 1 + .../internal/view/IInputMethodManager.aidl | 2 + .../view/imeinsetssourceconsumer.proto | 6 +- .../inputmethod/inputmethodeditortrace.proto | 16 +- .../InputMethodManagerService.java | 70 ++++++++ .../MultiClientInputMethodManagerService.java | 11 ++ 22 files changed, 810 insertions(+), 9 deletions(-) create mode 100644 core/java/android/util/imetracing/ImeTracing.java create mode 100644 core/java/android/util/imetracing/ImeTracingClientImpl.java create mode 100644 core/java/android/util/imetracing/ImeTracingServerImpl.java diff --git a/core/java/android/util/imetracing/ImeTracing.java b/core/java/android/util/imetracing/ImeTracing.java new file mode 100644 index 0000000000000..865d5608a40a1 --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracing.java @@ -0,0 +1,114 @@ +/* + * 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 android.app.ActivityThread; +import android.content.Context; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.view.IInputMethodManager; + +/** + * + * An abstract class that declares the methods for ime trace related operations - enable trace, + * schedule trace and add new trace to buffer. Both the client and server side classes can use + * it by getting an implementation through {@link ImeTracing#getInstance()}. + * + * @hide + */ +public abstract class ImeTracing { + + static final String TAG = "imeTracing"; + public static final String PROTO_ARG = "--proto-com-android-imetracing"; + + private static ImeTracing sInstance; + static boolean sEnabled = false; + IInputMethodManager mService; + + ImeTracing() throws ServiceNotFoundException { + mService = IInputMethodManager.Stub.asInterface( + ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE)); + } + + /** + * Returns an instance of {@link ImeTracingServerImpl} when called from a server side class + * and an instance of {@link ImeTracingClientImpl} when called from a client side class. + * Useful to schedule a dump for next frame or save a dump when certain methods are called. + * + * @return Instance of one of the children classes of {@link ImeTracing} + */ + public static ImeTracing getInstance() { + if (sInstance == null) { + try { + sInstance = isSystemProcess() + ? new ImeTracingServerImpl() : new ImeTracingClientImpl(); + } catch (RemoteException | ServiceNotFoundException e) { + Log.e(TAG, "Exception while creating ImeTracing instance", e); + } + } + return sInstance; + } + + /** + * Sends request to start proto dump to {@link ImeTracingServerImpl} when called from a + * server process and to {@link ImeTracingClientImpl} when called from a client process. + */ + public abstract void triggerDump(); + + /** + * @param proto dump to be added to the buffer + */ + public abstract void addToBuffer(ProtoOutputStream proto); + + /** + * @param shell The shell command to process + * @return {@code 0} if the command was successfully processed, {@code -1} otherwise + */ + public abstract int onShellCommand(ShellCommand shell); + + /** + * Sets whether ime tracing is enabled. + * + * @param enabled Tells whether ime tracing should be enabled or disabled. + */ + public void setEnabled(boolean enabled) { + sEnabled = enabled; + } + + /** + * @return {@code true} if dumping is enabled, {@code false} otherwise. + */ + public boolean isEnabled() { + return sEnabled; + } + + /** + * @return {@code true} if tracing is available, {@code false} otherwise. + */ + public boolean isAvailable() { + return mService != null; + } + + private static boolean isSystemProcess() { + return ActivityThread.isSystem(); + } +} diff --git a/core/java/android/util/imetracing/ImeTracingClientImpl.java b/core/java/android/util/imetracing/ImeTracingClientImpl.java new file mode 100644 index 0000000000000..e5d7d3380d021 --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracingClientImpl.java @@ -0,0 +1,71 @@ +/* + * 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 android.os.RemoteException; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; +import android.view.inputmethod.InputMethodManager; + +/** + * @hide + */ +class ImeTracingClientImpl extends ImeTracing { + + private boolean mDumpInProgress; + private final Object mDumpInProgressLock = new Object(); + + ImeTracingClientImpl() throws ServiceNotFoundException, RemoteException { + sEnabled = mService.isImeTraceEnabled(); + } + + @Override + public void addToBuffer(ProtoOutputStream proto) { + } + + @Override + public int onShellCommand(ShellCommand shell) { + return -1; + } + + @Override + public void triggerDump() { + if (isAvailable() && isEnabled()) { + boolean doDump = false; + synchronized (mDumpInProgressLock) { + if (!mDumpInProgress) { + mDumpInProgress = true; + doDump = true; + } + } + + if (doDump) { + try { + ProtoOutputStream proto = new ProtoOutputStream(); + InputMethodManager.dumpProto(proto); + mService.startProtoDump(proto.getBytes()); + } catch (RemoteException e) { + Log.e(TAG, "Exception while sending ime-related client dump to server", e); + } finally { + mDumpInProgress = false; + } + } + } + } +} diff --git a/core/java/android/util/imetracing/ImeTracingServerImpl.java b/core/java/android/util/imetracing/ImeTracingServerImpl.java new file mode 100644 index 0000000000000..350cf5721148a --- /dev/null +++ b/core/java/android/util/imetracing/ImeTracingServerImpl.java @@ -0,0 +1,154 @@ +/* + * 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.os.Build.IS_USER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_H; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.MAGIC_NUMBER_L; + +import android.os.RemoteException; +import android.os.ServiceManager.ServiceNotFoundException; +import android.os.ShellCommand; +import android.util.Log; +import android.util.proto.ProtoOutputStream; + +import com.android.internal.annotations.GuardedBy; +import com.android.internal.util.TraceBuffer; + +import java.io.File; +import java.io.IOException; +import java.io.PrintWriter; + +/** + * @hide + */ +class ImeTracingServerImpl extends ImeTracing { + private static final String TRACE_FILENAME = "/data/misc/wmtrace/ime_trace.pb"; + private static final int BUFFER_CAPACITY = 4096 * 1024; + + // Needed for winscope to auto-detect the dump type. Explained further in + // core.proto.android.view.inputmethod.inputmethodeditortrace.proto + private static final long MAGIC_NUMBER_VALUE = ((long) MAGIC_NUMBER_H << 32) | MAGIC_NUMBER_L; + + private final TraceBuffer mBuffer; + private final File mTraceFile; + private final Object mEnabledLock = new Object(); + + ImeTracingServerImpl() throws ServiceNotFoundException { + mBuffer = new TraceBuffer<>(BUFFER_CAPACITY); + mTraceFile = new File(TRACE_FILENAME); + } + + /** + * The provided dump is added to the current dump buffer {@link ImeTracingServerImpl#mBuffer}. + * + * @param proto dump to be added to the buffer + */ + @Override + public void addToBuffer(ProtoOutputStream proto) { + if (isAvailable() && isEnabled()) { + mBuffer.add(proto); + } + } + + /** + * Responds to a shell command of the format "adb shell cmd input_method ime tracing " + * + * @param shell The shell command to process + * @return {@code 0} if the command was valid and successfully processed, {@code -1} otherwise + */ + @Override + public int onShellCommand(ShellCommand shell) { + PrintWriter pw = shell.getOutPrintWriter(); + String cmd = shell.getNextArgRequired(); + switch (cmd) { + case "start": + startTrace(pw); + return 0; + case "stop": + stopTrace(pw); + return 0; + default: + pw.println("Unknown command: " + cmd); + pw.println("Input method trace options:"); + pw.println(" start: Start tracing"); + pw.println(" stop: Stop tracing"); + return -1; + } + } + + @Override + public void triggerDump() { + if (isAvailable() && isEnabled()) { + try { + mService.startProtoDump(null); + } catch (RemoteException e) { + Log.e(TAG, "Exception while triggering proto dump", e); + } + } + } + + private void writeTraceToFileLocked() { + try { + ProtoOutputStream proto = new ProtoOutputStream(); + proto.write(MAGIC_NUMBER, MAGIC_NUMBER_VALUE); + mBuffer.writeTraceToFile(mTraceFile, proto); + } catch (IOException e) { + Log.e(TAG, "Unable to write buffer to file", e); + } + } + + @GuardedBy("mEnabledLock") + private void startTrace(PrintWriter pw) { + if (IS_USER) { + Log.w(TAG, "Warn: Tracing is not supported on user builds."); + return; + } + + synchronized (mEnabledLock) { + if (isAvailable() && isEnabled()) { + Log.w(TAG, "Warn: Tracing is already started."); + return; + } + + pw.println("Starting tracing to " + mTraceFile + "."); + sEnabled = true; + mBuffer.resetBuffer(); + } + } + + @GuardedBy("mEnabledLock") + private void stopTrace(PrintWriter pw) { + if (IS_USER) { + Log.w(TAG, "Warn: Tracing is not supported on user builds."); + return; + } + + synchronized (mEnabledLock) { + if (!isAvailable() || !isEnabled()) { + Log.w(TAG, "Warn: Tracing is not available or not started."); + return; + } + + pw.println("Stopping tracing and writing traces to " + mTraceFile + "."); + sEnabled = false; + writeTraceToFileLocked(); + mBuffer.resetBuffer(); + } + } +} diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index 92772c1d7a440..efc0bd2785f40 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -16,16 +16,23 @@ package android.view; +import static android.view.ImeFocusControllerProto.HAS_IME_FOCUS; +import static android.view.ImeFocusControllerProto.NEXT_SERVED_VIEW; +import static android.view.ImeFocusControllerProto.SERVED_VIEW; + import android.annotation.AnyThread; import android.annotation.NonNull; import android.annotation.UiThread; import android.util.Log; +import android.util.proto.ProtoOutputStream; import android.view.inputmethod.InputMethodManager; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; +import java.util.Objects; + /** * Responsible for IME focus handling inside {@link ViewRootImpl}. * @hide @@ -280,4 +287,12 @@ public final class ImeFocusController { boolean hasImeFocus() { return mHasImeFocus; } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(HAS_IME_FOCUS, mHasImeFocus); + proto.write(SERVED_VIEW, Objects.toString(mServedView)); + proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView)); + proto.end(token); + } } diff --git a/core/java/android/view/ImeInsetsSourceConsumer.java b/core/java/android/view/ImeInsetsSourceConsumer.java index 82f60366a8146..dd1a19458e1d5 100644 --- a/core/java/android/view/ImeInsetsSourceConsumer.java +++ b/core/java/android/view/ImeInsetsSourceConsumer.java @@ -16,6 +16,9 @@ package android.view; +import static android.view.ImeInsetsSourceConsumerProto.FOCUSED_EDITOR; +import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER; +import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL; import static android.view.InsetsController.AnimationType; import static android.view.InsetsState.ITYPE_IME; @@ -24,6 +27,7 @@ import android.inputmethodservice.InputMethodService; import android.os.IBinder; import android.os.Parcel; import android.text.TextUtils; +import android.util.proto.ProtoOutputStream; import android.view.SurfaceControl.Transaction; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -111,7 +115,6 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { public @ShowResult int requestShow(boolean fromIme) { // TODO: ResultReceiver for IME. // TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag. - if (getControl() == null) { // If control is null, schedule to show IME when control is available. mIsRequestedVisibleAwaitingControl = true; @@ -227,6 +230,17 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer { return Arrays.equals(parcel1.createByteArray(), parcel2.createByteArray()); } + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + super.dumpDebug(proto, INSETS_SOURCE_CONSUMER); + if (mFocusedEditor != null) { + mFocusedEditor.dumpDebug(proto, FOCUSED_EDITOR); + } + proto.write(IS_REQUESTED_VISIBLE_AWAITING_CONTROL, mIsRequestedVisibleAwaitingControl); + proto.end(token); + } + private InputMethodManager getImm() { return mController.getHost().getInputMethodManager(); } diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 6ffd892e43513..71899fab554c7 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -17,6 +17,14 @@ package android.view; import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; +import static android.view.InsetsAnimationControlImplProto.CURRENT_ALPHA; +import static android.view.InsetsAnimationControlImplProto.IS_CANCELLED; +import static android.view.InsetsAnimationControlImplProto.IS_FINISHED; +import static android.view.InsetsAnimationControlImplProto.PENDING_ALPHA; +import static android.view.InsetsAnimationControlImplProto.PENDING_FRACTION; +import static android.view.InsetsAnimationControlImplProto.PENDING_INSETS; +import static android.view.InsetsAnimationControlImplProto.SHOWN_ON_FINISH; +import static android.view.InsetsAnimationControlImplProto.TMP_MATRIX; import static android.view.InsetsController.ANIMATION_TYPE_SHOW; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; @@ -38,6 +46,8 @@ import android.util.Log; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.SparseSetArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsSide; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -48,6 +58,7 @@ import android.view.animation.Interpolator; import com.android.internal.annotations.VisibleForTesting; import java.util.ArrayList; +import java.util.Objects; /** * Implements {@link WindowInsetsAnimationController} @@ -122,6 +133,10 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll mAnimationType = animationType; mController.startAnimation(this, listener, types, mAnimation, new Bounds(mHiddenInsets, mShownInsets)); + + if ((mTypes & WindowInsets.Type.ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } } private boolean calculatePerceptible(Insets currentInsets, float currentAlpha) { @@ -285,6 +300,20 @@ public class InsetsAnimationControlImpl implements WindowInsetsAnimationControll return mAnimation; } + @Override + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(IS_CANCELLED, mCancelled); + proto.write(IS_FINISHED, mFinished); + proto.write(TMP_MATRIX, Objects.toString(mTmpMatrix)); + proto.write(PENDING_INSETS, Objects.toString(mPendingInsets)); + proto.write(PENDING_FRACTION, mPendingFraction); + proto.write(SHOWN_ON_FINISH, mShownOnFinish); + proto.write(CURRENT_ALPHA, mCurrentAlpha); + proto.write(PENDING_ALPHA, mPendingAlpha); + proto.end(token); + } + WindowInsetsAnimationControlListener getListener() { return mListener; } diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index 0711c3e166d83..0275b521a2a27 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -16,6 +16,7 @@ package android.view; +import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets.Type.InsetsType; @@ -53,4 +54,14 @@ public interface InsetsAnimationControlRunner { * @return The animation type this runner is running. */ @AnimationType int getAnimationType(); + + /** + * + * Export the state of classes that implement this interface into a protocol buffer + * output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of the implementation class + */ + void dumpDebug(ProtoOutputStream proto, long fieldId); } diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index 123604489da49..cc3cd278b2678 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -25,6 +25,7 @@ import android.os.Handler; import android.os.Trace; import android.util.Log; import android.util.SparseArray; +import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; @@ -120,6 +121,12 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } } + @Override + @UiThread + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + mControl.dumpDebug(proto, fieldId); + } + @Override @UiThread public int getTypes() { diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 92eade3affaa5..61209ed566040 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsControllerProto.CONTROL; +import static android.view.InsetsControllerProto.STATE; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.toInternalType; @@ -41,6 +43,8 @@ import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsSourceConsumer.ShowResult; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; @@ -298,6 +302,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void onReady(WindowInsetsAnimationController controller, int types) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } + mController = controller; if (DEBUG) Log.d(TAG, "default animation onReady types: " + types); @@ -812,6 +820,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @VisibleForTesting public void show(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } // Handle pending request ready in case there was one set. if (fromIme && mPendingImeControlRequest != null) { PendingControlRequest pendingRequest = mPendingImeControlRequest; @@ -860,6 +871,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } void hide(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } int typesReady = 0; final ArraySet internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { @@ -894,6 +908,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.onCancelled(null); return; } + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types), @@ -1292,6 +1309,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation private void hideDirectly( @InsetsType int types, boolean animationFinished, @AnimationType int animationType) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } final ArraySet internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).hide(animationFinished, animationType); @@ -1299,6 +1319,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void showDirectly(@InsetsType int types) { + if ((types & ime()) != 0) { + ImeTracing.getInstance().triggerDump(); + } final ArraySet internalTypes = InsetsState.toInternalType(types); for (int i = internalTypes.size() - 1; i >= 0; i--) { getSourceConsumer(internalTypes.valueAt(i)).show(false /* fromIme */); @@ -1318,6 +1341,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation mState.dump(prefix + " ", pw); } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + mState.dumpDebug(proto, STATE); + for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { + InsetsAnimationControlRunner runner = mRunningAnimations.get(i).runner; + runner.dumpDebug(proto, CONTROL); + } + proto.end(token); + } + @VisibleForTesting @Override public void startAnimation(InsetsAnimationControlImpl controller, diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java index dbf75705c073f..41cc8459a266f 100644 --- a/core/java/android/view/InsetsSource.java +++ b/core/java/android/view/InsetsSource.java @@ -16,6 +16,10 @@ package android.view; +import static android.view.InsetsSourceProto.FRAME; +import static android.view.InsetsSourceProto.TYPE; +import static android.view.InsetsSourceProto.VISIBLE; +import static android.view.InsetsSourceProto.VISIBLE_FRAME; import static android.view.InsetsState.ITYPE_CAPTION_BAR; import static android.view.InsetsState.ITYPE_IME; @@ -25,6 +29,7 @@ import android.graphics.Insets; import android.graphics.Rect; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; @@ -183,6 +188,17 @@ public class InsetsSource implements Parcelable { return false; } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(TYPE, InsetsState.typeToString(mType)); + mFrame.dumpDebug(proto, FRAME); + if (mVisibleFrame != null) { + mVisibleFrame.dumpDebug(proto, VISIBLE_FRAME); + } + proto.write(VISIBLE, mVisible); + proto.end(token); + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("InsetsSource type="); pw.print(InsetsState.typeToString(mType)); diff --git a/core/java/android/view/InsetsSourceConsumer.java b/core/java/android/view/InsetsSourceConsumer.java index ba40459692f79..d7ceaf792198d 100644 --- a/core/java/android/view/InsetsSourceConsumer.java +++ b/core/java/android/view/InsetsSourceConsumer.java @@ -19,6 +19,13 @@ package android.view; import static android.view.InsetsController.ANIMATION_TYPE_NONE; import static android.view.InsetsController.AnimationType; import static android.view.InsetsController.DEBUG; +import static android.view.InsetsSourceConsumerProto.HAS_WINDOW_FOCUS; +import static android.view.InsetsSourceConsumerProto.INTERNAL_INSETS_TYPE; +import static android.view.InsetsSourceConsumerProto.IS_REQUESTED_VISIBLE; +import static android.view.InsetsSourceConsumerProto.PENDING_FRAME; +import static android.view.InsetsSourceConsumerProto.PENDING_VISIBLE_FRAME; +import static android.view.InsetsSourceConsumerProto.SOURCE_CONTROL; +import static android.view.InsetsState.ITYPE_IME; import static android.view.InsetsState.getDefaultVisibility; import static android.view.InsetsState.toPublicType; @@ -28,6 +35,8 @@ import android.annotation.IntDef; import android.annotation.Nullable; import android.graphics.Rect; import android.util.Log; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import android.view.SurfaceControl.Transaction; import android.view.WindowInsets.Type.InsetsType; @@ -319,6 +328,9 @@ public class InsetsSourceConsumer { @VisibleForTesting(visibility = PACKAGE) public boolean notifyAnimationFinished() { + if (mType == ITYPE_IME) { + ImeTracing.getInstance().triggerDump(); + } if (mPendingFrame != null) { InsetsSource source = mState.getSource(mType); source.setFrame(mPendingFrame); @@ -360,4 +372,21 @@ public class InsetsSourceConsumer { t.apply(); onPerceptible(mRequestedVisible); } + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(INTERNAL_INSETS_TYPE, InsetsState.typeToString(mType)); + proto.write(HAS_WINDOW_FOCUS, mHasWindowFocus); + proto.write(IS_REQUESTED_VISIBLE, mRequestedVisible); + if (mSourceControl != null) { + mSourceControl.dumpDebug(proto, SOURCE_CONTROL); + } + if (mPendingFrame != null) { + mPendingFrame.dumpDebug(proto, PENDING_FRAME); + } + if (mPendingVisibleFrame != null) { + mPendingVisibleFrame.dumpDebug(proto, PENDING_VISIBLE_FRAME); + } + proto.end(token); + } } diff --git a/core/java/android/view/InsetsSourceControl.java b/core/java/android/view/InsetsSourceControl.java index 51b49214387a0..b45bd3869f64a 100644 --- a/core/java/android/view/InsetsSourceControl.java +++ b/core/java/android/view/InsetsSourceControl.java @@ -16,10 +16,17 @@ package android.view; +import static android.graphics.PointProto.X; +import static android.graphics.PointProto.Y; +import static android.view.InsetsSourceControlProto.LEASH; +import static android.view.InsetsSourceControlProto.POSITION; +import static android.view.InsetsSourceControlProto.TYPE; + import android.annotation.Nullable; import android.graphics.Point; import android.os.Parcel; import android.os.Parcelable; +import android.util.proto.ProtoOutputStream; import android.view.InsetsState.InternalInsetsType; import java.io.PrintWriter; @@ -120,4 +127,19 @@ public class InsetsSourceControl implements Parcelable { return new InsetsSourceControl[size]; } }; + + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(TYPE, InsetsState.typeToString(mType)); + + final long surfaceToken = proto.start(POSITION); + proto.write(X, mSurfacePosition.x); + proto.write(Y, mSurfacePosition.y); + proto.end(surfaceToken); + + if (mLeash != null) { + mLeash.dumpDebug(proto, LEASH); + } + proto.end(token); + } } diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java index c5d0a10bb108c..eabb71851303f 100644 --- a/core/java/android/view/InsetsState.java +++ b/core/java/android/view/InsetsState.java @@ -16,6 +16,8 @@ package android.view; +import static android.view.InsetsStateProto.DISPLAY_FRAME; +import static android.view.InsetsStateProto.SOURCES; import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_STABLE; import static android.view.WindowInsets.Type.MANDATORY_SYSTEM_GESTURES; import static android.view.WindowInsets.Type.SYSTEM_GESTURES; @@ -41,6 +43,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.util.ArraySet; import android.util.SparseIntArray; +import android.util.proto.ProtoOutputStream; import android.view.WindowInsets.Type; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -545,6 +548,16 @@ public class InsetsState implements Parcelable { } } + void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + InsetsSource source = mSources[ITYPE_IME]; + if (source != null) { + source.dumpDebug(proto, SOURCES); + } + mDisplayFrame.dumpDebug(proto, DISPLAY_FRAME); + proto.end(token); + } + public static String typeToString(@InternalInsetsType int type) { switch (type) { case ITYPE_STATUS_BAR: diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 4176e887c0e7c..027608fc60aad 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -33,6 +33,23 @@ import static android.view.View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR; import static android.view.View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR; import static android.view.View.SYSTEM_UI_FLAG_LOW_PROFILE; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; +import static android.view.ViewRootImplProto.ADDED; +import static android.view.ViewRootImplProto.APP_VISIBLE; +import static android.view.ViewRootImplProto.CUR_SCROLL_Y; +import static android.view.ViewRootImplProto.DISPLAY_ID; +import static android.view.ViewRootImplProto.HEIGHT; +import static android.view.ViewRootImplProto.IS_ANIMATING; +import static android.view.ViewRootImplProto.IS_DRAWING; +import static android.view.ViewRootImplProto.LAST_WINDOW_INSETS; +import static android.view.ViewRootImplProto.PENDING_DISPLAY_CUTOUT; +import static android.view.ViewRootImplProto.REMOVED; +import static android.view.ViewRootImplProto.SCROLL_Y; +import static android.view.ViewRootImplProto.SOFT_INPUT_MODE; +import static android.view.ViewRootImplProto.VIEW; +import static android.view.ViewRootImplProto.VISIBLE_RECT; +import static android.view.ViewRootImplProto.WIDTH; +import static android.view.ViewRootImplProto.WINDOW_ATTRIBUTES; +import static android.view.ViewRootImplProto.WIN_FRAME; import static android.view.WindowCallbacks.RESIZE_MODE_DOCKED_DIVIDER; import static android.view.WindowCallbacks.RESIZE_MODE_FREEFORM; import static android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS; @@ -61,6 +78,8 @@ import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; import static android.view.WindowManager.LayoutParams.TYPE_TOAST; import static android.view.WindowManager.LayoutParams.TYPE_VOLUME_OVERLAY; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_FOCUS_CONTROLLER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INSETS_CONTROLLER; import android.Manifest; import android.animation.LayoutTransition; @@ -127,6 +146,8 @@ import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; import android.util.TypedValue; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.InputDevice.InputSourceClass; import android.view.InsetsState.InternalInsetsType; import android.view.Surface.OutOfResourcesException; @@ -164,6 +185,7 @@ import android.window.ClientWindowFrames; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.os.IResultReceiver; import com.android.internal.os.SomeArgs; import com.android.internal.policy.DecorView; @@ -182,6 +204,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.LinkedList; import java.util.List; +import java.util.Objects; import java.util.Queue; import java.util.concurrent.CountDownLatch; @@ -7520,6 +7543,38 @@ public final class ViewRootImpl implements ViewParent, mView.debug(); } + /** + * Export the state of {@link ViewRootImpl} and other relevant classes into a protocol buffer + * output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of ViewRootImpl as defined in the parent message + */ + @GuardedBy("this") + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(VIEW, Objects.toString(mView)); + proto.write(DISPLAY_ID, mDisplay.getDisplayId()); + proto.write(APP_VISIBLE, mAppVisible); + proto.write(HEIGHT, mHeight); + proto.write(WIDTH, mWidth); + proto.write(IS_ANIMATING, mIsAnimating); + mVisRect.dumpDebug(proto, VISIBLE_RECT); + proto.write(IS_DRAWING, mIsDrawing); + proto.write(ADDED, mAdded); + mWinFrame.dumpDebug(proto, WIN_FRAME); + mPendingDisplayCutout.get().dumpDebug(proto, PENDING_DISPLAY_CUTOUT); + proto.write(LAST_WINDOW_INSETS, Objects.toString(mLastWindowInsets)); + proto.write(SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString(mSoftInputMode)); + proto.write(SCROLL_Y, mScrollY); + proto.write(CUR_SCROLL_Y, mCurScrollY); + proto.write(REMOVED, mRemoved); + mWindowAttributes.dumpDebug(proto, WINDOW_ATTRIBUTES); + proto.end(token); + mInsetsController.dumpDebug(proto, INSETS_CONTROLLER); + mImeFocusController.dumpDebug(proto, IME_FOCUS_CONTROLLER); + } + public void dump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) { String innerPrefix = prefix + " "; writer.println(prefix + "ViewRoot:"); @@ -9094,6 +9149,9 @@ public final class ViewRootImpl implements ViewParent, @Override public void showInsets(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.showInsets(types, fromIme); @@ -9102,6 +9160,9 @@ public final class ViewRootImpl implements ViewParent, @Override public void hideInsets(@InsetsType int types, boolean fromIme) { + if (fromIme) { + ImeTracing.getInstance().triggerDump(); + } final ViewRootImpl viewAncestor = mViewAncestor.get(); if (viewAncestor != null) { viewAncestor.hideInsets(types, fromIme); diff --git a/core/java/android/view/inputmethod/EditorInfo.java b/core/java/android/view/inputmethod/EditorInfo.java index 104bc4347c29e..7dbf693699964 100644 --- a/core/java/android/view/inputmethod/EditorInfo.java +++ b/core/java/android/view/inputmethod/EditorInfo.java @@ -17,6 +17,12 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; +import static android.view.inputmethod.EditorInfoProto.FIELD_ID; +import static android.view.inputmethod.EditorInfoProto.IME_OPTIONS; +import static android.view.inputmethod.EditorInfoProto.INPUT_TYPE; +import static android.view.inputmethod.EditorInfoProto.PACKAGE_NAME; +import static android.view.inputmethod.EditorInfoProto.PRIVATE_IME_OPTIONS; +import static android.view.inputmethod.EditorInfoProto.TARGET_INPUT_METHOD_USER_ID; import android.annotation.IntDef; import android.annotation.NonNull; @@ -32,6 +38,7 @@ import android.text.InputType; import android.text.ParcelableSpan; import android.text.TextUtils; import android.util.Printer; +import android.util.proto.ProtoOutputStream; import android.view.View; import android.view.autofill.AutofillId; @@ -794,6 +801,26 @@ public class EditorInfo implements InputType, Parcelable { } } + /** + * Export the state of {@link EditorInfo} into a protocol buffer output stream. + * + * @param proto Stream to write the state to + * @param fieldId FieldId of ViewRootImpl as defined in the parent message + * @hide + */ + public void dumpDebug(ProtoOutputStream proto, long fieldId) { + final long token = proto.start(fieldId); + proto.write(INPUT_TYPE, inputType); + proto.write(IME_OPTIONS, imeOptions); + proto.write(PRIVATE_IME_OPTIONS, privateImeOptions); + proto.write(PACKAGE_NAME, packageName); + proto.write(FIELD_ID, this.fieldId); + if (targetInputMethodUser != null) { + proto.write(TARGET_INPUT_METHOD_USER_ID, targetInputMethodUser.getIdentifier()); + } + proto.end(token); + } + /** * Write debug output of this object. */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 8adb7e59b7138..b8f04159faa90 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -18,6 +18,17 @@ package android.view.inputmethod; import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL; import static android.Manifest.permission.WRITE_SECURE_SETTINGS; +import static android.util.imetracing.ImeTracing.PROTO_ARG; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.DISPLAY_ID; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.EDITOR_INFO; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.INPUT_METHOD_MANAGER; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientSideProto.VIEW_ROOT_IMPL; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ClientsProto.CLIENT; +import static android.view.inputmethod.InputMethodManagerProto.ACTIVE; +import static android.view.inputmethod.InputMethodManagerProto.CUR_ID; +import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE; +import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITHOUT_CONNECTION; import static com.android.internal.inputmethod.StartInputReason.WINDOW_FOCUS_GAIN_REPORT_WITH_CONNECTION; @@ -62,6 +73,8 @@ import android.util.Pools.SimplePool; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.Display; import android.view.ImeFocusController; import android.view.ImeInsetsSourceConsumer; @@ -564,6 +577,7 @@ public final class InputMethodManager { @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags) { final View servedView; + ImeTracing.getInstance().triggerDump(); synchronized (mH) { mCurrentTextBoxAttribute = null; mCompletions = null; @@ -1084,6 +1098,11 @@ public final class InputMethodManager { mH.obtainMessage(MSG_UPDATE_ACTIVITY_VIEW_TO_SCREEN_MATRIX, bindSequence, 0, matrixValues).sendToTarget(); } + + @Override + public void setImeTraceEnabled(boolean enabled) { + ImeTracing.getInstance().setEnabled(enabled); + } }; final InputConnection mDummyInputConnection = new BaseInputConnection(this, false); @@ -1652,6 +1671,7 @@ public final class InputMethodManager { * {@link #RESULT_HIDDEN}. */ public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) { + ImeTracing.getInstance().triggerDump(); // Re-dispatch if there is a context mismatch. final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view); if (fallbackImm != null) { @@ -1757,6 +1777,7 @@ public final class InputMethodManager { */ public boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver) { + ImeTracing.getInstance().triggerDump(); checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); @@ -3108,6 +3129,10 @@ public final class InputMethodManager { } void doDump(FileDescriptor fd, PrintWriter fout, String[] args) { + if (processDump(fd, args)) { + return; + } + final Printer p = new PrintWriterPrinter(fout); p.println("Input method client state for " + this + ":"); @@ -3202,4 +3227,74 @@ public final class InputMethodManager { return sb.toString(); } + + /** + * Checks the args to see if a proto-based ime dump was requested and writes the client side + * ime dump to the given {@link FileDescriptor}. + * + * @return {@code true} if a proto-based ime dump was requested. + */ + private boolean processDump(final FileDescriptor fd, final String[] args) { + if (args == null) { + return false; + } + + for (String arg : args) { + if (arg.equals(PROTO_ARG)) { + final ProtoOutputStream proto = new ProtoOutputStream(fd); + dumpProto(proto); + proto.flush(); + return true; + } + } + return false; + } + + /** + * Write the proto dump for all displays associated with this client. + * + * @param proto The proto stream to which the dumps are written. + * @hide + */ + public static void dumpProto(ProtoOutputStream proto) { + for (int i = sInstanceMap.size() - 1; i >= 0; i--) { + InputMethodManager imm = sInstanceMap.valueAt(i); + imm.dumpDebug(proto); + } + } + + /** + * Write the proto dump of various client side components to the provided + * {@link ProtoOutputStream}. + * + * @param proto The proto stream to which the dumps are written. + * @hide + */ + @GuardedBy("mH") + public void dumpDebug(ProtoOutputStream proto) { + if (mCurMethod == null) { + return; + } + + final long clientDumpToken = proto.start(CLIENT); + proto.write(DISPLAY_ID, mDisplayId); + final long token = proto.start(INPUT_METHOD_MANAGER); + synchronized (mH) { + proto.write(CUR_ID, mCurId); + proto.write(FULLSCREEN_MODE, mFullscreenMode); + proto.write(ACTIVE, mActive); + proto.write(SERVED_CONNECTING, mServedConnecting); + proto.end(token); + if (mCurRootView != null) { + mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL); + } + if (mCurrentTextBoxAttribute != null) { + mCurrentTextBoxAttribute.dumpDebug(proto, EDITOR_INFO); + } + if (mImeInsetsConsumer != null) { + mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER); + } + } + proto.end(clientDumpToken); + } } diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 45090320c1922..c9443b0021332 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -33,4 +33,5 @@ oneway interface IInputMethodClient { void reportPreRendered(in EditorInfo info); void applyImeVisibility(boolean setVisible); void updateActivityViewToScreenMatrix(int bindSequence, in float[] matrixValues); + void setImeTraceEnabled(boolean enabled); } diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index a1cbd3fcae79a..5a06273bb1739 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -77,4 +77,6 @@ interface IInputMethodManager { void removeImeSurface(); /** Remove the IME surface. Requires passing the currently focused window. */ void removeImeSurfaceFromWindow(in IBinder windowToken); + void startProtoDump(in byte[] clientProtoDump); + boolean isImeTraceEnabled(); } diff --git a/core/proto/android/view/imeinsetssourceconsumer.proto b/core/proto/android/view/imeinsetssourceconsumer.proto index 680916345a311..5bee81bdc7cd5 100644 --- a/core/proto/android/view/imeinsetssourceconsumer.proto +++ b/core/proto/android/view/imeinsetssourceconsumer.proto @@ -17,6 +17,7 @@ syntax = "proto2"; import "frameworks/base/core/proto/android/view/inputmethod/editorinfo.proto"; +import "frameworks/base/core/proto/android/view/insetssourceconsumer.proto"; package android.view; @@ -26,6 +27,7 @@ option java_multiple_files = true; * Represents a {@link android.view.ImeInsetsSourceConsumer} object. */ message ImeInsetsSourceConsumerProto { - optional .android.view.inputmethod.EditorInfoProto focused_editor = 1; - optional bool is_requested_visible_awaiting_control = 2; + optional InsetsSourceConsumerProto insets_source_consumer = 1; + optional .android.view.inputmethod.EditorInfoProto focused_editor = 2; + optional bool is_requested_visible_awaiting_control = 3; } \ 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 732213966014e..f31d35b86a0c3 100644 --- a/core/proto/android/view/inputmethod/inputmethodeditortrace.proto +++ b/core/proto/android/view/inputmethod/inputmethodeditortrace.proto @@ -22,7 +22,6 @@ package android.view.inputmethod; import "frameworks/base/core/proto/android/view/inputmethod/inputmethodmanager.proto"; 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/insetssourceconsumer.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/imefocuscontroller.proto"; @@ -54,14 +53,19 @@ message InputMethodEditorProto { /* required: elapsed realtime in nanos since boot of when this entry was logged */ optional fixed64 elapsed_realtime_nanos = 1; - optional ClientSideProto client_side_dump = 2; + optional ClientsProto clients = 2; + + // this wrapper helps to simplify the dumping logic + message ClientsProto { + repeated ClientSideProto client = 1; + } /* groups together the dump from ime related client side classes */ message ClientSideProto { - optional InputMethodManagerProto input_method_manager = 1; - optional ViewRootImplProto view_root_impl = 2; - optional InsetsControllerProto insets_controller = 3; - optional InsetsSourceConsumerProto insets_source_consumer = 4; + optional int32 display_id = 1; + optional InputMethodManagerProto input_method_manager = 2; + optional ViewRootImplProto view_root_impl = 3; + optional InsetsControllerProto insets_controller = 4; optional ImeInsetsSourceConsumerProto ime_insets_source_consumer = 5; optional EditorInfoProto editor_info = 6; optional ImeFocusControllerProto ime_focus_controller = 7; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index a44fabbeb2d3e..2eccaf1434b4c 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -17,6 +17,9 @@ package com.android.server.inputmethod; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.CLIENTS; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorProto.ELAPSED_REALTIME_NANOS; +import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodEditorTraceFileProto.ENTRY; import static java.lang.annotation.RetentionPolicy.SOURCE; @@ -96,12 +99,15 @@ import android.text.style.SuggestionSpan; import android.util.ArrayMap; import android.util.ArraySet; import android.util.EventLog; +import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; import android.util.SparseArray; +import android.util.imetracing.ImeTracing; +import android.util.proto.ProtoOutputStream; import android.view.ContextThemeWrapper; import android.view.DisplayInfo; import android.view.IWindowManager; @@ -4032,6 +4038,55 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); } + /** + * Starting point for dumping the IME tracing information in proto format. + * + * @param clientProtoDump dump information from the IME client side + */ + @BinderThread + @Override + public void startProtoDump(byte[] clientProtoDump) { + if (!ImeTracing.getInstance().isAvailable() || !ImeTracing.getInstance().isEnabled()) { + return; + } + if (clientProtoDump == null && mCurClient == null) { + return; + } + + ProtoOutputStream proto = new ProtoOutputStream(); + final long token = proto.start(ENTRY); + proto.write(ELAPSED_REALTIME_NANOS, SystemClock.elapsedRealtimeNanos()); + // TODO: get server side dump + if (clientProtoDump != null) { + proto.write(CLIENTS, clientProtoDump); + } else { + IBinder client = null; + + synchronized (mMethodMap) { + if (mCurClient != null && mCurClient.client != null) { + client = mCurClient.client.asBinder(); + } + } + + if (client != null) { + try { + proto.write(CLIENTS, + TransferPipe.dumpAsync(client, ImeTracing.PROTO_ARG)); + } catch (IOException | RemoteException e) { + Log.e(TAG, "Exception while collecting client side ime dump", e); + } + } + } + proto.end(token); + ImeTracing.getInstance().addToBuffer(proto); + } + + @BinderThread + @Override + public boolean isImeTraceEnabled() { + return ImeTracing.getInstance().isEnabled(); + } + @BinderThread private void notifyUserAction(@NonNull IBinder token) { if (DEBUG) { @@ -5426,6 +5481,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub return mService.handleShellCommandSetInputMethod(this); case "reset": return mService.handleShellCommandResetInputMethod(this); + case "tracing": + int result = ImeTracing.getInstance().onShellCommand(this); + boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled(); + for (ClientState state : mService.mClients.values()) { + if (state != null) { + try { + state.client.setImeTraceEnabled(isImeTraceEnabled); + } catch (RemoteException e) { + Log.e(TAG, + "Error while trying to enable/disable ime " + + "trace on client window", e); + } + } + } + return result; default: getOutPrintWriter().println("Unknown command: " + imeCommand); return ShellCommandResult.FAILURE; diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index b518eb1ab6d08..a6ca25b0e6c12 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1805,5 +1805,16 @@ public final class MultiClientInputMethodManagerService { mUserDataMap.dump(fd, ipw, args); } } + + @BinderThread + @Override + public void startProtoDump(byte[] clientProtoDump) throws RemoteException { + } + + @BinderThread + @Override + public boolean isImeTraceEnabled() throws RemoteException { + return false; + } } }