/* * 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 * *
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. * *
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