diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java index db17a533c303e..d84acc03826b5 100644 --- a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java +++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java @@ -25,6 +25,7 @@ import android.annotation.RequiresNoPermission; import android.annotation.RequiresPermission; import android.annotation.UserIdInt; import android.content.Context; +import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.ResultReceiver; @@ -33,6 +34,7 @@ import android.view.WindowManager; import android.window.ImeOnBackInvokedDispatcher; import com.android.internal.inputmethod.DirectBootAwareness; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; @@ -40,7 +42,6 @@ import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; -import com.android.internal.view.IImeTracker; import com.android.internal.view.IInputMethodManager; import java.util.ArrayList; @@ -581,51 +582,57 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onRequestShow */ @AnyThread - @Nullable - static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { + @NonNull + static ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final IImeTracker service = getImeTrackerService(); if (service == null) { - return null; + // Create token with "fake" binder if the service was not found. + return new ImeTracker.Token(new Binder(), tag); } try { - return service.onRequestShow(uid, origin, reason); + return service.onRequestShow(tag, uid, origin, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onRequestHide */ @AnyThread - @Nullable - static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { + @NonNull + static ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { final IImeTracker service = getImeTrackerService(); if (service == null) { - return null; + // Create token with "fake" binder if the service was not found. + return new ImeTracker.Token(new Binder(), tag); } try { - return service.onRequestHide(uid, origin, reason); + return service.onRequestHide(tag, uid, origin, reason); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onProgress */ @AnyThread - static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; } try { - service.onProgress(statsToken, phase); + service.onProgress(binder, phase); } catch (RemoteException e) { throw e.rethrowFromSystemServer(); } } + /** @see com.android.server.inputmethod.ImeTrackerService#onFailed */ @AnyThread - static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onFailed(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -637,8 +644,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onCancelled */ @AnyThread - static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + static void onCancelled(@NonNull ImeTracker.Token statsToken, @ImeTracker.Phase int phase) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -650,8 +658,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onShown */ @AnyThread - static void onShown(@NonNull IBinder statsToken) { + static void onShown(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -663,8 +672,9 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#onHidden */ @AnyThread - static void onHidden(@NonNull IBinder statsToken) { + static void onHidden(@NonNull ImeTracker.Token statsToken) { final IImeTracker service = getImeTrackerService(); if (service == null) { return; @@ -676,6 +686,7 @@ final class IInputMethodManagerGlobalInvoker { } } + /** @see com.android.server.inputmethod.ImeTrackerService#hasPendingImeVisibilityRequests */ @AnyThread @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD) static boolean hasPendingImeVisibilityRequests() { diff --git a/core/java/android/view/inputmethod/ImeTracker.java b/core/java/android/view/inputmethod/ImeTracker.java index e5a99ff8566bd..f0d1019ffb067 100644 --- a/core/java/android/view/inputmethod/ImeTracker.java +++ b/core/java/android/view/inputmethod/ImeTracker.java @@ -26,7 +26,6 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.content.Context; -import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Parcelable; @@ -47,7 +46,7 @@ import java.lang.reflect.Field; import java.util.Arrays; import java.util.Locale; import java.util.Map; -import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import java.util.stream.Collectors; /** @hide */ @@ -321,9 +320,8 @@ public interface ImeTracker { /** * Creates an IME show request tracking token. * - * @param component the component name where the IME show request was created, - * or {@code null} otherwise - * (defaulting to {@link ActivityThread#currentProcessName()}). + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME show request. * @param reason the reason why the IME show request was created. @@ -337,9 +335,8 @@ public interface ImeTracker { /** * Creates an IME hide request tracking token. * - * @param component the component name where the IME hide request was created, - * or {@code null} otherwise - * (defaulting to {@link ActivityThread#currentProcessName()}). + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME hide request. * @param reason the reason why the IME hide request was created. @@ -435,8 +432,7 @@ public interface ImeTracker { mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false); // Update logging flag dynamically. SystemProperties.addChangeCallback(() -> - mLogProgress = - SystemProperties.getBoolean("persist.debug.imetracker", false)); + mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false)); } /** Whether progress should be logged. */ @@ -446,10 +442,9 @@ public interface ImeTracker { @Override public Token onRequestShow(@Nullable String component, int uid, @Origin int origin, @SoftInputShowHideReason int reason) { - IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason); - if (binder == null) binder = new Binder(); - - final Token token = Token.build(binder, component); + final var tag = getTag(component); + final var token = IInputMethodManagerGlobalInvoker.onRequestShow(tag, uid, origin, + reason); Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); @@ -461,10 +456,9 @@ public interface ImeTracker { @Override public Token onRequestHide(@Nullable String component, int uid, @Origin int origin, @SoftInputShowHideReason int reason) { - IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason); - if (binder == null) binder = new Binder(); - - final Token token = Token.build(binder, component); + final var tag = getTag(component); + final var token = IInputMethodManagerGlobalInvoker.onRequestHide(tag, uid, origin, + reason); Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin) + " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)); @@ -485,7 +479,7 @@ public interface ImeTracker { @Override public void onFailed(@Nullable Token token, @Phase int phase) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase); + IInputMethodManagerGlobalInvoker.onFailed(token, phase); Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase)); } @@ -499,7 +493,7 @@ public interface ImeTracker { @Override public void onCancelled(@Nullable Token token, @Phase int phase) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, phase); + IInputMethodManagerGlobalInvoker.onCancelled(token, phase); Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase)); } @@ -507,7 +501,7 @@ public interface ImeTracker { @Override public void onShown(@Nullable Token token) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onShown(token.mBinder); + IInputMethodManagerGlobalInvoker.onShown(token); Log.i(TAG, token.mTag + ": onShown"); } @@ -515,10 +509,24 @@ public interface ImeTracker { @Override public void onHidden(@Nullable Token token) { if (token == null) return; - IInputMethodManagerGlobalInvoker.onHidden(token.mBinder); + IInputMethodManagerGlobalInvoker.onHidden(token); Log.i(TAG, token.mTag + ": onHidden"); } + + /** + * Returns a logging tag using the given component name. + * + * @param component the name of the component that created the IME request, or {@code null} + * otherwise (defaulting to {@link ActivityThread#currentProcessName()}). + */ + @NonNull + private String getTag(@Nullable String component) { + if (component == null) { + component = ActivityThread.currentProcessName(); + } + return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt()); + } }; /** The singleton IME tracker instance for instrumenting jank metrics. */ @@ -528,28 +536,31 @@ public interface ImeTracker { ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker(); /** A token that tracks the progress of an IME request. */ - class Token implements Parcelable { + final class Token implements Parcelable { + /** The binder used to identify this token. */ @NonNull - public final IBinder mBinder; + private final IBinder mBinder; + /** Logging tag, of the shape "component:random_hexadecimal". */ @NonNull private final String mTag; - @NonNull - private static Token build(@NonNull IBinder binder, @Nullable String component) { - if (component == null) component = ActivityThread.currentProcessName(); - final String tag = component + ":" + Integer.toHexString((new Random().nextInt())); - - return new Token(binder, tag); - } - - private Token(@NonNull IBinder binder, @NonNull String tag) { + public Token(@NonNull IBinder binder, @NonNull String tag) { mBinder = binder; mTag = tag; } - /** Returns the {@link Token#mTag} */ + private Token(@NonNull Parcel in) { + mBinder = in.readStrongBinder(); + mTag = in.readString8(); + } + + @NonNull + public IBinder getBinder() { + return mBinder; + } + @NonNull public String getTag() { return mTag; @@ -562,7 +573,7 @@ public interface ImeTracker { } @Override - public void writeToParcel(Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeStrongBinder(mBinder); dest.writeString8(mTag); } @@ -571,12 +582,11 @@ public interface ImeTracker { public static final Creator CREATOR = new Creator<>() { @NonNull @Override - public Token createFromParcel(Parcel source) { - final IBinder binder = source.readStrongBinder(); - final String tag = source.readString8(); - return new Token(binder, tag); + public Token createFromParcel(@NonNull Parcel in) { + return new Token(in); } + @NonNull @Override public Token[] newArray(int size) { return new Token[size]; @@ -589,40 +599,50 @@ public interface ImeTracker { * * Note: This is held in a separate class so that it only gets initialized when actually needed. */ - class Debug { + final class Debug { + @NonNull private static final Map sTypes = getFieldMapping(ImeTracker.class, "TYPE_"); + @NonNull private static final Map sStatus = getFieldMapping(ImeTracker.class, "STATUS_"); + @NonNull private static final Map sOrigins = getFieldMapping(ImeTracker.class, "ORIGIN_"); + @NonNull private static final Map sPhases = getFieldMapping(ImeTracker.class, "PHASE_"); + @NonNull public static String typeToString(@Type int type) { return sTypes.getOrDefault(type, "TYPE_" + type); } + @NonNull public static String statusToString(@Status int status) { return sStatus.getOrDefault(status, "STATUS_" + status); } + @NonNull public static String originToString(@Origin int origin) { return sOrigins.getOrDefault(origin, "ORIGIN_" + origin); } + @NonNull public static String phaseToString(@Phase int phase) { return sPhases.getOrDefault(phase, "PHASE_" + phase); } - private static Map getFieldMapping(Class cls, String fieldPrefix) { + @NonNull + private static Map getFieldMapping(Class cls, + @NonNull 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) { + private static int getFieldValue(@NonNull Field field) { try { return field.getInt(null); } catch (IllegalAccessException e) { diff --git a/core/java/com/android/internal/view/IImeTracker.aidl b/core/java/com/android/internal/inputmethod/IImeTracker.aidl similarity index 72% rename from core/java/com/android/internal/view/IImeTracker.aidl rename to core/java/com/android/internal/inputmethod/IImeTracker.aidl index b062ca7df9212..c7418ee36fff9 100644 --- a/core/java/com/android/internal/view/IImeTracker.aidl +++ b/core/java/com/android/internal/inputmethod/IImeTracker.aidl @@ -14,43 +14,45 @@ * limitations under the License. */ -package com.android.internal.view; +package com.android.internal.inputmethod; import android.view.inputmethod.ImeTracker; /** - * Interface to the global Ime tracker, used by all client applications. + * Interface to the global IME tracker service, used by all client applications. * {@hide} */ interface IImeTracker { /** - * Called when an IME show request is created, - * returns a new Binder to be associated with the IME tracking token. + * Called when an IME show request is created. * + * @param tag the logging tag. * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME show request. * @param reason the reason why the IME show request was created. + * @return A new IME tracking token. */ - IBinder onRequestShow(int uid, int origin, int reason); + ImeTracker.Token onRequestShow(String tag, int uid, int origin, int reason); /** - * Called when an IME hide request is created, - * returns a new Binder to be associated with the IME tracking token. + * Called when an IME hide request is created. * + * @param tag the logging tag. * @param uid the uid of the client that requested the IME. * @param origin the origin of the IME hide request. * @param reason the reason why the IME hide request was created. + * @return A new IME tracking token. */ - IBinder onRequestHide(int uid, int origin, int reason); + ImeTracker.Token onRequestHide(String tag, int uid, int origin, int reason); /** * Called when the IME request progresses to a further phase. * - * @param statsToken the token tracking the current IME request. + * @param binder the binder of token tracking the current IME request. * @param phase the new phase the IME request reached. */ - oneway void onProgress(in IBinder statsToken, int phase); + oneway void onProgress(in IBinder binder, int phase); /** * Called when the IME request fails. @@ -58,7 +60,7 @@ interface IImeTracker { * @param statsToken the token tracking the current IME request. * @param phase the phase the IME request failed at. */ - oneway void onFailed(in IBinder statsToken, int phase); + oneway void onFailed(in ImeTracker.Token statsToken, int phase); /** * Called when the IME request is cancelled. @@ -66,21 +68,21 @@ interface IImeTracker { * @param statsToken the token tracking the current IME request. * @param phase the phase the IME request was cancelled at. */ - oneway void onCancelled(in IBinder statsToken, int phase); + oneway void onCancelled(in ImeTracker.Token statsToken, int phase); /** * Called when the IME show request is successful. * * @param statsToken the token tracking the current IME request. */ - oneway void onShown(in IBinder statsToken); + oneway void onShown(in ImeTracker.Token statsToken); /** * Called when the IME hide request is successful. * * @param statsToken the token tracking the current IME request. */ - oneway void onHidden(in IBinder statsToken); + oneway void onHidden(in ImeTracker.Token statsToken); /** * Checks whether there are any pending IME visibility requests. diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index 5805d0e050d16..9a4610e8c0a12 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -23,11 +23,11 @@ import android.view.inputmethod.InputMethodSubtype; import android.view.inputmethod.EditorInfo; import android.window.ImeOnBackInvokedDispatcher; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInputMethodClient; import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; import com.android.internal.inputmethod.IRemoteInputConnection; import com.android.internal.inputmethod.InputBindResult; -import com.android.internal.view.IImeTracker; /** * Public interface to the global input method manager, used by all client diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java index da65f27688d81..2efb0beaa567d 100644 --- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java +++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java @@ -24,13 +24,14 @@ import android.os.Binder; import android.os.Handler; import android.os.IBinder; import android.os.Looper; +import android.util.Log; import android.view.inputmethod.ImeTracker; import com.android.internal.annotations.GuardedBy; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.InputMethodDebug; import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.internal.util.FrameworkStatsLog; -import com.android.internal.view.IImeTracker; import java.io.PrintWriter; import java.time.Instant; @@ -53,7 +54,7 @@ import java.util.concurrent.atomic.AtomicInteger; @SuppressWarnings("GuardedBy") public final class ImeTrackerService extends IImeTracker.Stub { - static final String TAG = "ImeTrackerService"; + private static final String TAG = ImeTracker.TAG; /** The threshold in milliseconds after which a history entry is considered timed out. */ private static final long TIMEOUT_MS = 10_000; @@ -71,67 +72,71 @@ public final class ImeTrackerService extends IImeTracker.Stub { @NonNull @Override - public synchronized IBinder onRequestShow(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { - final IBinder binder = new Binder(); - final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_SHOW, - ImeTracker.STATUS_RUN, origin, reason); + public synchronized ImeTracker.Token onRequestShow(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + final var binder = new Binder(); + final var token = new ImeTracker.Token(binder, tag); + final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_SHOW, ImeTracker.STATUS_RUN, + origin, reason); mHistory.addEntry(binder, entry); // Register a delayed task to handle the case where the new entry times out. mHandler.postDelayed(() -> { synchronized (ImeTrackerService.this) { - mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); } }, TIMEOUT_MS); - return binder; + return token; } @NonNull @Override - public synchronized IBinder onRequestHide(int uid, @ImeTracker.Origin int origin, - @SoftInputShowHideReason int reason) { - final IBinder binder = new Binder(); - final History.Entry entry = new History.Entry(uid, ImeTracker.TYPE_HIDE, - ImeTracker.STATUS_RUN, origin, reason); + public synchronized ImeTracker.Token onRequestHide(@NonNull String tag, int uid, + @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + final var binder = new Binder(); + final var token = new ImeTracker.Token(binder, tag); + final var entry = new History.Entry(tag, uid, ImeTracker.TYPE_HIDE, ImeTracker.STATUS_RUN, + origin, reason); mHistory.addEntry(binder, entry); // Register a delayed task to handle the case where the new entry times out. mHandler.postDelayed(() -> { synchronized (ImeTrackerService.this) { - mHistory.setFinished(binder, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); + mHistory.setFinished(token, ImeTracker.STATUS_TIMEOUT, ImeTracker.PHASE_NOT_SET); } }, TIMEOUT_MS); - return binder; + return token; } @Override - public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { - final History.Entry entry = mHistory.getEntry(statsToken); + public synchronized void onProgress(@NonNull IBinder binder, @ImeTracker.Phase int phase) { + final var entry = mHistory.getEntry(binder); if (entry == null) return; entry.mPhase = phase; } @Override - public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + public synchronized void onFailed(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Phase int phase) { mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase); } @Override - public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) { + public synchronized void onCancelled(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Phase int phase) { mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase); } @Override - public synchronized void onShown(@NonNull IBinder statsToken) { + public synchronized void onShown(@NonNull ImeTracker.Token statsToken) { mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); } @Override - public synchronized void onHidden(@NonNull IBinder statsToken) { + public synchronized void onHidden(@NonNull ImeTracker.Token statsToken) { mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET); } @@ -141,9 +146,9 @@ public final class ImeTrackerService extends IImeTracker.Stub { * @param statsToken the token corresponding to the current IME request. * @param requestWindowName the name of the window that created the IME request. */ - public synchronized void onImmsUpdate(@NonNull IBinder statsToken, + public synchronized void onImmsUpdate(@NonNull ImeTracker.Token statsToken, @NonNull String requestWindowName) { - final History.Entry entry = mHistory.getEntry(statsToken); + final var entry = mHistory.getEntry(statsToken.getBinder()); if (entry == null) return; entry.mRequestWindowName = requestWindowName; @@ -181,17 +186,17 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** Latest entry sequence number. */ private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); - /** Adds a live entry. */ + /** Adds a live entry corresponding to the given IME tracking token's binder. */ @GuardedBy("ImeTrackerService.this") - private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) { - mLiveEntries.put(statsToken, entry); + private void addEntry(@NonNull IBinder binder, @NonNull Entry entry) { + mLiveEntries.put(binder, entry); } - /** Gets the entry corresponding to the given IME tracking token, if it exists. */ + /** Gets the entry corresponding to the given IME tracking token's binder, if it exists. */ @Nullable @GuardedBy("ImeTrackerService.this") - private Entry getEntry(@NonNull IBinder statsToken) { - return mLiveEntries.get(statsToken); + private Entry getEntry(@NonNull IBinder binder) { + return mLiveEntries.get(binder); } /** @@ -204,10 +209,21 @@ public final class ImeTrackerService extends IImeTracker.Stub { * (or {@link ImeTracker#PHASE_NOT_SET} otherwise). */ @GuardedBy("ImeTrackerService.this") - private void setFinished(@NonNull IBinder statsToken, @ImeTracker.Status int status, - @ImeTracker.Phase int phase) { - final Entry entry = mLiveEntries.remove(statsToken); - if (entry == null) return; + private void setFinished(@NonNull ImeTracker.Token statsToken, + @ImeTracker.Status int status, @ImeTracker.Phase int phase) { + final var entry = mLiveEntries.remove(statsToken.getBinder()); + if (entry == null) { + // This will be unconditionally called through the postDelayed above to handle + // potential timeouts, and is thus intentionally dropped to avoid having to manually + // save and remove the registered callback. Only timeout calls are expected. + if (status != ImeTracker.STATUS_TIMEOUT) { + Log.i(TAG, statsToken.getTag() + + ": setFinished on previously finished token at " + + ImeTracker.Debug.phaseToString(phase) + " with " + + ImeTracker.Debug.statusToString(status)); + } + return; + } entry.mDuration = System.currentTimeMillis() - entry.mStartTime; entry.mStatus = status; @@ -216,6 +232,13 @@ public final class ImeTrackerService extends IImeTracker.Stub { entry.mPhase = phase; } + if (status == ImeTracker.STATUS_TIMEOUT) { + // All events other than timeouts are already logged in the client-side ImeTracker. + Log.i(TAG, statsToken.getTag() + ": setFinished at " + + ImeTracker.Debug.phaseToString(entry.mPhase) + " with " + + ImeTracker.Debug.statusToString(status)); + } + // Remove excess entries overflowing capacity (plus one for the new entry). while (mEntries.size() >= CAPACITY) { mEntries.remove(); @@ -232,21 +255,22 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** Dumps the contents of the circular buffer. */ @GuardedBy("ImeTrackerService.this") private void dump(@NonNull PrintWriter pw, @NonNull String prefix) { - final DateTimeFormatter formatter = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) - .withZone(ZoneId.systemDefault()); + final var formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US) + .withZone(ZoneId.systemDefault()); pw.print(prefix); - pw.println("ImeTrackerService#History.mLiveEntries:"); + pw.println("ImeTrackerService#History.mLiveEntries: " + + mLiveEntries.size() + " elements"); - for (final Entry entry: mLiveEntries.values()) { + for (final var entry: mLiveEntries.values()) { dumpEntry(entry, pw, prefix, formatter); } pw.print(prefix); - pw.println("ImeTrackerService#History.mEntries:"); + pw.println("ImeTrackerService#History.mEntries: " + + mEntries.size() + " elements"); - for (final Entry entry: mEntries) { + for (final var entry: mEntries) { dumpEntry(entry, pw, prefix, formatter); } } @@ -255,34 +279,22 @@ public final class ImeTrackerService extends IImeTracker.Stub { private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw, @NonNull String prefix, @NonNull DateTimeFormatter formatter) { pw.print(prefix); - pw.println("ImeTrackerService#History #" + entry.mSequenceNumber + ":"); + pw.print(" #" + entry.mSequenceNumber); + pw.print(" " + ImeTracker.Debug.typeToString(entry.mType)); + pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus)); + pw.print(" - " + entry.mTag); + pw.println(" (" + entry.mDuration + "ms):"); pw.print(prefix); - pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime))); + pw.print(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime))); + pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin)); pw.print(prefix); - pw.println(" duration=" + entry.mDuration + "ms"); + pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason)); + pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase)); pw.print(prefix); - pw.print(" type=" + ImeTracker.Debug.typeToString(entry.mType)); - - pw.print(prefix); - pw.print(" status=" + ImeTracker.Debug.statusToString(entry.mStatus)); - - pw.print(prefix); - pw.print(" origin=" - + ImeTracker.Debug.originToString(entry.mOrigin)); - - pw.print(prefix); - pw.print(" reason=" - + InputMethodDebug.softInputDisplayReasonToString(entry.mReason)); - - pw.print(prefix); - pw.print(" phase=" - + ImeTracker.Debug.phaseToString(entry.mPhase)); - - pw.print(prefix); - pw.print(" requestWindowName=" + entry.mRequestWindowName); + pw.println(" requestWindowName=" + entry.mRequestWindowName); } /** A history entry. */ @@ -291,6 +303,10 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** The entry's sequence number in the history. */ private final int mSequenceNumber = sSequenceNumber.getAndIncrement(); + /** Logging tag, of the shape "component:random_hexadecimal". */ + @NonNull + private final String mTag; + /** Uid of the client that requested the IME. */ private final int mUid; @@ -323,13 +339,15 @@ public final class ImeTrackerService extends IImeTracker.Stub { /** * Name of the window that created the IME request. * - * Note: This is later set through {@link #onImmsUpdate(IBinder, String)}. + * Note: This is later set through {@link #onImmsUpdate}. */ @NonNull private String mRequestWindowName = "not set"; - private Entry(int uid, @ImeTracker.Type int type, @ImeTracker.Status int status, - @ImeTracker.Origin int origin, @SoftInputShowHideReason int reason) { + private Entry(@NonNull String tag, int uid, @ImeTracker.Type int type, + @ImeTracker.Status int status, @ImeTracker.Origin int origin, + @SoftInputShowHideReason int reason) { + mTag = tag; mUid = uid; mType = type; mStatus = status; diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 65d7399378a4e..8e13093b93364 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -145,6 +145,7 @@ import com.android.internal.content.PackageMonitor; import com.android.internal.infra.AndroidFuture; import com.android.internal.inputmethod.DirectBootAwareness; import com.android.internal.inputmethod.IAccessibilityInputMethodSession; +import com.android.internal.inputmethod.IImeTracker; import com.android.internal.inputmethod.IInlineSuggestionsRequestCallback; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethod; @@ -168,7 +169,6 @@ import com.android.internal.os.TransferPipe; import com.android.internal.util.ArrayUtils; import com.android.internal.util.ConcurrentUtils; import com.android.internal.util.DumpUtils; -import com.android.internal.view.IImeTracker; import com.android.internal.view.IInputMethodManager; import com.android.server.AccessibilityManagerInternal; import com.android.server.EventLogTags; @@ -4715,7 +4715,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub info.imeSurfaceParentName)); if (statsToken != null) { - mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName); + mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); } }