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; + } } }