diff options
| author | 2022-11-14 14:36:36 +0000 | |
|---|---|---|
| committer | 2022-11-14 14:36:36 +0000 | |
| commit | fc03fbd025e747dea1dbbb652fa1dfdc415b7ab9 (patch) | |
| tree | 311f710a5147273e4d34b9f1c19d278587730dbd | |
| parent | 70453947debc9b35f6c76fd66007450f4e9ebcd2 (diff) | |
| parent | 44ff0dd9eb26d429620dddff3511a84f184986a2 (diff) | |
Merge changes from topic "revert-20437706-revert-20313761-IME tracking - hide-HZXGDCCPOZ-XOCVITMXNR"
* changes:
Revert^2 "Input latency & reliability tracking - hide flow"
Revert^2 "Input latency & reliability tracking"
44 files changed, 1189 insertions, 268 deletions
diff --git a/core/java/android/inputmethodservice/IInputMethodWrapper.java b/core/java/android/inputmethodservice/IInputMethodWrapper.java index d23fb363df1c..d55367f19a70 100644 --- a/core/java/android/inputmethodservice/IInputMethodWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodWrapper.java @@ -32,6 +32,7 @@ import android.os.ResultReceiver; import android.util.Log; import android.view.InputChannel; import android.view.MotionEvent; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; @@ -93,9 +94,10 @@ class IInputMethodWrapper extends IInputMethod.Stub final int mTargetSdkVersion; /** - * This is not {@null} only between {@link #bindInput(InputBinding)} and {@link #unbindInput()} - * so that {@link RemoteInputConnection} can query if {@link #unbindInput()} has already been - * called or not, mainly to avoid unnecessary blocking operations. + * This is not {@code null} only between {@link #bindInput(InputBinding)} and + * {@link #unbindInput()} so that {@link RemoteInputConnection} can query if + * {@link #unbindInput()} has already been called or not, mainly to avoid unnecessary + * blocking operations. * * <p>This field must be set and cleared only from the binder thread(s), where the system * guarantees that {@link #bindInput(InputBinding)}, @@ -219,18 +221,26 @@ class IInputMethodWrapper extends IInputMethod.Stub return; case DO_SHOW_SOFT_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; + final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3; if (isValid(inputMethod, target, "DO_SHOW_SOFT_INPUT")) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); inputMethod.showSoftInputWithToken( - msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1); + msg.arg1, (ResultReceiver) args.arg2, (IBinder) args.arg1, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); } args.recycle(); return; } case DO_HIDE_SOFT_INPUT: { final SomeArgs args = (SomeArgs) msg.obj; + final ImeTracker.Token statsToken = (ImeTracker.Token) args.arg3; if (isValid(inputMethod, target, "DO_HIDE_SOFT_INPUT")) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); inputMethod.hideSoftInputWithToken(msg.arg1, (ResultReceiver) args.arg2, - (IBinder) args.arg1); + (IBinder) args.arg1, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_IME_WRAPPER_DISPATCH); } args.recycle(); return; @@ -416,16 +426,20 @@ class IInputMethodWrapper extends IInputMethod.Stub @BinderThread @Override - public void showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_SHOW_SOFT_INPUT, - flags, showInputToken, resultReceiver)); + public void showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); + mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_SHOW_SOFT_INPUT, + flags, showInputToken, resultReceiver, statsToken)); } @BinderThread @Override - public void hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIOO(DO_HIDE_SOFT_INPUT, - flags, hideInputToken, resultReceiver)); + public void hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_IME_WRAPPER); + mCaller.executeOrSendMessage(mCaller.obtainMessageIOOO(DO_HIDE_SOFT_INPUT, + flags, hideInputToken, resultReceiver, statsToken)); } @BinderThread diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index d902486faced..bf4fc4a72fdc 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -124,6 +124,7 @@ import android.view.inputmethod.CursorAnchorInfo; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InlineSuggestionsRequest; import android.view.inputmethod.InlineSuggestionsResponse; import android.view.inputmethod.InputBinding; @@ -669,6 +670,10 @@ public class InputMethodService extends AbstractInputMethodService { */ private IBinder mCurHideInputToken; + /** The token tracking the current IME request or {@code null} otherwise. */ + @Nullable + private ImeTracker.Token mCurStatsToken; + final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> { onComputeInsets(mTmpInsets); if (!mViewsCreated) { @@ -870,10 +875,12 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken) { + IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { mSystemCallingHideSoftInput = true; mCurHideInputToken = hideInputToken; + mCurStatsToken = statsToken; hideSoftInput(flags, resultReceiver); + mCurStatsToken = null; mCurHideInputToken = null; mSystemCallingHideSoftInput = false; } @@ -884,6 +891,7 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { + ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_HIDE_SOFT_INPUT); if (DEBUG) Log.v(TAG, "hideSoftInput()"); if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R && !mSystemCallingHideSoftInput) { @@ -918,12 +926,17 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder showInputToken) { + IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { mSystemCallingShowSoftInput = true; mCurShowInputToken = showInputToken; - showSoftInput(flags, resultReceiver); - mCurShowInputToken = null; - mSystemCallingShowSoftInput = false; + mCurStatsToken = statsToken; + try { + showSoftInput(flags, resultReceiver); + } finally { + mCurStatsToken = null; + mCurShowInputToken = null; + mSystemCallingShowSoftInput = false; + } } /** @@ -932,6 +945,7 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { + ImeTracker.get().onProgress(mCurStatsToken, ImeTracker.PHASE_IME_SHOW_SOFT_INPUT); if (DEBUG) Log.v(TAG, "showSoftInput()"); // TODO(b/148086656): Disallow IME developers from calling InputMethodImpl methods. if (getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.R @@ -947,7 +961,12 @@ public class InputMethodService extends AbstractInputMethodService { null /* icProto */); final boolean wasVisible = isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { + ImeTracker.get().onProgress(mCurStatsToken, + ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); showWindow(true); + } else { + ImeTracker.get().onFailed(mCurStatsToken, + ImeTracker.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE); } setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); @@ -2923,8 +2942,10 @@ public class InputMethodService extends AbstractInputMethodService { ImeTracing.getInstance().triggerServiceDump( "InputMethodService#applyVisibilityInInsetsConsumerIfNecessary", mDumper, null /* icProto */); + ImeTracker.get().onProgress(mCurStatsToken, + ImeTracker.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER); mPrivOps.applyImeVisibilityAsync(setVisible - ? mCurShowInputToken : mCurHideInputToken, setVisible); + ? mCurShowInputToken : mCurHideInputToken, setVisible, mCurStatsToken); } private void finishViews(boolean finishingInput) { diff --git a/core/java/android/view/IDisplayWindowInsetsController.aidl b/core/java/android/view/IDisplayWindowInsetsController.aidl index 0769f1209a2b..91270d4160f5 100644 --- a/core/java/android/view/IDisplayWindowInsetsController.aidl +++ b/core/java/android/view/IDisplayWindowInsetsController.aidl @@ -19,6 +19,7 @@ package android.view; import android.content.ComponentName; import android.view.InsetsSourceControl; import android.view.InsetsState; +import android.view.inputmethod.ImeTracker; /** * Singular controller of insets to use when there isn't another obvious controller available. @@ -48,10 +49,10 @@ oneway interface IDisplayWindowInsetsController { /** * @see IWindow#showInsets */ - void showInsets(int types, boolean fromIme); + void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); /** * @see IWindow#hideInsets */ - void hideInsets(int types, boolean fromIme); + void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); } diff --git a/core/java/android/view/IWindow.aidl b/core/java/android/view/IWindow.aidl index a8564740c0c8..8e16f24b154f 100644 --- a/core/java/android/view/IWindow.aidl +++ b/core/java/android/view/IWindow.aidl @@ -29,6 +29,7 @@ import android.view.InsetsState; import android.view.IScrollCaptureResponseListener; import android.view.KeyEvent; import android.view.MotionEvent; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import com.android.internal.os.IResultReceiver; @@ -68,16 +69,18 @@ oneway interface IWindow { * * @param types internal insets types (WindowInsets.Type.InsetsType) to show * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME show request or {@code null} otherwise. */ - void showInsets(int types, boolean fromIme); + void showInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); /** * Called when a set of insets source window should be hidden by policy. * * @param types internal insets types (WindowInsets.Type.InsetsType) to hide * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. */ - void hideInsets(int types, boolean fromIme); + void hideInsets(int types, boolean fromIme, in @nullable ImeTracker.Token statsToken); void moved(int newX, int newY); void dispatchAppVisibility(boolean visible); diff --git a/core/java/android/view/InsetsAnimationControlImpl.java b/core/java/android/view/InsetsAnimationControlImpl.java index 27b4d8744452..e775969238a0 100644 --- a/core/java/android/view/InsetsAnimationControlImpl.java +++ b/core/java/android/view/InsetsAnimationControlImpl.java @@ -58,6 +58,7 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.WindowManager.LayoutParams; import android.view.animation.Interpolator; +import android.view.inputmethod.ImeTracker; import com.android.internal.annotations.VisibleForTesting; @@ -68,7 +69,7 @@ import java.util.Objects; * Implements {@link WindowInsetsAnimationController} * @hide */ -@VisibleForTesting +@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) public class InsetsAnimationControlImpl implements InternalInsetsAnimationController, InsetsAnimationControlRunner { @@ -96,6 +97,8 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro /** @see WindowInsetsAnimationController#hasZeroInsetsIme */ private final boolean mHasZeroInsetsIme; private final CompatibilityInfo.Translator mTranslator; + @Nullable + private final ImeTracker.Token mStatsToken; private Insets mCurrentInsets; private Insets mPendingInsets; private float mPendingFraction; @@ -114,7 +117,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - CompatibilityInfo.Translator translator) { + CompatibilityInfo.Translator translator, @Nullable ImeTracker.Token statsToken) { mControls = controls; mListener = listener; mTypes = types; @@ -152,6 +155,7 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro mAnimationType = animationType; mLayoutInsetsDuringAnimation = layoutInsetsDuringAnimation; mTranslator = translator; + mStatsToken = statsToken; mController.startAnimation(this, listener, types, mAnimation, new Bounds(mHiddenInsets, mShownInsets)); } @@ -228,6 +232,11 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } @Override + public ImeTracker.Token getStatsToken() { + return mStatsToken; + } + + @Override public void setInsetsAndAlpha(Insets insets, float alpha, float fraction) { setInsetsAndAlpha(insets, alpha, fraction, false /* allowWhenFinished */); } @@ -253,10 +262,10 @@ public class InsetsAnimationControlImpl implements InternalInsetsAnimationContro } } - @VisibleForTesting /** * @return Whether the finish callback of this animation should be invoked. */ + @VisibleForTesting public boolean applyChangeInsets(@Nullable InsetsState outState) { if (mCancelled) { if (DEBUG) Log.d(TAG, "applyChangeInsets canceled"); diff --git a/core/java/android/view/InsetsAnimationControlRunner.java b/core/java/android/view/InsetsAnimationControlRunner.java index 291351e0b9d3..cf40e7e4d308 100644 --- a/core/java/android/view/InsetsAnimationControlRunner.java +++ b/core/java/android/view/InsetsAnimationControlRunner.java @@ -16,10 +16,12 @@ package android.view; +import android.annotation.Nullable; import android.util.SparseArray; import android.util.proto.ProtoOutputStream; import android.view.InsetsController.AnimationType; import android.view.WindowInsets.Type.InsetsType; +import android.view.inputmethod.ImeTracker; /** * Interface representing a runner for an insets animation. @@ -74,6 +76,12 @@ public interface InsetsAnimationControlRunner { @AnimationType int getAnimationType(); /** + * @return The token tracking the current IME request or {@code null} otherwise. + */ + @Nullable + ImeTracker.Token getStatsToken(); + + /** * * Export the state of classes that implement this interface into a protocol buffer * output stream. diff --git a/core/java/android/view/InsetsAnimationThreadControlRunner.java b/core/java/android/view/InsetsAnimationThreadControlRunner.java index fc97541bd34d..f7b9aa26ad96 100644 --- a/core/java/android/view/InsetsAnimationThreadControlRunner.java +++ b/core/java/android/view/InsetsAnimationThreadControlRunner.java @@ -34,6 +34,7 @@ import android.view.SyncRtSurfaceTransactionApplier.SurfaceParams; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.animation.Interpolator; +import android.view.inputmethod.ImeTracker; /** * Insets animation runner that uses {@link InsetsAnimationThread} to run the animation off from the @@ -112,12 +113,13 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro @InsetsType int types, InsetsAnimationControlCallbacks controller, long durationMs, Interpolator interpolator, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - CompatibilityInfo.Translator translator, Handler mainThreadHandler) { + CompatibilityInfo.Translator translator, Handler mainThreadHandler, + @Nullable ImeTracker.Token statsToken) { mMainThreadHandler = mainThreadHandler; mOuterCallbacks = controller; mControl = new InsetsAnimationControlImpl(controls, frame, state, listener, types, mCallbacks, durationMs, interpolator, animationType, layoutInsetsDuringAnimation, - translator); + translator, statsToken); InsetsAnimationThread.getHandler().post(() -> { if (mControl.isCancelled()) { return; @@ -141,6 +143,11 @@ public class InsetsAnimationThreadControlRunner implements InsetsAnimationContro } @Override + public ImeTracker.Token getStatsToken() { + return mControl.getStatsToken(); + } + + @Override @UiThread public int getTypes() { return mControl.getTypes(); diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java index 35838a39057e..fbd82266246b 100644 --- a/core/java/android/view/InsetsController.java +++ b/core/java/android/view/InsetsController.java @@ -44,6 +44,7 @@ import android.os.CancellationSignal; import android.os.Handler; import android.os.IBinder; import android.os.Trace; +import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; import android.util.Pair; @@ -60,6 +61,7 @@ import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.animation.Interpolator; import android.view.animation.LinearInterpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.VisibleForTesting; @@ -929,10 +931,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation hideTypes[0] &= ~animatingTypes; if (showTypes[0] != 0) { - applyAnimation(showTypes[0], true /* show */, false /* fromIme */); + applyAnimation(showTypes[0], true /* show */, false /* fromIme */, + null /* statsToken */); } if (hideTypes[0] != 0) { - applyAnimation(hideTypes[0], false /* show */, false /* fromIme */); + applyAnimation(hideTypes[0], false /* show */, false /* fromIme */, + null /* statsToken */); } if (mControllableTypes != controllableTypes) { @@ -948,11 +952,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation @Override public void show(@InsetsType int types) { - show(types, false /* fromIme */); + show(types, false /* fromIme */, null /* statsToken */); } - @VisibleForTesting - public void show(@InsetsType int types, boolean fromIme) { + @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE) + public void show(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if ((types & ime()) != 0) { Log.d(TAG, "show(ime(), fromIme=" + fromIme + ")"); } @@ -979,7 +984,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation true /* fromIme */, pendingRequest.durationMs, pendingRequest.interpolator, pendingRequest.animationType, pendingRequest.layoutInsetsDuringAnimation, - pendingRequest.useInsetsAnimationThread); + pendingRequest.useInsetsAnimationThread, statsToken); return; } @@ -990,8 +995,9 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if ((types & type) == 0) { continue; } - final @AnimationType int animationType = getAnimationType(type); + @AnimationType final int animationType = getAnimationType(type); final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0; + final boolean isImeAnimation = type == ime(); if (requestedVisible && animationType == ANIMATION_TYPE_NONE || animationType == ANIMATION_TYPE_SHOW) { // no-op: already shown or animating in (because window visibility is @@ -999,25 +1005,36 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if (DEBUG) Log.d(TAG, String.format( "show ignored for type: %d animType: %d requestedVisible: %s", type, animationType, requestedVisible)); + if (isImeAnimation) { + ImeTracker.get().onCancelled(statsToken, + ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); + } continue; } if (fromIme && animationType == ANIMATION_TYPE_USER) { // App is already controlling the IME, don't cancel it. + if (isImeAnimation) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); + } continue; } + if (isImeAnimation) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); + } typesReady |= type; } if (DEBUG) Log.d(TAG, "show typesReady: " + typesReady); - applyAnimation(typesReady, true /* show */, fromIme); + applyAnimation(typesReady, true /* show */, fromIme, statsToken); } @Override public void hide(@InsetsType int types) { - hide(types, false /* fromIme */); + hide(types, false /* fromIme */, null /* statsToken */); } @VisibleForTesting - public void hide(@InsetsType int types, boolean fromIme) { + public void hide(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if (fromIme) { ImeTracing.getInstance().triggerClientDump("InsetsController#hide", mHost.getInputMethodManager(), null /* icProto */); @@ -1030,16 +1047,25 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation if ((types & type) == 0) { continue; } - final @AnimationType int animationType = getAnimationType(type); + @AnimationType final int animationType = getAnimationType(type); final boolean requestedVisible = (type & mRequestedVisibleTypes) != 0; + final boolean isImeAnimation = type == ime(); if (!requestedVisible && animationType == ANIMATION_TYPE_NONE || animationType == ANIMATION_TYPE_HIDE) { - // no-op: already hidden or animating out. + // no-op: already hidden or animating out (because window visibility is + // applied before starting animation). + if (isImeAnimation) { + ImeTracker.get().onCancelled(statsToken, + ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); + } continue; } + if (isImeAnimation) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_APPLY_ANIMATION); + } typesReady |= type; } - applyAnimation(typesReady, false /* show */, fromIme /* fromIme */); + applyAnimation(typesReady, false /* show */, fromIme, statsToken); } @Override @@ -1068,7 +1094,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation controlAnimationUnchecked(types, cancellationSignal, listener, mFrame, fromIme, durationMs, interpolator, animationType, getLayoutInsetsDuringAnimationMode(types), - false /* useInsetsAnimationThread */); + false /* useInsetsAnimationThread */, null /* statsToken */); } private void controlAnimationUnchecked(@InsetsType int types, @@ -1077,7 +1103,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation long durationMs, Interpolator interpolator, @AnimationType int animationType, @LayoutInsetsDuringAnimation int layoutInsetsDuringAnimation, - boolean useInsetsAnimationThread) { + boolean useInsetsAnimationThread, @Nullable ImeTracker.Token statsToken) { + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_CONTROL_ANIMATION); if ((types & mTypesBeingCancelled) != 0) { throw new IllegalStateException("Cannot start a new insets animation of " + Type.toString(types) @@ -1152,14 +1179,16 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation ? new InsetsAnimationThreadControlRunner(controls, frame, mState, listener, typesReady, this, durationMs, interpolator, animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), - mHost.getHandler()) + mHost.getHandler(), statsToken) : new InsetsAnimationControlImpl(controls, frame, mState, listener, typesReady, this, durationMs, interpolator, - animationType, layoutInsetsDuringAnimation, mHost.getTranslator()); + animationType, layoutInsetsDuringAnimation, mHost.getTranslator(), + statsToken); if ((typesReady & WindowInsets.Type.ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsAnimationControlImpl", mHost.getInputMethodManager(), null /* icProto */); } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_ANIMATION_RUNNING); mRunningAnimations.add(new RunningAnimation(runner, animationType)); if (DEBUG) Log.d(TAG, "Animation added to runner. useInsetsAnimationThread: " + useInsetsAnimationThread); @@ -1311,11 +1340,18 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation // requested visibility. return; } + final ImeTracker.Token statsToken = runner.getStatsToken(); if (shown) { + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_SHOW); showDirectly(runner.getTypes(), true /* fromIme */); + ImeTracker.get().onShown(statsToken); } else { + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_ANIMATION_FINISHED_HIDE); hideDirectly(runner.getTypes(), true /* animationFinished */, runner.getAnimationType(), true /* fromIme */); + ImeTracker.get().onHidden(statsToken); } } @@ -1339,10 +1375,19 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } private void cancelAnimation(InsetsAnimationControlRunner control, boolean invokeCallback) { - if (DEBUG) Log.d(TAG, String.format("cancelAnimation of types: %d, animType: %d, host: %s", - control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle())); if (invokeCallback) { + ImeTracker.get().onCancelled(control.getStatsToken(), + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); control.cancel(); + } else { + // Succeeds if invokeCallback is false (i.e. when called from notifyFinished). + ImeTracker.get().onProgress(control.getStatsToken(), + ImeTracker.PHASE_CLIENT_ANIMATION_CANCEL); + } + if (DEBUG) { + Log.d(TAG, TextUtils.formatSimple( + "cancelAnimation of types: %d, animType: %d, host: %s", + control.getTypes(), control.getAnimationType(), mHost.getRootViewTitle())); } boolean stateChanged = false; for (int i = mRunningAnimations.size() - 1; i >= 0; i--) { @@ -1452,7 +1497,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation } @VisibleForTesting - public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme) { + public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { // TODO(b/166736352): We should only skip the animation of specific types, not all types. boolean skipAnim = false; if ((types & ime()) != 0) { @@ -1465,12 +1511,12 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation && consumer.hasViewFocusWhenWindowFocusGain(); } } - applyAnimation(types, show, fromIme, skipAnim); + applyAnimation(types, show, fromIme, skipAnim, statsToken); } @VisibleForTesting public void applyAnimation(@InsetsType final int types, boolean show, boolean fromIme, - boolean skipAnim) { + boolean skipAnim, @Nullable ImeTracker.Token statsToken) { if (types == 0) { // nothing to animate. if (DEBUG) Log.d(TAG, "applyAnimation, nothing to animate"); @@ -1490,12 +1536,11 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation listener.getDurationMs(), listener.getInsetsInterpolator(), show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - !hasAnimationCallbacks /* useInsetsAnimationThread */); + !hasAnimationCallbacks /* useInsetsAnimationThread */, statsToken); } - private void hideDirectly( - @InsetsType int types, boolean animationFinished, @AnimationType int animationType, - boolean fromIme) { + private void hideDirectly(@InsetsType int types, boolean animationFinished, + @AnimationType int animationType, boolean fromIme) { if ((types & ime()) != 0) { ImeTracing.getInstance().triggerClientDump("InsetsController#hideDirectly", mHost.getInputMethodManager(), null /* icProto */); diff --git a/core/java/android/view/InsetsResizeAnimationRunner.java b/core/java/android/view/InsetsResizeAnimationRunner.java index edcfc95fe4e4..778c677c58bd 100644 --- a/core/java/android/view/InsetsResizeAnimationRunner.java +++ b/core/java/android/view/InsetsResizeAnimationRunner.java @@ -37,6 +37,7 @@ import android.view.InsetsState.InternalInsetsType; import android.view.WindowInsets.Type.InsetsType; import android.view.WindowInsetsAnimation.Bounds; import android.view.animation.Interpolator; +import android.view.inputmethod.ImeTracker; /** * Runs a fake animation of resizing insets to produce insets animation callbacks. @@ -92,6 +93,12 @@ public class InsetsResizeAnimationRunner implements InsetsAnimationControlRunner } @Override + public ImeTracker.Token getStatsToken() { + // Return null as resizing the IME view is not explicitly tracked. + return null; + } + + @Override public void cancel() { if (mCancelled || mFinished) { return; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 43f09473b2d8..636023938b9d 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -192,6 +192,7 @@ import android.view.autofill.AutofillManager; import android.view.contentcapture.ContentCaptureManager; import android.view.contentcapture.ContentCaptureSession; import android.view.contentcapture.MainContentCaptureSession; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.window.ClientWindowFrames; @@ -5649,17 +5650,23 @@ public final class ViewRootImpl implements ViewParent, break; } case MSG_SHOW_INSETS: { + final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj; + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_HANDLE_SHOW_INSETS); if (mView == null) { Log.e(TAG, String.format("Calling showInsets(%d,%b) on window that no longer" + " has views.", msg.arg1, msg.arg2 == 1)); } clearLowProfileModeIfNeeded(msg.arg1, msg.arg2 == 1); - mInsetsController.show(msg.arg1, msg.arg2 == 1); + mInsetsController.show(msg.arg1, msg.arg2 == 1, statsToken); break; } case MSG_HIDE_INSETS: { - mInsetsController.hide(msg.arg1, msg.arg2 == 1); + final ImeTracker.Token statsToken = (ImeTracker.Token) msg.obj; + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_CLIENT_HANDLE_HIDE_INSETS); + mInsetsController.hide(msg.arg1, msg.arg2 == 1, statsToken); break; } case MSG_WINDOW_MOVED: @@ -8811,12 +8818,14 @@ public final class ViewRootImpl implements ViewParent, mHandler.obtainMessage(MSG_INSETS_CONTROL_CHANGED, args).sendToTarget(); } - private void showInsets(@InsetsType int types, boolean fromIme) { - mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0).sendToTarget(); + private void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { + mHandler.obtainMessage(MSG_SHOW_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget(); } - private void hideInsets(@InsetsType int types, boolean fromIme) { - mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0).sendToTarget(); + private void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { + mHandler.obtainMessage(MSG_HIDE_INSETS, types, fromIme ? 1 : 0, statsToken).sendToTarget(); } public void dispatchMoved(int newX, int newY) { @@ -10180,7 +10189,8 @@ public final class ViewRootImpl implements ViewParent, } @Override - public void showInsets(@InsetsType int types, boolean fromIme) { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (fromIme) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#showInsets", @@ -10188,13 +10198,16 @@ public final class ViewRootImpl implements ViewParent, null /* icProto */); } if (viewAncestor != null) { - viewAncestor.showInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS); + viewAncestor.showInsets(types, fromIme, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_SHOW_INSETS); } } @Override - public void hideInsets(@InsetsType int types, boolean fromIme) { - + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { final ViewRootImpl viewAncestor = mViewAncestor.get(); if (fromIme) { ImeTracing.getInstance().triggerClientDump("ViewRootImpl.W#hideInsets", @@ -10202,7 +10215,10 @@ public final class ViewRootImpl implements ViewParent, null /* icProto */); } if (viewAncestor != null) { - viewAncestor.hideInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS); + viewAncestor.hideInsets(types, fromIme, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_HIDE_INSETS); } } diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index a66c67b2e255..6eae63afcba7 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -275,15 +275,16 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver, + @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, + @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { - return service.showSoftInput( - client, windowToken, flags, lastClickToolType, resultReceiver, reason); + return service.showSoftInput(client, windowToken, statsToken, flags, lastClickToolType, + resultReceiver, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } @@ -291,14 +292,15 @@ final class IInputMethodManagerGlobalInvoker { @AnyThread static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken, - int flags, @Nullable ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + @Nullable ImeTracker.Token statsToken, int flags, + @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { final IInputMethodManager service = getService(); if (service == null) { return false; } try { - return service.hideSoftInput(client, windowToken, flags, resultReceiver, reason); + return service.hideSoftInput(client, windowToken, statsToken, flags, resultReceiver, + reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/android/view/inputmethod/ImeTracker.aidl b/core/java/android/view/inputmethod/ImeTracker.aidl new file mode 100644 index 000000000000..1988f482497d --- /dev/null +++ b/core/java/android/view/inputmethod/ImeTracker.aidl @@ -0,0 +1,19 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +parcelable ImeTracker.Token; diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java new file mode 100644 index 000000000000..f4ecdff12c7a --- /dev/null +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -0,0 +1,488 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.view.inputmethod; + +import static android.view.inputmethod.ImeTracker.Debug.originToString; +import static android.view.inputmethod.ImeTracker.Debug.phaseToString; + +import android.annotation.IntDef; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.app.ActivityThread; +import android.os.Binder; +import android.os.IBinder; +import android.os.Parcel; +import android.os.Parcelable; +import android.os.SystemProperties; +import android.util.Log; + +import com.android.internal.inputmethod.InputMethodDebug; +import com.android.internal.inputmethod.SoftInputShowHideReason; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.Field; +import java.util.Arrays; +import java.util.Map; +import java.util.Random; +import java.util.stream.Collectors; + +/** @hide */ +public interface ImeTracker { + + String TAG = "ImeTracker"; + + /** + * The origin of the IME request + * + * The name follows the format {@code PHASE_x_...} where {@code x} denotes + * where the origin is (i.e. {@code PHASE_SERVER_...} occurs in the server). + */ + @IntDef(prefix = { "ORIGIN_" }, value = { + ORIGIN_CLIENT_SHOW_SOFT_INPUT, + ORIGIN_CLIENT_HIDE_SOFT_INPUT, + ORIGIN_SERVER_START_INPUT, + ORIGIN_SERVER_HIDE_INPUT + }) + @Retention(RetentionPolicy.SOURCE) + @interface Origin {} + + /** + * The IME show request originated in the client. + */ + int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0; + + /** + * The IME hide request originated in the client. + */ + int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1; + + /** + * The IME show request originated in the server. + */ + int ORIGIN_SERVER_START_INPUT = 2; + + /** + * The IME hide request originated in the server. + */ + int ORIGIN_SERVER_HIDE_INPUT = 3; + + /** + * The current phase of the IME request. + * + * The name follows the format {@code PHASE_x_...} where {@code x} denotes + * where the phase is (i.e. {@code PHASE_SERVER_...} occurs in the server). + */ + @IntDef(prefix = { "PHASE_" }, value = { + PHASE_CLIENT_VIEW_SERVED, + PHASE_SERVER_CLIENT_KNOWN, + PHASE_SERVER_CLIENT_FOCUSED, + PHASE_SERVER_ACCESSIBILITY, + PHASE_SERVER_SYSTEM_READY, + PHASE_SERVER_HIDE_IMPLICIT, + PHASE_SERVER_HIDE_NOT_ALWAYS, + PHASE_SERVER_WAIT_IME, + PHASE_SERVER_HAS_IME, + PHASE_SERVER_SHOULD_HIDE, + PHASE_IME_WRAPPER, + PHASE_IME_WRAPPER_DISPATCH, + PHASE_IME_SHOW_SOFT_INPUT, + PHASE_IME_HIDE_SOFT_INPUT, + PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE, + PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER, + PHASE_SERVER_APPLY_IME_VISIBILITY, + PHASE_WM_SHOW_IME_RUNNER, + PHASE_WM_SHOW_IME_READY, + PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET, + PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS, + PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS, + PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS, + PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS, + PHASE_WM_REMOTE_INSETS_CONTROLLER, + PHASE_WM_ANIMATION_CREATE, + PHASE_WM_ANIMATION_RUNNING, + PHASE_CLIENT_SHOW_INSETS, + PHASE_CLIENT_HIDE_INSETS, + PHASE_CLIENT_HANDLE_SHOW_INSETS, + PHASE_CLIENT_HANDLE_HIDE_INSETS, + PHASE_CLIENT_APPLY_ANIMATION, + PHASE_CLIENT_CONTROL_ANIMATION, + PHASE_CLIENT_ANIMATION_RUNNING, + PHASE_CLIENT_ANIMATION_CANCEL, + PHASE_CLIENT_ANIMATION_FINISHED_SHOW, + PHASE_CLIENT_ANIMATION_FINISHED_HIDE + }) + @Retention(RetentionPolicy.SOURCE) + @interface Phase {} + + /** The view that requested the IME has been served by the IMM. */ + int PHASE_CLIENT_VIEW_SERVED = 0; + + /** The IME client that requested the IME has window manager focus. */ + int PHASE_SERVER_CLIENT_KNOWN = 1; + + /** The IME client that requested the IME has IME focus. */ + int PHASE_SERVER_CLIENT_FOCUSED = 2; + + /** The IME request complies with the current accessibility settings. */ + int PHASE_SERVER_ACCESSIBILITY = 3; + + /** The server is ready to run third party code. */ + int PHASE_SERVER_SYSTEM_READY = 4; + + /** Checked the implicit hide request against any explicit show requests. */ + int PHASE_SERVER_HIDE_IMPLICIT = 5; + + /** Checked the not-always hide request against any forced show requests. */ + int PHASE_SERVER_HIDE_NOT_ALWAYS = 6; + + /** The server is waiting for a connection to the IME. */ + int PHASE_SERVER_WAIT_IME = 7; + + /** The server has a connection to the IME. */ + int PHASE_SERVER_HAS_IME = 8; + + /** The server decided the IME should be hidden. */ + int PHASE_SERVER_SHOULD_HIDE = 9; + + /** Reached the IME wrapper. */ + int PHASE_IME_WRAPPER = 10; + + /** Dispatched from the IME wrapper to the IME. */ + int PHASE_IME_WRAPPER_DISPATCH = 11; + + /** Reached the IME' showSoftInput method. */ + int PHASE_IME_SHOW_SOFT_INPUT = 12; + + /** Reached the IME' hideSoftInput method. */ + int PHASE_IME_HIDE_SOFT_INPUT = 13; + + /** The server decided the IME should be shown. */ + int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = 14; + + /** Requested applying the IME visibility in the insets source consumer. */ + int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15; + + /** Applied the IME visibility. */ + int PHASE_SERVER_APPLY_IME_VISIBILITY = 16; + + /** Created the show IME runner. */ + int PHASE_WM_SHOW_IME_RUNNER = 17; + + /** Ready to show IME. */ + int PHASE_WM_SHOW_IME_READY = 18; + + /** The Window Manager has a connection to the IME insets control target. */ + int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET = 19; + + /** Reached the window insets control target's show insets method. */ + int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS = 20; + + /** Reached the window insets control target's hide insets method. */ + int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS = 21; + + /** Reached the remote insets control target's show insets method. */ + int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS = 22; + + /** Reached the remote insets control target's hide insets method. */ + int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS = 23; + + /** Reached the remote insets controller. */ + int PHASE_WM_REMOTE_INSETS_CONTROLLER = 24; + + /** Created the IME window insets show animation. */ + int PHASE_WM_ANIMATION_CREATE = 25; + + /** Started the IME window insets show animation. */ + int PHASE_WM_ANIMATION_RUNNING = 26; + + /** Reached the client's show insets method. */ + int PHASE_CLIENT_SHOW_INSETS = 27; + + /** Reached the client's hide insets method. */ + int PHASE_CLIENT_HIDE_INSETS = 28; + + /** Handling the IME window insets show request. */ + int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29; + + /** Handling the IME window insets hide request. */ + int PHASE_CLIENT_HANDLE_HIDE_INSETS = 30; + + /** Applied the IME window insets show animation. */ + int PHASE_CLIENT_APPLY_ANIMATION = 31; + + /** Started the IME window insets show animation. */ + int PHASE_CLIENT_CONTROL_ANIMATION = 32; + + /** Queued the IME window insets show animation. */ + int PHASE_CLIENT_ANIMATION_RUNNING = 33; + + /** Cancelled the IME window insets show animation. */ + int PHASE_CLIENT_ANIMATION_CANCEL = 34; + + /** Finished the IME window insets show animation. */ + int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35; + + /** Finished the IME window insets hide animation. */ + int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = 36; + + /** + * Called when an IME show request is created. + * + * @param token the token tracking the current IME show request or {@code null} otherwise. + * @param origin the origin of the IME show request. + * @param reason the reason why the IME show request was created. + */ + void onRequestShow(@Nullable Token token, @Origin int origin, + @SoftInputShowHideReason int reason); + + /** + * Called when an IME hide request is created. + * + * @param token the token tracking the current IME hide request or {@code null} otherwise. + * @param origin the origin of the IME hide request. + * @param reason the reason why the IME hide request was created. + */ + void onRequestHide(@Nullable Token token, @Origin int origin, + @SoftInputShowHideReason int reason); + + /** + * Called when an IME request progresses to a further phase. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param phase the new phase the IME request reached. + */ + void onProgress(@Nullable Token token, @Phase int phase); + + /** + * Called when an IME request fails. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param phase the phase the IME request failed at. + */ + void onFailed(@Nullable Token token, @Phase int phase); + + /** + * Called when an IME request reached a flow that is not yet implemented. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param phase the phase the IME request was currently at. + */ + void onTodo(@Nullable Token token, @Phase int phase); + + /** + * Called when an IME request is cancelled. + * + * @param token the token tracking the current IME request or {@code null} otherwise. + * @param phase the phase the IME request was cancelled at. + */ + void onCancelled(@Nullable Token token, @Phase int phase); + + /** + * Called when the IME show request is successful. + * + * @param token the token tracking the current IME show request or {@code null} otherwise. + */ + void onShown(@Nullable Token token); + + /** + * Called when the IME hide request is successful. + * + * @param token the token tracking the current IME hide request or {@code null} otherwise. + */ + void onHidden(@Nullable Token token); + + /** + * Get the singleton instance of this class. + * + * @return the singleton instance of this class + */ + @NonNull + static ImeTracker get() { + return SystemProperties.getBoolean("persist.debug.imetracker", false) + ? LOGGER + : NOOP_LOGGER; + } + + /** The singleton IME tracker instance. */ + ImeTracker LOGGER = new ImeTracker() { + + @Override + public void onRequestShow(@Nullable Token token, int origin, + @SoftInputShowHideReason int reason) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin) + + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + + @Override + public void onRequestHide(@Nullable Token token, int origin, + @SoftInputShowHideReason int reason) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin) + + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); + } + + @Override + public void onProgress(@Nullable Token token, int phase) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase)); + } + + @Override + public void onFailed(@Nullable Token token, int phase) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase)); + } + + @Override + public void onTodo(@Nullable Token token, int phase) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase)); + } + + @Override + public void onCancelled(@Nullable Token token, int phase) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase)); + } + + @Override + public void onShown(@Nullable Token token) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onShown"); + } + + @Override + public void onHidden(@Nullable Token token) { + if (token == null) return; + Log.i(TAG, token.mTag + ": onHidden"); + } + }; + + /** The singleton no-op IME tracker instance. */ + ImeTracker NOOP_LOGGER = new ImeTracker() { + + @Override + public void onRequestShow(@Nullable Token token, int origin, + @SoftInputShowHideReason int reason) {} + + @Override + public void onRequestHide(@Nullable Token token, int origin, + @SoftInputShowHideReason int reason) {} + + @Override + public void onProgress(@Nullable Token token, int phase) {} + + @Override + public void onFailed(@Nullable Token token, int phase) {} + + @Override + public void onTodo(@Nullable Token token, int phase) {} + + @Override + public void onCancelled(@Nullable Token token, int phase) {} + + @Override + public void onShown(@Nullable Token token) {} + + @Override + public void onHidden(@Nullable Token token) {} + }; + + /** A token that tracks the progress of an IME request. */ + class Token implements Parcelable { + + private final IBinder mBinder; + private final String mTag; + + public Token() { + this(ActivityThread.currentProcessName()); + } + + public Token(String component) { + this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt()))); + } + + private Token(IBinder binder, String tag) { + mBinder = binder; + mTag = tag; + } + + /** For Parcelable, no special marshalled objects. */ + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeStrongBinder(mBinder); + dest.writeString8(mTag); + } + + @NonNull + public static final Creator<Token> CREATOR = new Creator<>() { + @Override + public Token createFromParcel(Parcel source) { + IBinder binder = source.readStrongBinder(); + String tag = source.readString8(); + return new Token(binder, tag); + } + + @Override + public Token[] newArray(int size) { + return new Token[size]; + } + }; + } + + /** + * Utilities for mapping phases and origins IntDef values to their names. + * + * Note: This is held in a separate class so that it only gets initialized when actually needed. + */ + class Debug { + + private static final Map<Integer, String> sOrigins = + getFieldMapping(ImeTracker.class, "ORIGIN_"); + private static final Map<Integer, String> sPhases = + getFieldMapping(ImeTracker.class, "PHASE_"); + + public static String originToString(int origin) { + return sOrigins.getOrDefault(origin, "ORIGIN_" + origin); + } + + public static String phaseToString(int phase) { + return sPhases.getOrDefault(phase, "PHASE_" + phase); + } + + private static Map<Integer, String> getFieldMapping(Class<?> cls, String fieldPrefix) { + return Arrays.stream(cls.getDeclaredFields()) + .filter(field -> field.getName().startsWith(fieldPrefix)) + .collect(Collectors.toMap(Debug::getFieldValue, Field::getName)); + } + + private static int getFieldValue(Field field) { + try { + return field.getInt(null); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 4d5a17d92f11..92380ed7a7bc 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -300,11 +300,12 @@ public interface InputMethod { * @param showInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#showSoftInput(View, int)} is associated with * this callback. + * @param statsToken the token tracking the current IME show request or {@code null} otherwise. * @hide */ @MainThread - default public void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder showInputToken) { + public default void showSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder showInputToken, @Nullable ImeTracker.Token statsToken) { showSoftInput(flags, resultReceiver); } @@ -338,11 +339,14 @@ public interface InputMethod { * @param hideInputToken an opaque {@link android.os.Binder} token to identify which API call * of {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}} is associated * with this callback. + * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. * @hide */ @MainThread - public void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, - IBinder hideInputToken); + public default void hideSoftInputWithToken(int flags, ResultReceiver resultReceiver, + IBinder hideInputToken, @Nullable ImeTracker.Token statsToken) { + hideSoftInput(flags, resultReceiver); + } /** * Request that any soft input part of the input method be hidden from the user. @@ -369,7 +373,7 @@ public interface InputMethod { /** * Checks if IME is ready to start stylus handwriting session. - * If yes, {@link #startStylusHandwriting(InputChannel, List)} is called. + * If yes, {@link #startStylusHandwriting(int, InputChannel, List)} is called. * @param requestId * @hide */ diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 7e720ffbcbce..ee31fd5763b7 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -2007,6 +2007,10 @@ public final class InputMethodManager { private boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + final ImeTracker.Token statsToken = new ImeTracker.Token(); + ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, + reason); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this, null /* icProto */); // Re-dispatch if there is a context mismatch. @@ -2018,10 +2022,13 @@ public final class InputMethodManager { checkFocus(); synchronized (mH) { if (!hasServedByInputMethodLocked(view)) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served."); return false; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread. // TODO(b/229426865): call WindowInsetsController#show instead. mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED)); @@ -2030,6 +2037,7 @@ public final class InputMethodManager { return IInputMethodManagerGlobalInvoker.showSoftInput( mClient, view.getWindowToken(), + statsToken, flags, mCurRootView.getLastClickToolType(), resultReceiver, @@ -2049,19 +2057,28 @@ public final class InputMethodManager { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499) public void showSoftInputUnchecked(int flags, ResultReceiver resultReceiver) { synchronized (mH) { + final ImeTracker.Token statsToken = new ImeTracker.Token(); + ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, + SoftInputShowHideReason.SHOW_SOFT_INPUT); + Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be" + " removed soon. If you are using androidx.appcompat.widget.SearchView," + " please update to version 26.0 or newer version."); if (mCurRootView == null || mCurRootView.getView() == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()"); return; } + + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread. // TODO(b/229426865): call WindowInsetsController#show instead. mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED)); IInputMethodManagerGlobalInvoker.showSoftInput( mClient, mCurRootView.getView().getWindowToken(), + statsToken, flags, mCurRootView.getLastClickToolType(), resultReceiver, @@ -2131,17 +2148,24 @@ public final class InputMethodManager { private boolean hideSoftInputFromWindow(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + final ImeTracker.Token statsToken = new ImeTracker.Token(); + ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, + reason); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow", this, null /* icProto */); checkFocus(); synchronized (mH) { final View servedView = getServedViewLocked(); if (servedView == null || servedView.getWindowToken() != windowToken) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); return false; } - return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags, - resultReceiver, reason); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + + return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken, + flags, resultReceiver, reason); } } @@ -2769,14 +2793,23 @@ public final class InputMethodManager { @UnsupportedAppUsage void closeCurrentInput() { + final ImeTracker.Token statsToken = new ImeTracker.Token(); + ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, + SoftInputShowHideReason.HIDE_SOFT_INPUT); + synchronized (mH) { if (mCurRootView == null || mCurRootView.getView() == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); Log.w(TAG, "No current root view, ignoring closeCurrentInput()"); return; } + + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + IInputMethodManagerGlobalInvoker.hideSoftInput( mClient, mCurRootView.getView().getWindowToken(), + statsToken, HIDE_NOT_ALWAYS, null, SoftInputShowHideReason.HIDE_SOFT_INPUT); @@ -2845,15 +2878,24 @@ public final class InputMethodManager { * @hide */ public void notifyImeHidden(IBinder windowToken) { + final ImeTracker.Token statsToken = new ImeTracker.Token(); + ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, + SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API); + ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this, null /* icProto */); synchronized (mH) { - if (isImeSessionAvailableLocked() && mCurRootView != null - && mCurRootView.getWindowToken() == windowToken) { - IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */, - null /* resultReceiver */, - SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API); + if (!isImeSessionAvailableLocked() || mCurRootView == null + || mCurRootView.getWindowToken() != windowToken) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + return; } + + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED); + + IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken, + 0 /* flags */, null /* resultReceiver */, + SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API); } } @@ -3977,7 +4019,7 @@ public final class InputMethodManager { /** * As reported by {@link InputBindResult}. This value is determined by - * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecking}. + * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}. */ final boolean mIsInputMethodSuppressingSpellChecker; diff --git a/core/java/com/android/internal/inputmethod/IInputMethod.aidl b/core/java/com/android/internal/inputmethod/IInputMethod.aidl index c62fba9fb9b8..1e3714eb342d 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethod.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethod.aidl @@ -21,6 +21,7 @@ import android.os.ResultReceiver; import android.view.InputChannel; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; @@ -69,9 +70,11 @@ oneway interface IInputMethod { void setSessionEnabled(IInputMethodSession session, boolean enabled); - void showSoftInput(in IBinder showInputToken, int flags, in ResultReceiver resultReceiver); + void showSoftInput(in IBinder showInputToken, in @nullable ImeTracker.Token statsToken, + int flags, in ResultReceiver resultReceiver); - void hideSoftInput(in IBinder hideInputToken, int flags, in ResultReceiver resultReceiver); + void hideSoftInput(in IBinder hideInputToken, in @nullable ImeTracker.Token statsToken, + int flags, in ResultReceiver resultReceiver); void updateEditorToolType(int toolType); diff --git a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl index 4babb7080176..f77e96286bea 100644 --- a/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl +++ b/core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl @@ -17,6 +17,7 @@ package com.android.internal.inputmethod; import android.net.Uri; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.infra.AndroidFuture; @@ -41,7 +42,8 @@ oneway interface IInputMethodPrivilegedOperations { void switchToNextInputMethod(boolean onlyCurrentIme, in AndroidFuture future /* T=Boolean */); void shouldOfferSwitchingToNextInputMethod(in AndroidFuture future /* T=Boolean */); void notifyUserActionAsync(); - void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible); + void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, + in @nullable ImeTracker.Token statsToken); void onStylusHandwritingReady(int requestId, int pid); void resetStylusHandwriting(int requestId); } diff --git a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java index 67c2103450bb..66e3333acf7c 100644 --- a/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java +++ b/core/java/com/android/internal/inputmethod/InputMethodPrivilegedOperations.java @@ -25,6 +25,7 @@ import android.os.IBinder; import android.os.RemoteException; import android.util.Log; import android.view.View; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; @@ -100,7 +101,7 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int}. + * Calls {@link IInputMethodPrivilegedOperations#setImeWindowStatusAsync(int, int)}. * * @param vis visibility flags * @param backDisposition disposition flags @@ -250,7 +251,7 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, IVoidResultCallback)} + * Calls {@link IInputMethodPrivilegedOperations#hideMySoftInput(int, int, AndroidFuture)} * * @param flags additional operating flags * @param reason the reason to hide soft input @@ -316,7 +317,7 @@ public final class InputMethodPrivilegedOperations { /** * Calls {@link IInputMethodPrivilegedOperations#switchToNextInputMethod(boolean, - * IBooleanResultCallback)} + * AndroidFuture)} * * @param onlyCurrentIme {@code true} to switch to a {@link InputMethodSubtype} within the same * IME @@ -375,22 +376,25 @@ public final class InputMethodPrivilegedOperations { } /** - * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean)}. + * Calls {@link IInputMethodPrivilegedOperations#applyImeVisibilityAsync(IBinder, boolean, + * ImeTracker.Token)}. * * @param showOrHideInputToken placeholder token that maps to window requesting * {@link android.view.inputmethod.InputMethodManager#showSoftInput(View, int)} or - * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow - * (IBinder, int)} + * {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow(IBinder, + * int)} * @param setVisible {@code true} to set IME visible, else hidden. + * @param statsToken the token tracking the current IME request or {@code null} otherwise. */ @AnyThread - public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible) { + public void applyImeVisibilityAsync(IBinder showOrHideInputToken, boolean setVisible, + @Nullable ImeTracker.Token statsToken) { final IInputMethodPrivilegedOperations ops = mOps.getAndWarnIfNull(); if (ops == null) { return; } try { - ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible); + ops.applyImeVisibilityAsync(showOrHideInputToken, setVisible, statsToken); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } diff --git a/core/java/com/android/internal/view/BaseIWindow.java b/core/java/com/android/internal/view/BaseIWindow.java index fe7723658761..2ac43099c741 100644 --- a/core/java/com/android/internal/view/BaseIWindow.java +++ b/core/java/com/android/internal/view/BaseIWindow.java @@ -16,6 +16,7 @@ package com.android.internal.view; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.hardware.input.InputManager; import android.os.Bundle; @@ -31,6 +32,7 @@ import android.view.InsetsState; import android.view.PointerIcon; import android.view.ScrollCaptureResponse; import android.view.WindowInsets.Type.InsetsType; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import com.android.internal.os.IResultReceiver; @@ -66,11 +68,13 @@ public class BaseIWindow extends IWindow.Stub { } @Override - public void showInsets(@InsetsType int types, boolean fromIme) { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { } @Override - public void hideInsets(@InsetsType int types, boolean fromIme) { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { } @Override diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 40c6a05650cf..00bc3f2ae530 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -17,6 +17,7 @@ package com.android.internal.view; import android.os.ResultReceiver; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodInfo; import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; @@ -54,11 +55,12 @@ interface IInputMethodManager { + "android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)") InputMethodSubtype getLastInputMethodSubtype(int userId); - boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags, - int lastClickToolType, in @nullable ResultReceiver resultReceiver, int reason); - boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, int flags, + boolean showSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, + in @nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, + in @nullable ResultReceiver resultReceiver, int reason); + boolean hideSoftInput(in IInputMethodClient client, @nullable IBinder windowToken, + in @nullable ImeTracker.Token statsToken, int flags, in @nullable ResultReceiver resultReceiver, int reason); - // If windowToken is null, this just does startInput(). Otherwise this reports that a window // has gained focus, and if 'editorInfo' is non-null then also does startInput. // @NonNull diff --git a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java index ddcb17544ec7..0bf133fe01b6 100644 --- a/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java +++ b/core/tests/coretests/src/android/view/ImeInsetsSourceConsumerTest.java @@ -24,6 +24,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; @@ -98,12 +99,12 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // test if setVisibility can show IME mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertTrue((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); // test if setVisibility can hide IME - mController.hide(WindowInsets.Type.ime(), true /* fromIme */); + mController.hide(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertFalse((mController.getRequestedVisibleTypes() & WindowInsets.Type.ime()) != 0); }); @@ -117,7 +118,7 @@ public class ImeInsetsSourceConsumerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { // Request IME visible before control is available. mImeConsumer.onWindowFocusGained(true); - mController.show(WindowInsets.Type.ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); // set control and verify visibility is applied. InsetsSourceControl control = @@ -125,9 +126,11 @@ public class ImeInsetsSourceConsumerTest { mController.onControlsChanged(new InsetsSourceControl[] { control }); // IME show animation should be triggered when control becomes available. verify(mController).applyAnimation( - eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */); + eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, + any() /* statsToken */); verify(mController, never()).applyAnimation( - eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */); + eq(WindowInsets.Type.ime()), eq(false) /* show */, eq(true) /* fromIme */, + any() /* statsToken */); }); } @@ -153,7 +156,8 @@ public class ImeInsetsSourceConsumerTest { mImeConsumer.onWindowFocusGained(hasWindowFocus); final boolean imeVisible = hasWindowFocus && hasViewFocus; if (imeVisible) { - mController.show(WindowInsets.Type.ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, + null /* statsToken */); } // set control and verify visibility is applied. @@ -169,20 +173,21 @@ public class ImeInsetsSourceConsumerTest { verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(false) /* fromIme */, - eq(expectSkipAnim) /* skipAnim */); + eq(expectSkipAnim) /* skipAnim */, null /* statsToken */); } // If previously hasViewFocus is false, verify when requesting the IME visible next // time will not skip animation. if (!hasViewFocus) { - mController.show(WindowInsets.Type.ime(), true); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, + null /* statsToken */); mController.onControlsChanged(new InsetsSourceControl[]{ control }); // Verify IME show animation should be triggered when control becomes available and // the animation will be skipped by getAndClearSkipAnimationOnce invoked. verify(control).getAndClearSkipAnimationOnce(); verify(mController).applyAnimation(eq(WindowInsets.Type.ime()), eq(true) /* show */, eq(true) /* fromIme */, - eq(false) /* skipAnim */); + eq(false) /* skipAnim */, null /* statsToken */); } }); } diff --git a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java index e9cd8ad7d5c2..c88255ef0e00 100644 --- a/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java +++ b/core/tests/coretests/src/android/view/InsetsAnimationControlImplTest.java @@ -109,7 +109,8 @@ public class InsetsAnimationControlImplTest { mController = new InsetsAnimationControlImpl(controls, new Rect(0, 0, 500, 500), mInsetsState, mMockListener, systemBars(), mMockController, 10 /* durationMs */, new LinearInterpolator(), - 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */); + 0 /* animationType */, 0 /* layoutInsetsDuringAnimation */, null /* translator */, + null /* statsToken */); mController.setReadyDispatched(true); } diff --git a/core/tests/coretests/src/android/view/InsetsControllerTest.java b/core/tests/coretests/src/android/view/InsetsControllerTest.java index 409bae8addc2..c6fa778763a8 100644 --- a/core/tests/coretests/src/android/view/InsetsControllerTest.java +++ b/core/tests/coretests/src/android/view/InsetsControllerTest.java @@ -248,7 +248,7 @@ public class InsetsControllerTest { mController.setSystemDrivenInsetsAnimationLoggingListener(loggingListener); mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. - mController.show(ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); verify(loggingListener).onReady(notNull(), anyInt()); }); } @@ -260,14 +260,14 @@ public class InsetsControllerTest { InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true); // since there is no focused view, forcefully make IME visible. - mController.show(ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.show(all()); // quickly jump to final state by cancelling it. mController.cancelExistingAnimations(); final @InsetsType int types = navigationBars() | statusBars() | ime(); assertEquals(types, mController.getRequestedVisibleTypes() & types); - mController.hide(ime(), true /* fromIme */); + mController.hide(ime(), true /* fromIme */, null /* statsToken */); mController.hide(all()); mController.cancelExistingAnimations(); assertEquals(0, mController.getRequestedVisibleTypes() & types); @@ -282,10 +282,10 @@ public class InsetsControllerTest { mController.onControlsChanged(new InsetsSourceControl[] { ime }); InstrumentationRegistry.getInstrumentation().runOnMainSync(() -> { mController.getSourceConsumer(ITYPE_IME).onWindowFocusGained(true); - mController.show(ime(), true /* fromIme */); + mController.show(WindowInsets.Type.ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertTrue(isRequestedVisible(mController, ime())); - mController.hide(ime(), true /* fromIme */); + mController.hide(ime(), true /* fromIme */, null /* statsToken */); mController.cancelExistingAnimations(); assertFalse(isRequestedVisible(mController, ime())); mController.getSourceConsumer(ITYPE_IME).onWindowFocusLost(); @@ -452,7 +452,7 @@ public class InsetsControllerTest { assertFalse(mController.getState().getSource(ITYPE_IME).isVisible()); // Pretend IME is calling - mController.show(ime(), true /* fromIme */); + mController.show(ime(), true /* fromIme */, null /* statsToken */); // Gaining control shortly after mController.onControlsChanged(createSingletonControl(ITYPE_IME)); @@ -476,7 +476,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); // Pretend IME is calling - mController.show(ime(), true /* fromIme */); + mController.show(ime(), true /* fromIme */, null /* statsToken */); assertEquals(ANIMATION_TYPE_SHOW, mController.getAnimationType(ime())); mController.cancelExistingAnimations(); @@ -554,7 +554,7 @@ public class InsetsControllerTest { verify(listener, never()).onReady(any(), anyInt()); // Pretend that IME is calling. - mController.show(ime(), true); + mController.show(ime(), true /* fromIme */, null /* statsToken */); // Ready gets deferred until next predraw mViewRoot.getView().getViewTreeObserver().dispatchOnPreDraw(); @@ -638,7 +638,7 @@ public class InsetsControllerTest { mController.onControlsChanged(createSingletonControl(ITYPE_IME)); // Pretend IME is calling - mController.show(ime(), true /* fromIme */); + mController.show(ime(), true /* fromIme */, null /* statsToken */); InsetsState copy = new InsetsState(mController.getState(), true /* copySources */); copy.getSource(ITYPE_IME).setFrame(0, 1, 2, 3); @@ -845,7 +845,7 @@ public class InsetsControllerTest { // Showing invisible ime should only causes insets change once. clearInvocations(mTestHost); - mController.show(ime(), true /* fromIme */); + mController.show(ime(), true /* fromIme */, null /* statsToken */); verify(mTestHost, times(1)).notifyInsetsChanged(); // Sending the same insets state should not cause insets change. diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java index bb7c4134aaaf..d9b4f475a50c 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java @@ -20,6 +20,7 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; import android.annotation.IntDef; +import android.annotation.Nullable; import android.content.ComponentName; import android.content.res.Configuration; import android.graphics.Point; @@ -38,6 +39,7 @@ import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; import android.view.animation.Interpolator; import android.view.animation.PathInterpolator; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethodManagerGlobal; import androidx.annotation.VisibleForTesting; @@ -112,7 +114,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } if (mDisplayController.getDisplayLayout(displayId).rotation() != pd.mRotation && isImeShowing(displayId)) { - pd.startAnimation(true, false /* forceRestart */); + pd.startAnimation(true, false /* forceRestart */, null /* statsToken */); } } @@ -244,7 +246,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mInsetsState.set(insetsState, true /* copySources */); if (mImeShowing && !newFrame.equals(oldFrame) && newSource.isVisible()) { if (DEBUG) Slog.d(TAG, "insetsChanged when IME showing, restart animation"); - startAnimation(mImeShowing, true /* forceRestart */); + startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); } } @@ -280,7 +282,7 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged !haveSameLeash(mImeSourceControl, imeSourceControl); if (mAnimation != null) { if (positionChanged) { - startAnimation(mImeShowing, true /* forceRestart */); + startAnimation(mImeShowing, true /* forceRestart */, null /* statsToken */); } } else { if (leashChanged) { @@ -312,21 +314,23 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } @Override - public void showInsets(int types, boolean fromIme) { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got showInsets for ime"); - startAnimation(true /* show */, false /* forceRestart */); + startAnimation(true /* show */, false /* forceRestart */, statsToken); } @Override - public void hideInsets(int types, boolean fromIme) { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { if ((types & WindowInsets.Type.ime()) == 0) { return; } if (DEBUG) Slog.d(TAG, "Got hideInsets for ime"); - startAnimation(false /* show */, false /* forceRestart */); + startAnimation(false /* show */, false /* forceRestart */, statsToken); } @Override @@ -367,9 +371,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged .navBarFrameHeight(); } - private void startAnimation(final boolean show, final boolean forceRestart) { + private void startAnimation(final boolean show, final boolean forceRestart, + @Nullable ImeTracker.Token statsToken) { final InsetsSource imeSource = mInsetsState.getSource(InsetsState.ITYPE_IME); if (imeSource == null || mImeSourceControl == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); return; } final Rect newFrame = imeSource.getFrame(); @@ -390,8 +396,9 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged + (mAnimationDirection == DIRECTION_SHOW ? "SHOW" : (mAnimationDirection == DIRECTION_HIDE ? "HIDE" : "NONE"))); } - if (!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show) + if ((!forceRestart && (mAnimationDirection == DIRECTION_SHOW && show)) || (mAnimationDirection == DIRECTION_HIDE && !show)) { + ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); return; } boolean seek = false; @@ -435,8 +442,11 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged mTransactionPool.release(t); }); mAnimation.setInterpolator(INTERPOLATOR); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_ANIMATION_CREATE); mAnimation.addListener(new AnimatorListenerAdapter() { private boolean mCancelled = false; + @Nullable + private final ImeTracker.Token mStatsToken = statsToken; @Override public void onAnimationStart(Animator animation) { @@ -455,6 +465,8 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged : 1.f; t.setAlpha(mImeSourceControl.getLeash(), alpha); if (mAnimationDirection == DIRECTION_SHOW) { + ImeTracker.get().onProgress(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.show(mImeSourceControl.getLeash()); } t.apply(); @@ -476,8 +488,16 @@ public class DisplayImeController implements DisplayController.OnDisplaysChanged } dispatchEndPositioning(mDisplayId, mCancelled, t); if (mAnimationDirection == DIRECTION_HIDE && !mCancelled) { + ImeTracker.get().onProgress(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); t.hide(mImeSourceControl.getLeash()); removeImeSurface(); + ImeTracker.get().onHidden(mStatsToken); + } else if (mAnimationDirection == DIRECTION_SHOW && !mCancelled) { + ImeTracker.get().onShown(mStatsToken); + } else if (mCancelled) { + ImeTracker.get().onCancelled(mStatsToken, + ImeTracker.PHASE_WM_ANIMATION_RUNNING); } t.apply(); mTransactionPool.release(t); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java index 8d4a09d8f371..8759301f695b 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayInsetsController.java @@ -16,6 +16,7 @@ package com.android.wm.shell.common; +import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; import android.util.Slog; @@ -25,6 +26,7 @@ import android.view.IWindowManager; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.WindowInsets.Type.InsetsType; +import android.view.inputmethod.ImeTracker; import androidx.annotation.BinderThread; @@ -156,23 +158,29 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } } - private void showInsets(int types, boolean fromIme) { + private void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); return; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); for (OnInsetsChangedListener listener : listeners) { - listener.showInsets(types, fromIme); + listener.showInsets(types, fromIme, statsToken); } } - private void hideInsets(int types, boolean fromIme) { + private void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { CopyOnWriteArrayList<OnInsetsChangedListener> listeners = mListeners.get(mDisplayId); if (listeners == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); return; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROLLER); for (OnInsetsChangedListener listener : listeners) { - listener.hideInsets(types, fromIme); + listener.hideInsets(types, fromIme, statsToken); } } @@ -214,16 +222,18 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan } @Override - public void showInsets(int types, boolean fromIme) throws RemoteException { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.showInsets(types, fromIme); + PerDisplay.this.showInsets(types, fromIme, statsToken); }); } @Override - public void hideInsets(int types, boolean fromIme) throws RemoteException { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) throws RemoteException { mMainExecutor.execute(() -> { - PerDisplay.this.hideInsets(types, fromIme); + PerDisplay.this.hideInsets(types, fromIme, statsToken); }); } } @@ -263,15 +273,21 @@ public class DisplayInsetsController implements DisplayController.OnDisplaysChan * * @param types {@link InsetsType} to show * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME show request + * or {@code null} otherwise. */ - default void showInsets(@InsetsType int types, boolean fromIme) {} + default void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) {} /** * Called when a set of insets source window should be hidden by policy. * * @param types {@link InsetsType} to hide * @param fromIme true if this request originated from IME (InputMethodService). + * @param statsToken the token tracking the current IME hide request + * or {@code null} otherwise. */ - default void hideInsets(@InsetsType int types, boolean fromIme) {} + default void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) {} } } diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java index e270edb800bd..af13bf54f691 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SystemWindows.java @@ -19,6 +19,7 @@ package com.android.wm.shell.common; import static android.view.WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; import android.annotation.NonNull; +import android.annotation.Nullable; import android.content.Context; import android.content.res.Configuration; import android.graphics.Region; @@ -46,6 +47,7 @@ import android.view.View; import android.view.ViewGroup; import android.view.WindowManager; import android.view.WindowlessWindowManager; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import com.android.internal.os.IResultReceiver; @@ -351,10 +353,10 @@ public class SystemWindows { InsetsSourceControl[] activeControls) {} @Override - public void showInsets(int types, boolean fromIme) {} + public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @Override - public void hideInsets(int types, boolean fromIme) {} + public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) {} @Override public void moved(int newX, int newY) {} diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java index a6f19e7d11d3..40f2e88f34fd 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java @@ -97,13 +97,13 @@ public class DisplayImeControllerTest extends ShellTestCase { @Test public void showInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.showInsets(ime(), true); + mPerDisplay.showInsets(ime(), true /* fromIme */, null /* statsToken */); verifyZeroInteractions(mExecutor); } @Test public void hideInsets_schedulesNoWorkOnExecutor() { - mPerDisplay.hideInsets(ime(), true); + mPerDisplay.hideInsets(ime(), true /* fromIme */, null /* statsToken */); verifyZeroInteractions(mExecutor); } diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java index 39db328ef0e0..956f1cd419c2 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayInsetsControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import android.annotation.Nullable; import android.content.ComponentName; import android.os.RemoteException; import android.util.SparseArray; @@ -33,6 +34,7 @@ import android.view.IWindowManager; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.WindowInsets; +import android.view.inputmethod.ImeTracker; import androidx.test.filters.SmallTest; @@ -111,8 +113,10 @@ public class DisplayInsetsControllerTest extends ShellTestCase { WindowInsets.Type.defaultVisible()); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).insetsControlChanged(null, null); - mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false); - mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).showInsets(0, false, + null /* statsToken */); + mInsetsControllersByDisplayId.get(DEFAULT_DISPLAY).hideInsets(0, false, + null /* statsToken */); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -131,8 +135,10 @@ public class DisplayInsetsControllerTest extends ShellTestCase { WindowInsets.Type.defaultVisible()); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsChanged(null); mInsetsControllersByDisplayId.get(SECOND_DISPLAY).insetsControlChanged(null, null); - mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false); - mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).showInsets(0, false, + null /* statsToken */); + mInsetsControllersByDisplayId.get(SECOND_DISPLAY).hideInsets(0, false, + null /* statsToken */); mExecutor.flushAll(); assertTrue(defaultListener.topFocusedWindowChangedCount == 1); @@ -191,12 +197,12 @@ public class DisplayInsetsControllerTest extends ShellTestCase { } @Override - public void showInsets(int types, boolean fromIme) { + public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { showInsetsCount++; } @Override - public void hideInsets(int types, boolean fromIme) { + public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) { hideInsetsCount++; } } diff --git a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java index 1a0f6f7fff4a..015e5768d505 100644 --- a/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java +++ b/services/core/java/com/android/server/inputmethod/IInputMethodInvoker.java @@ -28,6 +28,7 @@ import android.util.Slog; import android.view.InputChannel; import android.view.MotionEvent; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputMethodSubtype; import android.window.ImeOnBackInvokedDispatcher; @@ -198,9 +199,10 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean showSoftInput(IBinder showInputToken, int flags, ResultReceiver resultReceiver) { + boolean showSoftInput(IBinder showInputToken, @Nullable ImeTracker.Token statsToken, int flags, + ResultReceiver resultReceiver) { try { - mTarget.showSoftInput(showInputToken, flags, resultReceiver); + mTarget.showSoftInput(showInputToken, statsToken, flags, resultReceiver); } catch (RemoteException e) { logRemoteException(e); return false; @@ -210,9 +212,10 @@ final class IInputMethodInvoker { // TODO(b/192412909): Convert this back to void method @AnyThread - boolean hideSoftInput(IBinder hideInputToken, int flags, ResultReceiver resultReceiver) { + boolean hideSoftInput(IBinder hideInputToken, @Nullable ImeTracker.Token statsToken, int flags, + ResultReceiver resultReceiver) { try { - mTarget.hideSoftInput(hideInputToken, flags, resultReceiver); + mTarget.hideSoftInput(hideInputToken, statsToken, flags, resultReceiver); } catch (RemoteException e) { logRemoteException(e); return false; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 114c512b3f95..8b083bd72722 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -129,6 +129,7 @@ import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManager.LayoutParams.SoftInputModeFlags; import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputBinding; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethod; @@ -151,6 +152,7 @@ import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; +import com.android.internal.inputmethod.IInputMethod; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.IInputMethodSession; @@ -642,6 +644,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub */ private boolean mInputShown; + /** The token tracking the current IME request or {@code null} otherwise. */ + @Nullable + private ImeTracker.Token mCurStatsToken; + /** * {@code true} if the current input method is in fullscreen mode. */ @@ -761,7 +767,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub * <dd> * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. * </dd> - * dt>{@link InputMethodService#IME_INVISIBLE}</dt> + * <dt>{@link InputMethodService#IME_INVISIBLE}</dt> * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is * currently invisible. * </dd> @@ -785,8 +791,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub /** * Internal state snapshot when - * {@link com.android.internal.view.IInputMethod#startInput(IBinder, IRemoteInputConnection, EditorInfo, - * boolean)} is about to be called. + * {@link IInputMethod#startInput(IInputMethod.StartInputParams)} is about to be called. * * <p>Calling that IPC endpoint basically means that * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called @@ -1071,7 +1076,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub /** * Add a new entry and discard the oldest entry as needed. - * @param info {@lin StartInputInfo} to be added. + * @param info {@link StartInputInfo} to be added. */ void addEntry(@NonNull StartInputInfo info) { final int index = mNextIndex; @@ -1189,18 +1194,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } else if (accessibilityRequestingNoImeUri.equals(uri)) { final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser( mContext.getContentResolver(), - Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId); + Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0 /* def */, mUserId); mAccessibilityRequestingNoSoftKeyboard = (accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK) == AccessibilityService.SHOW_MODE_HIDDEN; if (mAccessibilityRequestingNoSoftKeyboard) { final boolean showRequested = mShowRequested; - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, + 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); mShowRequested = showRequested; } else if (mShowRequested) { - showCurrentInputLocked(mCurFocusedWindow, - InputMethodManager.SHOW_IMPLICIT, null, + showCurrentInputImplicitLocked(mCurFocusedWindow, SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); } } else { @@ -1666,8 +1671,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } // Hide soft input before user switch task since switch task may block main handler a while // and delayed the hideCurrentInputLocked(). - hideCurrentInputLocked( - mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER); + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_SWITCH_USER); final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, clientToBeReset); mUserSwitchHandlerTask = task; @@ -2222,7 +2227,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client); try { - client.asBinder().linkToDeath(deathRecipient, 0); + client.asBinder().linkToDeath(deathRecipient, 0 /* flags */); } catch (RemoteException e) { throw new IllegalStateException(e); } @@ -2247,7 +2252,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { ClientState cs = mClients.remove(client.asBinder()); if (cs != null) { - client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0); + client.asBinder().unlinkToDeath(cs.mClientDeathRecipient, 0 /* flags */); clearClientSessionLocked(cs); clearClientSessionForAccessibilityLocked(cs); @@ -2260,8 +2265,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (mCurClient == cs) { - hideCurrentInputLocked( - mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_REMOVE_CLIENT); if (mBoundToMethod) { mBoundToMethod = false; IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -2308,6 +2313,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurClient.mSessionRequestedForAccessibility = false; mCurClient = null; mCurVirtualDisplayToScreenMatrix = null; + ImeTracker.get().onFailed(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + mCurStatsToken = null; mMenuController.hideInputMethodMenuLocked(); } @@ -2380,8 +2387,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub navButtonFlags, mCurImeDispatcher); if (mShowRequested) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); - showCurrentInputLocked(mCurFocusedWindow, getAppShowFlagsLocked(), null, - SoftInputShowHideReason.ATTACH_NEW_INPUT); + // Re-use current statsToken, if it exists. + final ImeTracker.Token statsToken = mCurStatsToken; + mCurStatsToken = null; + showCurrentInputLocked(mCurFocusedWindow, statsToken, getAppShowFlagsLocked(), + null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT); } String curId = getCurIdLocked(); @@ -2500,7 +2510,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (mDisplayIdToShowIme == INVALID_DISPLAY) { mImeHiddenByDisplayPolicy = true; - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE); return InputBindResult.NO_IME; } @@ -3278,22 +3289,23 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @Override - public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags, - int lastClickTooType, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, + @Nullable ImeTracker.Token statsToken, int flags, int lastClickTooType, + ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#showSoftInput"); synchronized (ImfLock.class) { - if (!canInteractWithImeLocked(uid, client, "showSoftInput")) { + if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken)) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); return false; } final long ident = Binder.clearCallingIdentity(); try { if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); - return showCurrentInputLocked( - windowToken, flags, lastClickTooType, resultReceiver, reason); + return showCurrentInputLocked(windowToken, statsToken, flags, lastClickTooType, + resultReceiver, reason); } finally { Binder.restoreCallingIdentity(ident); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3310,7 +3322,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub "InputMethodManagerService#startStylusHandwriting"); int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { - if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting")) { + if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting", + null /* statsToken */)) { return; } if (!hasSupportedStylusLocked()) { @@ -3365,19 +3378,33 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - boolean showCurrentInputLocked(IBinder windowToken, int flags, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { - return showCurrentInputLocked( - windowToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason); + boolean showCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + return showCurrentInputLocked(windowToken, statsToken, flags, + MotionEvent.TOOL_TYPE_UNKNOWN, resultReceiver, reason); } @GuardedBy("ImfLock.class") - private boolean showCurrentInputLocked(IBinder windowToken, int flags, int lastClickToolType, + private boolean showCurrentInputLocked(IBinder windowToken, + @Nullable ImeTracker.Token statsToken, int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + // Create statsToken is none exists. + if (statsToken == null) { + String packageName = null; + if (mCurEditorInfo != null) { + packageName = mCurEditorInfo.packageName; + } + statsToken = new ImeTracker.Token(packageName); + ImeTracker.get().onRequestShow(statsToken, ImeTracker.ORIGIN_SERVER_START_INPUT, + reason); + } + mShowRequested = true; if (mAccessibilityRequestingNoSoftKeyboard || mImeHiddenByDisplayPolicy) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); return false; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); if ((flags & InputMethodManager.SHOW_FORCED) != 0) { mShowExplicitlyRequested = true; @@ -3387,8 +3414,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!mSystemReady) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY); return false; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY); mBindingController.setCurrentMethodVisible(); final IInputMethodInvoker curMethod = getCurMethodLocked(); @@ -3396,6 +3425,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // create a placeholder token for IMS so that IMS cannot inject windows into client app. Binder showInputToken = new Binder(); mShowRequestWindowMap.put(showInputToken, windowToken); + ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME); + mCurStatsToken = null; final int showFlags = getImeShowFlagsLocked(); if (DEBUG) { Slog.v(TAG, "Calling " + curMethod + ".showSoftInput(" + showInputToken @@ -3407,23 +3439,34 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub curMethod.updateEditorToolType(lastClickToolType); } // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. - if (curMethod.showSoftInput(showInputToken, showFlags, resultReceiver)) { + if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) { onShowHideSoftInputRequested(true /* show */, windowToken, reason); } mInputShown = true; return true; + } else { + ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + mCurStatsToken = statsToken; } return false; } @Override - public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags, - ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, + @Nullable ImeTracker.Token statsToken, int flags, ResultReceiver resultReceiver, + @SoftInputShowHideReason int reason) { int uid = Binder.getCallingUid(); ImeTracing.getInstance().triggerManagerServiceDump( "InputMethodManagerService#hideSoftInput"); synchronized (ImfLock.class) { - if (!canInteractWithImeLocked(uid, client, "hideSoftInput")) { + if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) { + if (mInputShown) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); + } else { + ImeTracker.get().onCancelled(statsToken, + ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); + } return false; } final long ident = Binder.clearCallingIdentity(); @@ -3431,7 +3474,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput"); if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); return InputMethodManagerService.this.hideCurrentInputLocked(windowToken, - flags, resultReceiver, reason); + statsToken, flags, resultReceiver, reason); } finally { Binder.restoreCallingIdentity(ident); Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -3440,17 +3483,32 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, - @SoftInputShowHideReason int reason) { + boolean hideCurrentInputLocked(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) { + // Create statsToken is none exists. + if (statsToken == null) { + String packageName = null; + if (mCurEditorInfo != null) { + packageName = mCurEditorInfo.packageName; + } + statsToken = new ImeTracker.Token(packageName); + ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason); + } + if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 && (mShowExplicitlyRequested || mShowForced)) { if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT); return false; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT); + if (mShowForced && (flags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) { if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS); return false; } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS); // There is a chance that IMM#hideSoftInput() is called in a transient state where // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting @@ -3461,8 +3519,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // IMMS#InputShown indicates that the software keyboard is shown. // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. IInputMethodInvoker curMethod = getCurMethodLocked(); - final boolean shouldHideSoftInput = (curMethod != null) && (mInputShown - || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); + final boolean shouldHideSoftInput = (curMethod != null) + && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); boolean res; if (shouldHideSoftInput) { final Binder hideInputToken = new Binder(); @@ -3471,17 +3529,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // delivered to the IME process as an IPC. Hence the inconsistency between // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in // the final state. + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); if (DEBUG) { Slog.v(TAG, "Calling " + curMethod + ".hideSoftInput(0, " + hideInputToken + ", " + resultReceiver + ") for reason: " + InputMethodDebug.softInputDisplayReasonToString(reason)); } // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not. - if (curMethod.hideSoftInput(hideInputToken, 0 /* flags */, resultReceiver)) { + if (curMethod.hideSoftInput(hideInputToken, statsToken, 0 /* flags */, + resultReceiver)) { onShowHideSoftInputRequested(false /* show */, windowToken, reason); } res = true; } else { + ImeTracker.get().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); res = false; } mBindingController.setCurrentMethodNotVisible(); @@ -3489,6 +3550,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mShowRequested = false; mShowExplicitlyRequested = false; mShowForced = false; + // Cancel existing statsToken for show IME as we got a hide request. + ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); + mCurStatsToken = null; return res; } @@ -3646,8 +3710,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Slog.w(TAG, "If you need to impersonate a foreground user/profile from" + " a background user, use EditorInfo.targetInputMethodUser with" + " INTERACT_ACROSS_USERS_FULL permission."); - hideCurrentInputLocked( - mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER); + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_INVALID_USER); return InputBindResult.INVALID_USER; } @@ -3703,7 +3767,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub boolean didStart = false; InputBindResult res = null; - // We shows the IME when the system allows the IME focused target window to restore the + // We show the IME when the system allows the IME focused target window to restore the // IME visibility (e.g. switching to the app task when last time the IME is visible). // Note that we don't restore IME visibility for some cases (e.g. when the soft input // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation). @@ -3715,7 +3779,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, imeDispatcher); - showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + showCurrentInputImplicitLocked(windowToken, SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); return res; } @@ -3728,8 +3792,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // be behind any soft input window, so hide the // soft input window if it is shown. if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); - hideCurrentInputLocked( - mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, + InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); // If focused display changed, we should unbind current method @@ -3758,10 +3822,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imeDispatcher); didStart = true; } - showCurrentInputLocked( - windowToken, - InputMethodManager.SHOW_IMPLICIT, - null, + showCurrentInputImplicitLocked(windowToken, SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); } break; @@ -3774,14 +3835,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case LayoutParams.SOFT_INPUT_STATE_HIDDEN: if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); } break; case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: if (!sameWindowFocused) { if (DEBUG) Slog.v(TAG, "Window asks to hide input"); - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); } break; @@ -3797,7 +3860,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imeDispatcher); didStart = true; } - showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + showCurrentInputImplicitLocked(windowToken, SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); } else { Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" @@ -3818,7 +3881,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imeDispatcher); didStart = true; } - showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, + showCurrentInputImplicitLocked(windowToken, SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); } } else { @@ -3840,7 +3903,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // an editor upon refocusing a window. if (startInputByWinGainedFocus) { if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, + 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); } } @@ -3854,7 +3918,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // 2) SOFT_INPUT_STATE_VISIBLE state without an editor // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR); } res = startInputUncheckedLocked(cs, inputContext, @@ -3869,8 +3934,15 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - private boolean canInteractWithImeLocked( - int uid, IInputMethodClient client, String methodName) { + private void showCurrentInputImplicitLocked(@NonNull IBinder windowToken, + @SoftInputShowHideReason int reason) { + showCurrentInputLocked(windowToken, null /* statsToken */, InputMethodManager.SHOW_IMPLICIT, + null /* resultReceiver */, reason); + } + + @GuardedBy("ImfLock.class") + private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, + @Nullable ImeTracker.Token statsToken) { if (mCurClient == null || client == null || mCurClient.mClient.asBinder() != client.asBinder()) { // We need to check if this is the current client with @@ -3878,13 +3950,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // be made before input is started in it. final ClientState cs = mClients.get(client.asBinder()); if (cs == null) { + ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); throw new IllegalArgumentException("unknown client " + client.asBinder()); } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); if (!isImeClientFocused(mCurFocusedWindow, cs)) { Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); return false; } } + ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); return true; } @@ -4237,7 +4312,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final int curTokenDisplayId; synchronized (ImfLock.class) { if (!canInteractWithImeLocked(callingUid, client, - "getInputMethodWindowVisibleHeight")) { + "getInputMethodWindowVisibleHeight", null /* statsToken */)) { if (!mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.get(callingUid)) { EventLog.writeEvent(0x534e4554, "204906124", callingUid, ""); mLoggedDeniedGetInputMethodWindowVisibleHeightForUid.put(callingUid, true); @@ -4460,7 +4535,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { - if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession")) { + if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession", + null /* statsToken */)) { return; } final long ident = Binder.clearCallingIdentity(); @@ -4487,7 +4563,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub int uid = Binder.getCallingUid(); synchronized (ImfLock.class) { - if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest")) { + if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest", + null /* statsToken */)) { return; } final long ident = Binder.clearCallingIdentity(); @@ -4672,7 +4749,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @BinderThread - private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) { + private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible, + @Nullable ImeTracker.Token statsToken) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility"); synchronized (ImfLock.class) { if (!calledWithValidTokenLocked(token)) { @@ -4680,13 +4758,22 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } if (!setVisible) { if (mCurClient != null) { + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); + mWindowManagerInternal.hideIme( mHideRequestWindowMap.get(windowToken), - mCurClient.mSelfReportedDisplayId); + mCurClient.mSelfReportedDisplayId, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); } } else { + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); // Send to window manager to show IME after IME layout finishes. - mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken)); + mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken), + statsToken); } } Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); @@ -4753,7 +4840,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - hideCurrentInputLocked(mLastImeTargetWindow, flags, null, reason); + hideCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, + null /* resultReceiver */, reason); } finally { Binder.restoreCallingIdentity(ident); } @@ -4770,7 +4858,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } final long ident = Binder.clearCallingIdentity(); try { - showCurrentInputLocked(mLastImeTargetWindow, flags, null, + showCurrentInputLocked(mLastImeTargetWindow, null /* statsToken */, flags, + null /* resultReceiver */, SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME); } finally { Binder.restoreCallingIdentity(ident); @@ -4861,7 +4950,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub case MSG_HIDE_CURRENT_INPUT_METHOD: synchronized (ImfLock.class) { final @SoftInputShowHideReason int reason = (int) msg.obj; - hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason); + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, + null /* resultReceiver */, reason); } return true; @@ -6349,7 +6439,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub final String nextIme; final List<InputMethodInfo> nextEnabledImes; if (userId == mSettings.getCurrentUserId()) { - hideCurrentInputLocked(mCurFocusedWindow, 0, null, + hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, + 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); mBindingController.unbindCurrentMethod(); // Reset the current IME @@ -6614,8 +6705,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @BinderThread @Override - public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible) { - mImms.applyImeVisibility(mToken, windowToken, setVisible); + public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, + @Nullable ImeTracker.Token statsToken) { + mImms.applyImeVisibility(mToken, windowToken, setVisible, statsToken); } @BinderThread diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java index 8b3444318636..12efe0dc074f 100644 --- a/services/core/java/com/android/server/wm/DisplayContent.java +++ b/services/core/java/com/android/server/wm/DisplayContent.java @@ -230,6 +230,7 @@ import android.view.WindowInsets.Type.InsetsType; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; import android.view.WindowManagerPolicyConstants.PointerEventListener; +import android.view.inputmethod.ImeTracker; import android.window.DisplayWindowPolicyController; import android.window.IDisplayAreaOrganizer; import android.window.ScreenCapture; @@ -454,7 +455,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp /** * Compat metrics computed based on {@link #mDisplayMetrics}. - * @see #updateDisplayAndOrientation(int, Configuration) + * @see #updateDisplayAndOrientation(Configuration) */ private final DisplayMetrics mCompatDisplayMetrics = new DisplayMetrics(); @@ -5031,7 +5032,7 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp * layer has been assigned since), to facilitate assigning the layer from the IME target, or * fall back if there is no target. * - the container doesn't always participate in window traversal, according to - * {@link #skipImeWindowsDuringTraversal()} + * {@link #skipImeWindowsDuringTraversal(DisplayContent)} */ private static class ImeContainer extends DisplayArea.Tokens { boolean mNeedsLayer = false; @@ -6712,25 +6713,35 @@ class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.Disp mRemoteInsetsController.insetsControlChanged(stateController.getRawInsetsState(), stateController.getControlsForDispatch(this)); } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver inset state change", e); + Slog.w(TAG, "Failed to deliver inset control state change", e); } } @Override - public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme) { + public void showInsets(@WindowInsets.Type.InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { try { - mRemoteInsetsController.showInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS); + mRemoteInsetsController.showInsets(types, fromIme, statsToken); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS); } } @Override - public void hideInsets(@InsetsType int types, boolean fromIme) { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { try { - mRemoteInsetsController.hideInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS); + mRemoteInsetsController.hideInsets(types, fromIme, statsToken); } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver showInsets", e); + Slog.w(TAG, "Failed to deliver hideInsets", e); + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS); } } diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java index 25891485330e..1fef3c22a523 100644 --- a/services/core/java/com/android/server/wm/DisplayPolicy.java +++ b/services/core/java/com/android/server/wm/DisplayPolicy.java @@ -2071,7 +2071,8 @@ public class DisplayPolicy { // Don't show status bar when swiping on already visible navigation bar. // But restore the position of navigation bar if it has been moved by the control // target. - controlTarget.showInsets(Type.navigationBars(), false); + controlTarget.showInsets(Type.navigationBars(), false /* fromIme */, + null /* statsToken */); return; } @@ -2079,10 +2080,12 @@ public class DisplayPolicy { // Show transient bars if they are hidden; restore position if they are visible. mDisplayContent.getInsetsPolicy().showTransient(SHOW_TYPES_FOR_SWIPE, isGestureOnSystemBar); - controlTarget.showInsets(restorePositionTypes, false); + controlTarget.showInsets(restorePositionTypes, false /* fromIme */, + null /* statsToken */); } else { // Restore visibilities and positions of system bars. - controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), false); + controlTarget.showInsets(Type.statusBars() | Type.navigationBars(), + false /* fromIme */, null /* statsToken */); // To further allow the pull-down-from-the-top gesture to pull down the notification // shade as a consistent motion, we reroute the touch events here from the currently // touched window to the status bar after making it visible. diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java index 554791a0da91..7fd093fffe95 100644 --- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java +++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java @@ -33,9 +33,11 @@ import android.graphics.Rect; import android.os.Trace; import android.util.proto.ProtoOutputStream; import android.view.InsetsSource; +import android.view.InsetsSourceConsumer; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.WindowInsets; +import android.view.inputmethod.ImeTracker; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; @@ -49,6 +51,9 @@ import java.io.PrintWriter; */ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider { + /** The token tracking the current IME request or {@code null} otherwise. */ + @Nullable + private ImeTracker.Token mImeRequesterStatsToken; private InsetsControlTarget mImeRequester; private Runnable mShowImeRunner; private boolean mIsImeLayoutDrawn; @@ -162,14 +167,20 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider } /** - * Called from {@link WindowManagerInternal#showImePostLayout} when {@link InputMethodService} - * requests to show IME on {@param imeTarget}. + * Called from {@link WindowManagerInternal#showImePostLayout} + * when {@link android.inputmethodservice.InputMethodService} requests to show IME + * on {@param imeTarget}. * - * @param imeTarget imeTarget on which IME request is coming from. + * @param imeTarget imeTarget on which IME show request is coming from. + * @param statsToken the token tracking the current IME show request or {@code null} otherwise. */ - void scheduleShowImePostLayout(InsetsControlTarget imeTarget) { + void scheduleShowImePostLayout(InsetsControlTarget imeTarget, + @Nullable ImeTracker.Token statsToken) { boolean targetChanged = isTargetChangedWithinActivity(imeTarget); mImeRequester = imeTarget; + // There was still a stats token, so that request presumably failed. + ImeTracker.get().onFailed(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + mImeRequesterStatsToken = statsToken; if (targetChanged) { // target changed, check if new target can show IME. ProtoLog.d(WM_DEBUG_IME, "IME target changed within ActivityRecord"); @@ -183,15 +194,20 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider ProtoLog.d(WM_DEBUG_IME, "Schedule IME show for %s", mImeRequester.getWindow() == null ? mImeRequester : mImeRequester.getWindow().getName()); mShowImeRunner = () -> { + ImeTracker.get().onProgress(mImeRequesterStatsToken, + ImeTracker.PHASE_WM_SHOW_IME_RUNNER); ProtoLog.d(WM_DEBUG_IME, "Run showImeRunner"); // Target should still be the same. if (isReadyToShowIme()) { + ImeTracker.get().onProgress(mImeRequesterStatsToken, + ImeTracker.PHASE_WM_SHOW_IME_READY); final InsetsControlTarget target = mDisplayContent.getImeTarget(IME_TARGET_CONTROL); ProtoLog.i(WM_DEBUG_IME, "call showInsets(ime) on %s", target.getWindow() != null ? target.getWindow().getName() : ""); setImeShowing(true); - target.showInsets(WindowInsets.Type.ime(), true /* fromIme */); + target.showInsets(WindowInsets.Type.ime(), true /* fromIme */, + mImeRequesterStatsToken); Trace.asyncTraceEnd(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); if (target != mImeRequester && mImeRequester != null) { ProtoLog.w(WM_DEBUG_IME, @@ -199,7 +215,12 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider (mImeRequester.getWindow() != null ? mImeRequester.getWindow().getName() : "")); } + } else { + ImeTracker.get().onFailed(mImeRequesterStatsToken, + ImeTracker.PHASE_WM_SHOW_IME_READY); } + // Clear token here so we don't report an error in abortShowImePostLayout(). + mImeRequesterStatsToken = null; abortShowImePostLayout(); }; mDisplayContent.mWmService.requestTraversal(); @@ -234,6 +255,8 @@ final class ImeInsetsSourceProvider extends WindowContainerInsetsSourceProvider mImeRequester = null; mIsImeLayoutDrawn = false; mShowImeRunner = null; + ImeTracker.get().onCancelled(mImeRequesterStatsToken, ImeTracker.PHASE_WM_SHOW_IME_RUNNER); + mImeRequesterStatsToken = null; } @VisibleForTesting diff --git a/services/core/java/com/android/server/wm/InsetsControlTarget.java b/services/core/java/com/android/server/wm/InsetsControlTarget.java index d35b7c3d5fbe..8ecbc177896c 100644 --- a/services/core/java/com/android/server/wm/InsetsControlTarget.java +++ b/services/core/java/com/android/server/wm/InsetsControlTarget.java @@ -16,9 +16,11 @@ package com.android.server.wm; +import android.annotation.Nullable; import android.inputmethodservice.InputMethodService; import android.view.WindowInsets; import android.view.WindowInsets.Type.InsetsType; +import android.view.inputmethod.ImeTracker; /** * Generalization of an object that can control insets state. @@ -57,8 +59,10 @@ interface InsetsControlTarget { * * @param types to specify which types of insets source window should be shown. * @param fromIme {@code true} if IME show request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME show request or {@code null} otherwise. */ - default void showInsets(@InsetsType int types, boolean fromIme) { + default void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { } /** @@ -66,8 +70,10 @@ interface InsetsControlTarget { * * @param types to specify which types of insets source window should be hidden. * @param fromIme {@code true} if IME hide request originated from {@link InputMethodService}. + * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. */ - default void hideInsets(@InsetsType int types, boolean fromIme) { + default void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { } /** diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java index b9fa80cf2c0f..f66fa0f33b04 100644 --- a/services/core/java/com/android/server/wm/InsetsPolicy.java +++ b/services/core/java/com/android/server/wm/InsetsPolicy.java @@ -799,7 +799,7 @@ class InsetsPolicy { show ? ANIMATION_TYPE_SHOW : ANIMATION_TYPE_HIDE, show ? LAYOUT_INSETS_DURING_ANIMATION_SHOWN : LAYOUT_INSETS_DURING_ANIMATION_HIDDEN, - null /* translator */); + null /* translator */, null /* statsToken */); SurfaceAnimationThread.getHandler().post( () -> mListener.onReady(mAnimationControl, typesReady)); } diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java index c206a15503de..bab3a0553e63 100644 --- a/services/core/java/com/android/server/wm/WindowManagerInternal.java +++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java @@ -43,6 +43,7 @@ import android.view.SurfaceControl; import android.view.SurfaceControlViewHost; import android.view.WindowInfo; import android.view.WindowManager.DisplayImePolicy; +import android.view.inputmethod.ImeTracker; import com.android.internal.policy.KeyInterceptionInfo; import com.android.server.input.InputManagerService; @@ -729,16 +730,20 @@ public abstract class WindowManagerInternal { * Show IME on imeTargetWindow once IME has finished layout. * * @param imeTargetWindowToken token of the (IME target) window on which IME should be shown. + * @param statsToken the token tracking the current IME show request or {@code null} otherwise. */ - public abstract void showImePostLayout(IBinder imeTargetWindowToken); + public abstract void showImePostLayout(IBinder imeTargetWindowToken, + @Nullable ImeTracker.Token statsToken); /** * Hide IME using imeTargetWindow when requested. * * @param imeTargetWindowToken token of the (IME target) window on which IME should be hidden. * @param displayId the id of the display the IME is on. + * @param statsToken the token tracking the current IME hide request or {@code null} otherwise. */ - public abstract void hideIme(IBinder imeTargetWindowToken, int displayId); + public abstract void hideIme(IBinder imeTargetWindowToken, int displayId, + @Nullable ImeTracker.Token statsToken); /** * Tell window manager about a package that should be running with a restricted range of diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4080223f817f..3419207eb14f 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -292,6 +292,7 @@ import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicyConstants.PointerEventListener; import android.view.displayhash.DisplayHash; import android.view.displayhash.VerifiedDisplayHash; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import android.window.ITaskFpsCallback; import android.window.ScreenCapture; @@ -8014,7 +8015,8 @@ public class WindowManagerService extends IWindowManager.Stub } @Override - public void showImePostLayout(IBinder imeTargetWindowToken) { + public void showImePostLayout(IBinder imeTargetWindowToken, + @Nullable ImeTracker.Token statsToken) { synchronized (mGlobalLock) { InputTarget imeTarget = getInputTargetFromWindowTokenLocked(imeTargetWindowToken); if (imeTarget == null) { @@ -8023,17 +8025,18 @@ public class WindowManagerService extends IWindowManager.Stub Trace.asyncTraceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.showImePostLayout", 0); final InsetsControlTarget controlTarget = imeTarget.getImeControlTarget(); imeTarget = controlTarget.getWindow(); - // If InsetsControlTarget doesn't have a window, its using remoteControlTarget which - // is controlled by default display + // If InsetsControlTarget doesn't have a window, it's using remoteControlTarget + // which is controlled by default display final DisplayContent dc = imeTarget != null ? imeTarget.getDisplayContent() : getDefaultDisplayContentLocked(); dc.getInsetsStateController().getImeSourceProvider() - .scheduleShowImePostLayout(controlTarget); + .scheduleShowImePostLayout(controlTarget, statsToken); } } @Override - public void hideIme(IBinder imeTargetWindowToken, int displayId) { + public void hideIme(IBinder imeTargetWindowToken, int displayId, + @Nullable ImeTracker.Token statsToken) { Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "WMS.hideIme"); synchronized (mGlobalLock) { WindowState imeTarget = mWindowMap.get(imeTargetWindowToken); @@ -8049,10 +8052,15 @@ public class WindowManagerService extends IWindowManager.Stub dc.getInsetsStateController().getImeSourceProvider().abortShowImePostLayout(); } if (dc != null && dc.getImeTarget(IME_TARGET_CONTROL) != null) { + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); ProtoLog.d(WM_DEBUG_IME, "hideIme Control target: %s ", dc.getImeTarget(IME_TARGET_CONTROL)); - dc.getImeTarget(IME_TARGET_CONTROL).hideInsets( - WindowInsets.Type.ime(), true /* fromIme */); + dc.getImeTarget(IME_TARGET_CONTROL).hideInsets(WindowInsets.Type.ime(), + true /* fromIme */, statsToken); + } else { + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET); } if (dc != null) { dc.getInsetsStateController().getImeSourceProvider().setImeShowing(false); diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java index 2712cb7c4c35..5c5c70334e65 100644 --- a/services/core/java/com/android/server/wm/WindowState.java +++ b/services/core/java/com/android/server/wm/WindowState.java @@ -249,6 +249,7 @@ import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Interpolator; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import android.window.OnBackInvokedCallbackInfo; @@ -4018,7 +4019,7 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP mClient.insetsControlChanged(getCompatInsetsState(), stateController.getControlsForDispatch(this)); } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver inset state change to w=" + this, e); + Slog.w(TAG, "Failed to deliver inset control state change to w=" + this, e); } } @@ -4028,20 +4029,30 @@ class WindowState extends WindowContainer<WindowState> implements WindowManagerP } @Override - public void showInsets(@InsetsType int types, boolean fromIme) { + public void showInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { try { - mClient.showInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS); + mClient.showInsets(types, fromIme, statsToken); } catch (RemoteException e) { Slog.w(TAG, "Failed to deliver showInsets", e); + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS); } } @Override - public void hideInsets(@InsetsType int types, boolean fromIme) { + public void hideInsets(@InsetsType int types, boolean fromIme, + @Nullable ImeTracker.Token statsToken) { try { - mClient.hideInsets(types, fromIme); + ImeTracker.get().onProgress(statsToken, + ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS); + mClient.hideInsets(types, fromIme, statsToken); } catch (RemoteException e) { - Slog.w(TAG, "Failed to deliver showInsets", e); + Slog.w(TAG, "Failed to deliver hideInsets", e); + ImeTracker.get().onFailed(statsToken, + ImeTracker.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS); } } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 3fbc4004785d..640bde330cee 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -244,7 +244,7 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodVisible(); } verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) - .showSoftInput(any(), anyInt(), any()); + .showSoftInput(any(), any(), anyInt(), any()); } protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) @@ -254,6 +254,6 @@ public class InputMethodManagerServiceTestBase { .setCurrentMethodNotVisible(); } verify(mMockInputMethod, times(hideSoftInput ? 1 : 0)) - .hideSoftInput(any(), anyInt(), any()); + .hideSoftInput(any(), any(), anyInt(), any()); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java index 52af8adcf9f6..d99946f0a5c4 100644 --- a/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/DisplayPolicyTests.java @@ -45,6 +45,7 @@ import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.mock; @@ -375,10 +376,10 @@ public class DisplayPolicyTests extends WindowTestsBase { displayPolicy.setCanSystemBarsBeShownByUser(false); displayPolicy.requestTransientBars(windowState, true); - verify(controlTarget, never()).showInsets(anyInt(), anyBoolean()); + verify(controlTarget, never()).showInsets(anyInt(), anyBoolean(), any() /* statsToken */); displayPolicy.setCanSystemBarsBeShownByUser(true); displayPolicy.requestTransientBars(windowState, true); - verify(controlTarget).showInsets(anyInt(), anyBoolean()); + verify(controlTarget).showInsets(anyInt(), anyBoolean(), any() /* statsToken */); } } diff --git a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java index eb8b89d59310..a26cad98f4d2 100644 --- a/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java +++ b/services/tests/wmtests/src/com/android/server/wm/ImeInsetsSourceProviderTest.java @@ -55,7 +55,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeControlTarget(popup); mDisplayContent.setImeLayeringTarget(appWin); popup.mAttrs.format = PixelFormat.TRANSPARENT; - mImeProvider.scheduleShowImePostLayout(appWin); + mImeProvider.scheduleShowImePostLayout(appWin, null /* statsToken */); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -64,7 +64,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { WindowState target = createWindow(null, TYPE_APPLICATION, "app"); mDisplayContent.setImeLayeringTarget(target); mDisplayContent.updateImeInputAndControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target); + mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); assertTrue(mImeProvider.isReadyToShowIme()); } @@ -78,7 +78,7 @@ public class ImeInsetsSourceProviderTest extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(target); mDisplayContent.setImeControlTarget(target); - mImeProvider.scheduleShowImePostLayout(target); + mImeProvider.scheduleShowImePostLayout(target, null /* statsToken */); assertFalse(mImeProvider.isImeShowing()); mImeProvider.checkShowImePostLayout(); assertTrue(mImeProvider.isImeShowing()); diff --git a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java index bb5acebfacd7..6e72bf360295 100644 --- a/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java +++ b/services/tests/wmtests/src/com/android/server/wm/TestIWindow.java @@ -16,6 +16,7 @@ package com.android.server.wm; +import android.annotation.Nullable; import android.os.Bundle; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -26,6 +27,7 @@ import android.view.IWindow; import android.view.InsetsSourceControl; import android.view.InsetsState; import android.view.ScrollCaptureResponse; +import android.view.inputmethod.ImeTracker; import android.window.ClientWindowFrames; import com.android.internal.os.IResultReceiver; @@ -117,10 +119,12 @@ public class TestIWindow extends IWindow.Stub { } @Override - public void showInsets(int types, boolean fromIme) throws RemoteException { + public void showInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) + throws RemoteException { } @Override - public void hideInsets(int types, boolean fromIme) throws RemoteException { + public void hideInsets(int types, boolean fromIme, @Nullable ImeTracker.Token statsToken) + throws RemoteException { } } diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java index 0139f6a5695a..6bd341210cad 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java @@ -1000,7 +1000,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(app); mDisplayContent.setImeInputTarget(app); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); @@ -1037,7 +1037,7 @@ public class WindowStateTests extends WindowTestsBase { mDisplayContent.setImeLayeringTarget(app); mDisplayContent.setImeInputTarget(app); assertTrue(mDisplayContent.shouldImeAttachedToApp()); - controller.getImeSourceProvider().scheduleShowImePostLayout(app); + controller.getImeSourceProvider().scheduleShowImePostLayout(app, null /* statsToken */); controller.getImeSourceProvider().getSource().setVisible(true); controller.updateAboveInsetsState(false); diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java index eca7cbbc5486..ab042d15963b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java +++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java @@ -100,6 +100,7 @@ import android.view.SurfaceControl.Transaction; import android.view.View; import android.view.WindowManager; import android.view.WindowManager.DisplayImePolicy; +import android.view.inputmethod.ImeTracker; import android.window.ITransitionPlayer; import android.window.ScreenCapture; import android.window.StartingWindowInfo; @@ -848,11 +849,13 @@ class WindowTestsBase extends SystemServiceTestsBase { } @Override - public void showInsets(int i, boolean b) throws RemoteException { + public void showInsets(int i, boolean b, @Nullable ImeTracker.Token t) + throws RemoteException { } @Override - public void hideInsets(int i, boolean b) throws RemoteException { + public void hideInsets(int i, boolean b, @Nullable ImeTracker.Token t) + throws RemoteException { } @Override |