script-astra/Android/Sdk/sources/android-35/android/view/inputmethod/ImeTracker.java

912 lines
34 KiB
Java
Raw Normal View History

2025-01-20 15:15:20 +00:00
/*
* 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.InsetsController.ANIMATION_TYPE_HIDE;
import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
import static com.android.internal.inputmethod.InputMethodDebug.softInputDisplayReasonToString;
import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_HIDE_ANIMATION;
import static com.android.internal.jank.Cuj.CUJ_IME_INSETS_SHOW_ANIMATION;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_HIDDEN;
import static com.android.internal.util.LatencyTracker.ACTION_REQUEST_IME_SHOWN;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.os.Binder;
import android.os.IBinder;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.Process;
import android.os.SystemProperties;
import android.util.Log;
import android.view.InsetsController.AnimationType;
import android.view.SurfaceControl;
import android.view.View;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.annotations.VisibleForTesting.Visibility;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.jank.InteractionJankMonitor;
import com.android.internal.jank.InteractionJankMonitor.Configuration;
import com.android.internal.util.LatencyTracker;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.reflect.Field;
import java.util.Arrays;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.ThreadLocalRandom;
import java.util.stream.Collectors;
/** @hide */
public interface ImeTracker {
String TAG = "ImeTracker";
/** The debug flag for IME visibility event log. */
boolean DEBUG_IME_VISIBILITY = SystemProperties.getBoolean("persist.debug.imf_event", false);
/** The message to indicate if there is no valid {@link Token}. */
String TOKEN_NONE = "TOKEN_NONE";
/** The type of the IME request. */
@IntDef(prefix = { "TYPE_" }, value = {
TYPE_SHOW,
TYPE_HIDE
})
@Retention(RetentionPolicy.SOURCE)
@interface Type {}
/** IME show request type. */
int TYPE_SHOW = ImeProtoEnums.TYPE_SHOW;
/** IME hide request type. */
int TYPE_HIDE = ImeProtoEnums.TYPE_HIDE;
/** The status of the IME request. */
@IntDef(prefix = { "STATUS_" }, value = {
STATUS_RUN,
STATUS_CANCEL,
STATUS_FAIL,
STATUS_SUCCESS,
STATUS_TIMEOUT
})
@Retention(RetentionPolicy.SOURCE)
@interface Status {}
/** IME request running. */
int STATUS_RUN = ImeProtoEnums.STATUS_RUN;
/** IME request cancelled. */
int STATUS_CANCEL = ImeProtoEnums.STATUS_CANCEL;
/** IME request failed. */
int STATUS_FAIL = ImeProtoEnums.STATUS_FAIL;
/** IME request succeeded. */
int STATUS_SUCCESS = ImeProtoEnums.STATUS_SUCCESS;
/** IME request timed out. */
int STATUS_TIMEOUT = ImeProtoEnums.STATUS_TIMEOUT;
/**
* The origin of the IME request
*
* <p> The name follows the format {@code ORIGIN_x_...} where {@code x} denotes
* where the origin is (i.e. {@code ORIGIN_SERVER} occurs in the server).
*/
@IntDef(prefix = { "ORIGIN_" }, value = {
ORIGIN_CLIENT,
ORIGIN_SERVER,
ORIGIN_IME
})
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
/** The IME request originated in the client. */
int ORIGIN_CLIENT = ImeProtoEnums.ORIGIN_CLIENT;
/** The IME request originated in the server. */
int ORIGIN_SERVER = ImeProtoEnums.ORIGIN_SERVER;
/** The IME request originated in the IME. */
int ORIGIN_IME = ImeProtoEnums.ORIGIN_IME;
/** The IME request originated in the WindowManager Shell. */
int ORIGIN_WM_SHELL = ImeProtoEnums.ORIGIN_WM_SHELL;
/**
* The current phase of the IME request.
*
* <p> 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_NOT_SET,
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_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_COLLECT_SOURCE_CONTROLS,
PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW,
PHASE_CLIENT_REQUEST_IME_SHOW,
PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN,
PHASE_CLIENT_ANIMATION_RUNNING,
PHASE_CLIENT_ANIMATION_CANCEL,
PHASE_CLIENT_ANIMATION_FINISHED_SHOW,
PHASE_CLIENT_ANIMATION_FINISHED_HIDE,
PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT,
PHASE_IME_SHOW_WINDOW,
PHASE_IME_HIDE_WINDOW,
PHASE_IME_PRIVILEGED_OPERATIONS,
PHASE_SERVER_CURRENT_ACTIVE_IME,
})
@Retention(RetentionPolicy.SOURCE)
@interface Phase {}
int PHASE_NOT_SET = ImeProtoEnums.PHASE_NOT_SET;
/** The view that requested the IME has been served by the IMM. */
int PHASE_CLIENT_VIEW_SERVED = ImeProtoEnums.PHASE_CLIENT_VIEW_SERVED;
/** The IME client that requested the IME has window manager focus. */
int PHASE_SERVER_CLIENT_KNOWN = ImeProtoEnums.PHASE_SERVER_CLIENT_KNOWN;
/** The IME client that requested the IME has IME focus. */
int PHASE_SERVER_CLIENT_FOCUSED = ImeProtoEnums.PHASE_SERVER_CLIENT_FOCUSED;
/** The IME request complies with the current accessibility settings. */
int PHASE_SERVER_ACCESSIBILITY = ImeProtoEnums.PHASE_SERVER_ACCESSIBILITY;
/** The server is ready to run third party code. */
int PHASE_SERVER_SYSTEM_READY = ImeProtoEnums.PHASE_SERVER_SYSTEM_READY;
/** Checked the implicit hide request against any explicit show requests. */
int PHASE_SERVER_HIDE_IMPLICIT = ImeProtoEnums.PHASE_SERVER_HIDE_IMPLICIT;
/** Checked the not-always hide request against any forced show requests. */
int PHASE_SERVER_HIDE_NOT_ALWAYS = ImeProtoEnums.PHASE_SERVER_HIDE_NOT_ALWAYS;
/** The server is waiting for a connection to the IME. */
int PHASE_SERVER_WAIT_IME = ImeProtoEnums.PHASE_SERVER_WAIT_IME;
/** The server has a connection to the IME. */
int PHASE_SERVER_HAS_IME = ImeProtoEnums.PHASE_SERVER_HAS_IME;
/** The server decided the IME should be hidden. */
int PHASE_SERVER_SHOULD_HIDE = ImeProtoEnums.PHASE_SERVER_SHOULD_HIDE;
/** Reached the IME wrapper. */
int PHASE_IME_WRAPPER = ImeProtoEnums.PHASE_IME_WRAPPER;
/** Dispatched from the IME wrapper to the IME. */
int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
/** Reached the IME's showSoftInput method. */
int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
/** Reached the IME's hideSoftInput method. */
int PHASE_IME_HIDE_SOFT_INPUT = ImeProtoEnums.PHASE_IME_HIDE_SOFT_INPUT;
/** The server decided the IME should be shown. */
int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
/** Applied the IME visibility. */
int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
/** Started the show IME runner. */
int PHASE_WM_SHOW_IME_RUNNER = ImeProtoEnums.PHASE_WM_SHOW_IME_RUNNER;
/** Ready to show IME. */
int PHASE_WM_SHOW_IME_READY = ImeProtoEnums.PHASE_WM_SHOW_IME_READY;
/** The Window Manager has a connection to the IME insets control target. */
int PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET =
ImeProtoEnums.PHASE_WM_HAS_IME_INSETS_CONTROL_TARGET;
/** Reached the window insets control target's show insets method. */
int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS =
ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_SHOW_INSETS;
/** Reached the window insets control target's hide insets method. */
int PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS =
ImeProtoEnums.PHASE_WM_WINDOW_INSETS_CONTROL_TARGET_HIDE_INSETS;
/** Reached the remote insets control target's show insets method. */
int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS =
ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_SHOW_INSETS;
/** Reached the remote insets control target's hide insets method. */
int PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS =
ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROL_TARGET_HIDE_INSETS;
/** Reached the remote insets controller. */
int PHASE_WM_REMOTE_INSETS_CONTROLLER = ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROLLER;
/** Created the IME window insets show animation. */
int PHASE_WM_ANIMATION_CREATE = ImeProtoEnums.PHASE_WM_ANIMATION_CREATE;
/** Started the IME window insets show animation. */
int PHASE_WM_ANIMATION_RUNNING = ImeProtoEnums.PHASE_WM_ANIMATION_RUNNING;
/** Reached the client's show insets method. */
int PHASE_CLIENT_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_SHOW_INSETS;
/** Reached the client's hide insets method. */
int PHASE_CLIENT_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HIDE_INSETS;
/** Handling the IME window insets show request. */
int PHASE_CLIENT_HANDLE_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_SHOW_INSETS;
/** Handling the IME window insets hide request. */
int PHASE_CLIENT_HANDLE_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_HIDE_INSETS;
/** Applied the IME window insets show animation. */
int PHASE_CLIENT_APPLY_ANIMATION = ImeProtoEnums.PHASE_CLIENT_APPLY_ANIMATION;
/** Started the IME window insets show animation. */
int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
/** Collecting insets source controls. */
int PHASE_CLIENT_COLLECT_SOURCE_CONTROLS = ImeProtoEnums.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS;
/** Reached the insets source consumer's show request method. */
int PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW =
ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW;
/** Reached input method manager's request IME show method. */
int PHASE_CLIENT_REQUEST_IME_SHOW = ImeProtoEnums.PHASE_CLIENT_REQUEST_IME_SHOW;
/** Reached the insets source consumer's notify hidden method. */
int PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN =
ImeProtoEnums.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN;
/** Queued the IME window insets show animation. */
int PHASE_CLIENT_ANIMATION_RUNNING = ImeProtoEnums.PHASE_CLIENT_ANIMATION_RUNNING;
/** Cancelled the IME window insets show animation. */
int PHASE_CLIENT_ANIMATION_CANCEL = ImeProtoEnums.PHASE_CLIENT_ANIMATION_CANCEL;
/** Finished the IME window insets show animation. */
int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_SHOW;
/** Finished the IME window insets hide animation. */
int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_HIDE;
/** Aborted the request to show the IME post layout. */
int PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT =
ImeProtoEnums.PHASE_WM_ABORT_SHOW_IME_POST_LAYOUT;
/** Reached the IME's showWindow method. */
int PHASE_IME_SHOW_WINDOW = ImeProtoEnums.PHASE_IME_SHOW_WINDOW;
/** Reached the IME's hideWindow method. */
int PHASE_IME_HIDE_WINDOW = ImeProtoEnums.PHASE_IME_HIDE_WINDOW;
/** Reached the InputMethodPrivilegedOperations handler. */
int PHASE_IME_PRIVILEGED_OPERATIONS = ImeProtoEnums.PHASE_IME_PRIVILEGED_OPERATIONS;
/** Checked that the calling IME is the currently active IME. */
int PHASE_SERVER_CURRENT_ACTIVE_IME = ImeProtoEnums.PHASE_SERVER_CURRENT_ACTIVE_IME;
/**
* Called when an IME request is started.
*
* @param component the name of the component that started the request.
* @param uid the uid of the client that started the request.
* @param type the type of the request.
* @param origin the origin of the request.
* @param reason the reason for starting the request.
* @param fromUser whether this request was created directly from user interaction.
*
* @return An IME request tracking token.
*/
@NonNull
Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@SoftInputShowHideReason int reason, boolean fromUser);
/**
* Called when an IME request is started for the current process.
*
* @param type the type of the request.
* @param origin the origin of the request.
* @param reason the reason for starting the request.
* @param fromUser whether this request was created directly from user interaction.
*
* @return An IME request tracking token.
*/
@NonNull
default Token onStart(@Type int type, @Origin int origin, @SoftInputShowHideReason int reason,
boolean fromUser) {
return onStart(Process.myProcessName(), Process.myUid(), type, origin, reason, fromUser);
}
/**
* 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 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 request or {@code null} otherwise.
*/
void onHidden(@Nullable Token token);
/**
* Returns whether the current IME request was created due to a user interaction. This can
* only be {@code true} when running on the view's UI thread.
*
* @param view the view for which the IME was requested.
* @return {@code true} if this request is coming from a user interaction,
* {@code false} otherwise.
*/
static boolean isFromUser(@Nullable View view) {
if (view == null) {
return false;
}
final var handler = view.getHandler();
// Early return if not on the UI thread, to ensure safe access to getViewRootImpl() below.
if (handler == null || handler.getLooper() == null
|| !handler.getLooper().isCurrentThread()) {
return false;
}
final var viewRootImpl = view.getViewRootImpl();
return viewRootImpl != null && viewRootImpl.isHandlingPointerEvent();
}
/**
* Get the singleton request tracker instance.
*
* @return the singleton request tracker instance
*/
@NonNull
static ImeTracker forLogging() {
return LOGGER;
}
/**
* Get the singleton jank tracker instance.
*
* @return the singleton jank tracker instance
*/
@NonNull
static ImeJankTracker forJank() {
return JANK_TRACKER;
}
/**
* Get the singleton latency tracker instance.
*
* @return the singleton latency tracker instance
*/
@NonNull
static ImeLatencyTracker forLatency() {
return LATENCY_TRACKER;
}
/** The singleton IME tracker instance. */
@NonNull
ImeTracker LOGGER = new ImeTracker() {
{
// Read initial system properties.
reloadSystemProperties();
// Update when system properties change.
SystemProperties.addChangeCallback(this::reloadSystemProperties);
}
/** Whether {@link #onProgress} calls should be logged. */
private boolean mLogProgress;
/** Whether the stack trace at the request call site should be logged. */
private boolean mLogStackTrace;
private void reloadSystemProperties() {
mLogProgress = SystemProperties.getBoolean(
"persist.debug.imetracker", false);
mLogStackTrace = SystemProperties.getBoolean(
"persist.debug.imerequest.logstacktrace", false);
}
@NonNull
@Override
public Token onStart(@NonNull String component, int uid, @Type int type, @Origin int origin,
@SoftInputShowHideReason int reason, boolean fromUser) {
final var tag = Token.createTag(component);
final var token = IInputMethodManagerGlobalInvoker.onStart(tag, uid, type,
origin, reason, fromUser);
Log.i(TAG, token.mTag + ": onRequest" + (type == TYPE_SHOW ? "Show" : "Hide")
+ " at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason)
+ " fromUser " + fromUser,
mLogStackTrace ? new Throwable() : null);
return token;
}
@Override
public void onProgress(@Nullable Token token, @Phase int phase) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onProgress(token.mBinder, phase);
if (mLogProgress) {
Log.i(TAG, token.mTag + ": onProgress at " + Debug.phaseToString(phase));
}
}
@Override
public void onFailed(@Nullable Token token, @Phase int phase) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onFailed(token, phase);
Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
}
@Override
public void onTodo(@Nullable Token token, @Phase int phase) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onTodo at " + Debug.phaseToString(phase));
}
@Override
public void onCancelled(@Nullable Token token, @Phase int phase) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onCancelled(token, phase);
Log.i(TAG, token.mTag + ": onCancelled at " + Debug.phaseToString(phase));
}
@Override
public void onShown(@Nullable Token token) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onShown(token);
Log.i(TAG, token.mTag + ": onShown");
}
@Override
public void onHidden(@Nullable Token token) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onHidden(token);
Log.i(TAG, token.mTag + ": onHidden");
}
};
/** The singleton IME tracker instance for instrumenting jank metrics. */
ImeJankTracker JANK_TRACKER = new ImeJankTracker();
/** The singleton IME tracker instance for instrumenting latency metrics. */
ImeLatencyTracker LATENCY_TRACKER = new ImeLatencyTracker();
/** A token that tracks the progress of an IME request. */
final class Token implements Parcelable {
/** Empty binder, lazily initialized, used for empty token instantiation. */
@Nullable
private static IBinder sEmptyBinder;
/** The binder used to identify this token. */
@NonNull
private final IBinder mBinder;
/** Logging tag, of the shape "component:random_hexadecimal". */
@NonNull
private final String mTag;
public Token(@NonNull IBinder binder, @NonNull String tag) {
mBinder = binder;
mTag = tag;
}
private Token(@NonNull Parcel in) {
mBinder = in.readStrongBinder();
mTag = in.readString8();
}
/** Returns the binder used to identify this token. */
@NonNull
public IBinder getBinder() {
return mBinder;
}
/** Returns the logging tag of this token. */
@NonNull
public String getTag() {
return mTag;
}
/**
* Creates a logging tag.
*
* @param component the name of the component that created the IME request.
*/
@NonNull
private static String createTag(@NonNull String component) {
return component + ":" + Integer.toHexString(ThreadLocalRandom.current().nextInt());
}
/** Returns a new token with an empty binder. */
@NonNull
@VisibleForTesting(visibility = Visibility.PACKAGE)
public static Token empty() {
final var tag = createTag(Process.myProcessName());
return empty(tag);
}
/** Returns a new token with an empty binder and the given logging tag. */
@NonNull
static Token empty(@NonNull String tag) {
return new Token(getEmptyBinder(), tag);
}
/** Returns the empty binder instance for empty token creation, lazily initializing it. */
@NonNull
private static IBinder getEmptyBinder() {
if (sEmptyBinder == null) {
sEmptyBinder = new Binder();
}
return sEmptyBinder;
}
@Override
public String toString() {
return super.toString() + "(tag: " + mTag + ")";
}
/** For Parcelable, no special marshalled objects. */
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeStrongBinder(mBinder);
dest.writeString8(mTag);
}
@NonNull
public static final Creator<Token> CREATOR = new Creator<>() {
@NonNull
@Override
public Token createFromParcel(@NonNull Parcel in) {
return new Token(in);
}
@NonNull
@Override
public Token[] newArray(int size) {
return new Token[size];
}
};
}
/**
* Utilities for mapping IntDef values to their names.
*
* Note: This is held in a separate class so that it only gets initialized when actually needed.
*/
final class Debug {
@NonNull
private static final Map<Integer, String> sTypes =
getFieldMapping(ImeTracker.class, "TYPE_");
@NonNull
private static final Map<Integer, String> sStatus =
getFieldMapping(ImeTracker.class, "STATUS_");
@NonNull
private static final Map<Integer, String> sOrigins =
getFieldMapping(ImeTracker.class, "ORIGIN_");
@NonNull
private static final Map<Integer, String> 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);
}
@NonNull
private static Map<Integer, String> 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(@NonNull Field field) {
try {
return field.getInt(null);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* Context related to {@link InteractionJankMonitor}.
*/
interface InputMethodJankContext {
/**
* @return a context associated with a display
*/
Context getDisplayContext();
/**
* @return a SurfaceControl that is going to be monitored
*/
SurfaceControl getTargetSurfaceControl();
/**
* @return the package name of the host
*/
String getHostPackageName();
}
/**
* Context related to {@link LatencyTracker}.
*/
interface InputMethodLatencyContext {
/**
* @return a context associated with current application
*/
Context getAppContext();
}
/**
* A tracker instance which is in charge of communicating with {@link InteractionJankMonitor}.
* This class disallows instantiating from outside, use {@link #forJank()} to get the singleton.
*/
final class ImeJankTracker {
/**
* This class disallows instantiating from outside.
*/
private ImeJankTracker() {
}
/**
* Called when the animation, which is going to be monitored, starts.
*
* @param jankContext context which is needed by {@link InteractionJankMonitor}.
* @param animType the animation type.
* @param useSeparatedThread {@code true} if the animation is handled by the app,
* {@code false} if the animation will be scheduled on the
* {@link android.view.InsetsAnimationThread}.
*/
public void onRequestAnimation(@NonNull InputMethodJankContext jankContext,
@AnimationType int animType, boolean useSeparatedThread) {
final int cujType = getImeInsetsCujFromAnimation(animType);
if (jankContext.getDisplayContext() == null
|| jankContext.getTargetSurfaceControl() == null
|| cujType == -1) {
return;
}
final Configuration.Builder builder = Configuration.Builder.withSurface(
cujType,
jankContext.getDisplayContext(),
jankContext.getTargetSurfaceControl())
.setTag(String.format(Locale.US, "%d@%d@%s", animType,
useSeparatedThread ? 0 : 1, jankContext.getHostPackageName()));
InteractionJankMonitor.getInstance().begin(builder);
}
/**
* Called when the animation, which is going to be monitored, cancels.
*
* @param animType the animation type.
*/
public void onCancelAnimation(@AnimationType int animType) {
final int cujType = getImeInsetsCujFromAnimation(animType);
if (cujType != -1) {
InteractionJankMonitor.getInstance().cancel(cujType);
}
}
/**
* Called when the animation, which is going to be monitored, ends.
*
* @param animType the animation type.
*/
public void onFinishAnimation(@AnimationType int animType) {
final int cujType = getImeInsetsCujFromAnimation(animType);
if (cujType != -1) {
InteractionJankMonitor.getInstance().end(cujType);
}
}
/**
* A helper method to translate animation type to CUJ type for IME animations.
*
* @param animType the animation type.
* @return the integer in {@link com.android.internal.jank.Cuj.CujType},
* or {@code -1} if the animation type is not supported for tracking yet.
*/
private static int getImeInsetsCujFromAnimation(@AnimationType int animType) {
switch (animType) {
case ANIMATION_TYPE_SHOW:
return CUJ_IME_INSETS_SHOW_ANIMATION;
case ANIMATION_TYPE_HIDE:
return CUJ_IME_INSETS_HIDE_ANIMATION;
default:
return -1;
}
}
}
/**
* A tracker instance which is in charge of communicating with {@link LatencyTracker}.
* This class disallows instantiating from outside, use {@link #forLatency()}
* to get the singleton.
*/
final class ImeLatencyTracker {
/**
* This class disallows instantiating from outside.
*/
private ImeLatencyTracker() {
}
private boolean shouldMonitorLatency(@SoftInputShowHideReason int reason) {
return reason == SoftInputShowHideReason.SHOW_SOFT_INPUT
|| reason == SoftInputShowHideReason.HIDE_SOFT_INPUT
|| reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW
|| reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API
|| reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API
|| reason == SoftInputShowHideReason.SHOW_SOFT_INPUT_FROM_IME
|| reason == SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_IME;
}
public void onRequestShow(@Nullable Token token, @Origin int origin,
@SoftInputShowHideReason int reason,
@NonNull InputMethodLatencyContext latencyContext) {
if (!shouldMonitorLatency(reason)) return;
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionStart(
ACTION_REQUEST_IME_SHOWN,
softInputDisplayReasonToString(reason));
}
public void onRequestHide(@Nullable Token token, @Origin int origin,
@SoftInputShowHideReason int reason,
@NonNull InputMethodLatencyContext latencyContext) {
if (!shouldMonitorLatency(reason)) return;
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionStart(
ACTION_REQUEST_IME_HIDDEN,
softInputDisplayReasonToString(reason));
}
public void onShowFailed(@Nullable Token token, @Phase int phase,
@NonNull InputMethodLatencyContext latencyContext) {
onShowCancelled(token, phase, latencyContext);
}
public void onHideFailed(@Nullable Token token, @Phase int phase,
@NonNull InputMethodLatencyContext latencyContext) {
onHideCancelled(token, phase, latencyContext);
}
public void onShowCancelled(@Nullable Token token, @Phase int phase,
@NonNull InputMethodLatencyContext latencyContext) {
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionCancel(ACTION_REQUEST_IME_SHOWN);
}
public void onHideCancelled(@Nullable Token token, @Phase int phase,
@NonNull InputMethodLatencyContext latencyContext) {
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionCancel(ACTION_REQUEST_IME_HIDDEN);
}
public void onShown(@Nullable Token token,
@NonNull InputMethodLatencyContext latencyContext) {
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionEnd(ACTION_REQUEST_IME_SHOWN);
}
public void onHidden(@Nullable Token token,
@NonNull InputMethodLatencyContext latencyContext) {
LatencyTracker.getInstance(latencyContext.getAppContext())
.onActionEnd(ACTION_REQUEST_IME_HIDDEN);
}
}
}