Merge "Implement ImeTrackerService"

This commit is contained in:
Cosmin Băieș
2022-12-22 07:51:59 +00:00
committed by Android (Google) Code Review
14 changed files with 1019 additions and 230 deletions

View File

@@ -3256,6 +3256,7 @@ package android.view.inputmethod {
method public int getDisplayId();
method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
method public boolean hasActiveInputConnection(@Nullable android.view.View);
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean hasPendingImeVisibilityRequests();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L

View File

@@ -16,6 +16,7 @@
package android.view;
import static android.os.Trace.TRACE_TAG_VIEW;
import static android.view.ImeInsetsSourceConsumerProto.INSETS_SOURCE_CONSUMER;
import static android.view.ImeInsetsSourceConsumerProto.IS_HIDE_ANIMATION_RUNNING;
import static android.view.ImeInsetsSourceConsumerProto.IS_REQUESTED_VISIBLE_AWAITING_CONTROL;
@@ -23,11 +24,15 @@ import static android.view.ImeInsetsSourceConsumerProto.IS_SHOW_REQUESTED_DURING
import android.annotation.Nullable;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.inputmethod.ImeTracker;
import android.view.inputmethod.InputMethodManager;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import java.util.function.Supplier;
@@ -48,8 +53,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
/**
* Tracks whether {@link WindowInsetsController#show(int)} or
* {@link InputMethodManager#showSoftInput(View, int)} is called during IME hide animation.
* If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder)},
* because the IME is being shown.
* If it was called, we should not call {@link InputMethodManager#notifyImeHidden(IBinder,
* ImeTracker.Token)}, because the IME is being shown.
*/
private boolean mIsShowRequestedDuringHideAnimation;
@@ -76,7 +81,7 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
// Remove IME surface as IME has finished hide animation, if there is no pending
// show request.
if (!mIsShowRequestedDuringHideAnimation) {
notifyHidden();
notifyHidden(null /* statsToken */);
removeSurface();
}
}
@@ -120,7 +125,8 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
* @return @see {@link android.view.InsetsSourceConsumer.ShowResult}.
*/
@Override
public @ShowResult int requestShow(boolean fromIme) {
@ShowResult
public int requestShow(boolean fromIme, @Nullable ImeTracker.Token statsToken) {
if (fromIme) {
ImeTracing.getInstance().triggerClientDump(
"ImeInsetsSourceConsumer#requestShow",
@@ -129,6 +135,9 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
// TODO: ResultReceiver for IME.
// TODO: Set mShowOnNextImeRender to automatically show IME and guard it with a flag.
ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_REQUEST_SHOW);
if (getControl() == null) {
// If control is null, schedule to show IME when control is available.
mIsRequestedVisibleAwaitingControl = true;
@@ -140,16 +149,32 @@ public final class ImeInsetsSourceConsumer extends InsetsSourceConsumer {
return ShowResult.SHOW_IMMEDIATELY;
}
return getImm().requestImeShow(mController.getHost().getWindowToken())
return getImm().requestImeShow(mController.getHost().getWindowToken(), statsToken)
? ShowResult.IME_SHOW_DELAYED : ShowResult.IME_SHOW_FAILED;
}
/**
* Notify {@link com.android.server.inputmethod.InputMethodManagerService} that
* IME insets are hidden.
*
* @param statsToken the token tracking the current IME hide request or {@code null} otherwise.
*/
private void notifyHidden() {
getImm().notifyImeHidden(mController.getHost().getWindowToken());
private void notifyHidden(@Nullable ImeTracker.Token statsToken) {
// Create a new stats token to track the hide request when:
// - we do not already have one, or
// - we do already have one, but we have control and use the passed in token
// for the insets animation already.
if (statsToken == null || getControl() != null) {
statsToken = ImeTracker.get().onRequestHide(null /* component */, Process.myUid(),
ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
ImeTracker.get().onProgress(statsToken,
ImeTracker.PHASE_CLIENT_INSETS_CONSUMER_NOTIFY_HIDDEN);
getImm().notifyImeHidden(mController.getHost().getWindowToken(), statsToken);
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.hideRequestFromApi", 0);
}
@Override

View File

@@ -41,6 +41,7 @@ import android.graphics.Rect;
import android.os.CancellationSignal;
import android.os.Handler;
import android.os.IBinder;
import android.os.Process;
import android.os.Trace;
import android.text.TextUtils;
import android.util.ArraySet;
@@ -64,6 +65,7 @@ import android.view.inputmethod.InputMethodManager;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
@@ -977,7 +979,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Override
public void show(@InsetsType int types) {
show(types, false /* fromIme */, null /* statsToken */);
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
statsToken = ImeTracker.get().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
}
show(types, false /* fromIme */, statsToken);
}
@VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
@@ -1054,7 +1063,14 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
@Override
public void hide(@InsetsType int types) {
hide(types, false /* fromIme */, null /* statsToken */);
ImeTracker.Token statsToken = null;
if ((types & ime()) != 0) {
statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
hide(types, false /* fromIme */, statsToken);
}
@VisibleForTesting
@@ -1164,13 +1180,17 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
if (DEBUG) Log.d(TAG, "user animation disabled types: " + disabledTypes);
types &= ~mDisabledUserAnimationInsetsTypes;
if (fromIme && (disabledTypes & ime()) != 0
&& !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
// We've requested IMM to show IME, but the IME is not controllable. We need to
// cancel the request.
setRequestedVisibleTypes(0 /* visibleTypes */, ime());
if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
notifyVisibilityChanged();
if ((disabledTypes & ime()) != 0) {
ImeTracker.get().onFailed(statsToken,
ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
if (fromIme && !mState.getSource(mImeSourceConsumer.getId()).isVisible()) {
// We've requested IMM to show IME, but the IME is not controllable. We need to
// cancel the request.
setRequestedVisibleTypes(0 /* visibleTypes */, ime());
if (mImeSourceConsumer.onAnimationStateChanged(false /* running */)) {
notifyVisibilityChanged();
}
}
}
}
@@ -1182,6 +1202,8 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
Trace.asyncTraceEnd(TRACE_TAG_VIEW, "IC.showRequestFromApiToImeReady", 0);
return;
}
ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_DISABLED_USER_ANIMATION);
cancelExistingControllers(types);
if (DEBUG) Log.d(TAG, "controlAnimation types: " + types);
mLastStartedAnimTypes |= types;
@@ -1189,7 +1211,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
final SparseArray<InsetsSourceControl> controls = new SparseArray<>();
Pair<Integer, Boolean> typesReadyPair = collectSourceControls(
fromIme, types, controls, animationType);
fromIme, types, controls, animationType, statsToken);
int typesReady = typesReadyPair.first;
boolean imeReady = typesReadyPair.second;
if (DEBUG) Log.d(TAG, String.format(
@@ -1288,7 +1310,10 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
* @return Pair of (types ready to animate, IME ready to animate).
*/
private Pair<Integer, Boolean> collectSourceControls(boolean fromIme, @InsetsType int types,
SparseArray<InsetsSourceControl> controls, @AnimationType int animationType) {
SparseArray<InsetsSourceControl> controls, @AnimationType int animationType,
@Nullable ImeTracker.Token statsToken) {
ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_COLLECT_SOURCE_CONTROLS);
int typesReady = 0;
boolean imeReady = true;
for (int i = mSourceConsumers.size() - 1; i >= 0; i--) {
@@ -1301,7 +1326,7 @@ public class InsetsController implements WindowInsetsController, InsetsAnimation
boolean canRun = true;
if (show) {
// Show request
switch(consumer.requestShow(fromIme)) {
switch(consumer.requestShow(fromIme, statsToken)) {
case ShowResult.SHOW_IMMEDIATELY:
break;
case ShowResult.IME_SHOW_DELAYED:

View File

@@ -35,6 +35,7 @@ import android.util.Log;
import android.util.proto.ProtoOutputStream;
import android.view.SurfaceControl.Transaction;
import android.view.WindowInsets.Type.InsetsType;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.VisibleForTesting;
@@ -50,10 +51,14 @@ import java.util.function.Supplier;
public class InsetsSourceConsumer {
@Retention(RetentionPolicy.SOURCE)
@IntDef(value = {ShowResult.SHOW_IMMEDIATELY, ShowResult.IME_SHOW_DELAYED, ShowResult.IME_SHOW_FAILED})
@IntDef(value = {
ShowResult.SHOW_IMMEDIATELY,
ShowResult.IME_SHOW_DELAYED,
ShowResult.IME_SHOW_FAILED
})
@interface ShowResult {
/**
* Window type is ready to be shown, will be shown immidiately.
* Window type is ready to be shown, will be shown immediately.
*/
int SHOW_IMMEDIATELY = 0;
/**
@@ -71,11 +76,13 @@ public class InsetsSourceConsumer {
protected final InsetsController mController;
protected final InsetsState mState;
private int mId;
private final @InsetsType int mType;
@InsetsType
private final int mType;
private static final String TAG = "InsetsSourceConsumer";
private final Supplier<Transaction> mTransactionSupplier;
private @Nullable InsetsSourceControl mSourceControl;
@Nullable
private InsetsSourceControl mSourceControl;
private boolean mHasWindowFocus;
/**
@@ -180,7 +187,7 @@ public class InsetsSourceConsumer {
return true;
}
@VisibleForTesting
@VisibleForTesting(visibility = PACKAGE)
public InsetsSourceControl getControl() {
return mSourceControl;
}
@@ -280,10 +287,16 @@ public class InsetsSourceConsumer {
* @param fromController {@code true} if request is coming from controller.
* (e.g. in IME case, controller is
* {@link android.inputmethodservice.InputMethodService}).
* @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*
* @implNote The {@code statsToken} is ignored here, and only handled in
* {@link ImeInsetsSourceConsumer} for IME animations only.
*
* @return @see {@link ShowResult}.
*/
@VisibleForTesting
public @ShowResult int requestShow(boolean fromController) {
@VisibleForTesting(visibility = PACKAGE)
@ShowResult
public int requestShow(boolean fromController, @Nullable ImeTracker.Token statsToken) {
return ShowResult.SHOW_IMMEDIATELY;
}

View File

@@ -40,6 +40,7 @@ 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;
@@ -61,6 +62,9 @@ final class IInputMethodManagerGlobalInvoker {
@Nullable
private static volatile IInputMethodManager sServiceCache = null;
@Nullable
private static volatile IImeTracker sTrackerServiceCache = null;
/**
* @return {@code true} if {@link IInputMethodManager} is available.
*/
@@ -527,4 +531,137 @@ final class IInputMethodManagerGlobalInvoker {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
@Nullable
static IBinder onRequestShow(int uid, @ImeTracker.Origin int origin,
@SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return null;
}
try {
return service.onRequestShow(uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
@Nullable
static IBinder onRequestHide(int uid, @ImeTracker.Origin int origin,
@SoftInputShowHideReason int reason) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return null;
}
try {
return service.onRequestHide(uid, origin, reason);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
static void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
service.onProgress(statsToken, phase);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
static void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
service.onFailed(statsToken, phase);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
static void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
service.onCancelled(statsToken, phase);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
static void onShown(@NonNull IBinder statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
service.onShown(statsToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
static void onHidden(@NonNull IBinder statsToken) {
final IImeTracker service = getImeTrackerService();
if (service == null) {
return;
}
try {
service.onHidden(statsToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
static boolean hasPendingImeVisibilityRequests() {
final var service = getImeTrackerService();
if (service == null) {
return true;
}
try {
return service.hasPendingImeVisibilityRequests();
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
@AnyThread
@Nullable
private static IImeTracker getImeTrackerService() {
var trackerService = sTrackerServiceCache;
if (trackerService == null) {
final var service = getService();
if (service == null) {
return null;
}
try {
trackerService = service.getImeTrackerService();
if (trackerService == null) {
return null;
}
sTrackerServiceCache = trackerService;
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
return trackerService;
}
}

View File

@@ -16,9 +16,6 @@
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;
@@ -46,6 +43,46 @@ public interface ImeTracker {
String TAG = "ImeTracker";
/** 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
*
@@ -61,25 +98,17 @@ public interface ImeTracker {
@Retention(RetentionPolicy.SOURCE)
@interface Origin {}
/**
* The IME show request originated in the client.
*/
int ORIGIN_CLIENT_SHOW_SOFT_INPUT = 0;
/** The IME show request originated in the client. */
int ORIGIN_CLIENT_SHOW_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_SHOW_SOFT_INPUT;
/**
* The IME hide request originated in the client.
*/
int ORIGIN_CLIENT_HIDE_SOFT_INPUT = 1;
/** The IME hide request originated in the client. */
int ORIGIN_CLIENT_HIDE_SOFT_INPUT = ImeProtoEnums.ORIGIN_CLIENT_HIDE_SOFT_INPUT;
/**
* The IME show request originated in the server.
*/
int ORIGIN_SERVER_START_INPUT = 2;
/** The IME show request originated in the server. */
int ORIGIN_SERVER_START_INPUT = ImeProtoEnums.ORIGIN_SERVER_START_INPUT;
/**
* The IME hide request originated in the server.
*/
int ORIGIN_SERVER_HIDE_INPUT = 3;
/** The IME hide request originated in the server. */
int ORIGIN_SERVER_HIDE_INPUT = ImeProtoEnums.ORIGIN_SERVER_HIDE_INPUT;
/**
* The current phase of the IME request.
@@ -88,6 +117,7 @@ public interface ImeTracker {
* 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,
@@ -121,6 +151,11 @@ public interface ImeTracker {
PHASE_CLIENT_HANDLE_HIDE_INSETS,
PHASE_CLIENT_APPLY_ANIMATION,
PHASE_CLIENT_CONTROL_ANIMATION,
PHASE_CLIENT_DISABLED_USER_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,
@@ -129,135 +164,172 @@ public interface ImeTracker {
@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 = 0;
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 = 1;
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 = 2;
int PHASE_SERVER_CLIENT_FOCUSED = ImeProtoEnums.PHASE_SERVER_CLIENT_FOCUSED;
/** The IME request complies with the current accessibility settings. */
int PHASE_SERVER_ACCESSIBILITY = 3;
int PHASE_SERVER_ACCESSIBILITY = ImeProtoEnums.PHASE_SERVER_ACCESSIBILITY;
/** The server is ready to run third party code. */
int PHASE_SERVER_SYSTEM_READY = 4;
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 = 5;
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 = 6;
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 = 7;
int PHASE_SERVER_WAIT_IME = ImeProtoEnums.PHASE_SERVER_WAIT_IME;
/** The server has a connection to the IME. */
int PHASE_SERVER_HAS_IME = 8;
int PHASE_SERVER_HAS_IME = ImeProtoEnums.PHASE_SERVER_HAS_IME;
/** The server decided the IME should be hidden. */
int PHASE_SERVER_SHOULD_HIDE = 9;
int PHASE_SERVER_SHOULD_HIDE = ImeProtoEnums.PHASE_SERVER_SHOULD_HIDE;
/** Reached the IME wrapper. */
int PHASE_IME_WRAPPER = 10;
int PHASE_IME_WRAPPER = ImeProtoEnums.PHASE_IME_WRAPPER;
/** Dispatched from the IME wrapper to the IME. */
int PHASE_IME_WRAPPER_DISPATCH = 11;
int PHASE_IME_WRAPPER_DISPATCH = ImeProtoEnums.PHASE_IME_WRAPPER_DISPATCH;
/** Reached the IME' showSoftInput method. */
int PHASE_IME_SHOW_SOFT_INPUT = 12;
int PHASE_IME_SHOW_SOFT_INPUT = ImeProtoEnums.PHASE_IME_SHOW_SOFT_INPUT;
/** Reached the IME' hideSoftInput method. */
int PHASE_IME_HIDE_SOFT_INPUT = 13;
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 = 14;
int PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE = ImeProtoEnums.PHASE_IME_ON_SHOW_SOFT_INPUT_TRUE;
/** Requested applying the IME visibility in the insets source consumer. */
int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER = 15;
int PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER =
ImeProtoEnums.PHASE_IME_APPLY_VISIBILITY_INSETS_CONSUMER;
/** Applied the IME visibility. */
int PHASE_SERVER_APPLY_IME_VISIBILITY = 16;
int PHASE_SERVER_APPLY_IME_VISIBILITY = ImeProtoEnums.PHASE_SERVER_APPLY_IME_VISIBILITY;
/** Created the show IME runner. */
int PHASE_WM_SHOW_IME_RUNNER = 17;
int PHASE_WM_SHOW_IME_RUNNER = ImeProtoEnums.PHASE_WM_SHOW_IME_RUNNER;
/** Ready to show IME. */
int PHASE_WM_SHOW_IME_READY = 18;
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 = 19;
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 = 20;
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 = 21;
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 = 22;
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 = 23;
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 = 24;
int PHASE_WM_REMOTE_INSETS_CONTROLLER = ImeProtoEnums.PHASE_WM_REMOTE_INSETS_CONTROLLER;
/** Created the IME window insets show animation. */
int PHASE_WM_ANIMATION_CREATE = 25;
int PHASE_WM_ANIMATION_CREATE = ImeProtoEnums.PHASE_WM_ANIMATION_CREATE;
/** Started the IME window insets show animation. */
int PHASE_WM_ANIMATION_RUNNING = 26;
int PHASE_WM_ANIMATION_RUNNING = ImeProtoEnums.PHASE_WM_ANIMATION_RUNNING;
/** Reached the client's show insets method. */
int PHASE_CLIENT_SHOW_INSETS = 27;
int PHASE_CLIENT_SHOW_INSETS = ImeProtoEnums.PHASE_CLIENT_SHOW_INSETS;
/** Reached the client's hide insets method. */
int PHASE_CLIENT_HIDE_INSETS = 28;
int PHASE_CLIENT_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HIDE_INSETS;
/** Handling the IME window insets show request. */
int PHASE_CLIENT_HANDLE_SHOW_INSETS = 29;
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 = 30;
int PHASE_CLIENT_HANDLE_HIDE_INSETS = ImeProtoEnums.PHASE_CLIENT_HANDLE_HIDE_INSETS;
/** Applied the IME window insets show animation. */
int PHASE_CLIENT_APPLY_ANIMATION = 31;
int PHASE_CLIENT_APPLY_ANIMATION = ImeProtoEnums.PHASE_CLIENT_APPLY_ANIMATION;
/** Started the IME window insets show animation. */
int PHASE_CLIENT_CONTROL_ANIMATION = 32;
int PHASE_CLIENT_CONTROL_ANIMATION = ImeProtoEnums.PHASE_CLIENT_CONTROL_ANIMATION;
/** Checked that the IME is controllable. */
int PHASE_CLIENT_DISABLED_USER_ANIMATION = ImeProtoEnums.PHASE_CLIENT_DISABLED_USER_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 = 33;
int PHASE_CLIENT_ANIMATION_RUNNING = ImeProtoEnums.PHASE_CLIENT_ANIMATION_RUNNING;
/** Cancelled the IME window insets show animation. */
int PHASE_CLIENT_ANIMATION_CANCEL = 34;
int PHASE_CLIENT_ANIMATION_CANCEL = ImeProtoEnums.PHASE_CLIENT_ANIMATION_CANCEL;
/** Finished the IME window insets show animation. */
int PHASE_CLIENT_ANIMATION_FINISHED_SHOW = 35;
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 = 36;
int PHASE_CLIENT_ANIMATION_FINISHED_HIDE = ImeProtoEnums.PHASE_CLIENT_ANIMATION_FINISHED_HIDE;
/**
* Called when an IME show request is created.
* Creates an IME show request tracking token.
*
* @param token the token tracking the current IME show request or {@code null} otherwise.
* @param component the component name where the IME show request was created,
* 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.
*
* @return An IME tracking token.
*/
void onRequestShow(@Nullable Token token, @Origin int origin,
@NonNull
Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason);
/**
* Called when an IME hide request is created.
* Creates an IME hide request tracking token.
*
* @param token the token tracking the current IME hide request or {@code null} otherwise.
* @param component the component name where the IME hide request was created,
* 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.
*
* @return An IME tracking token.
*/
void onRequestHide(@Nullable Token token, @Origin int origin,
@NonNull
Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason);
/**
@@ -313,112 +385,122 @@ public interface ImeTracker {
*/
@NonNull
static ImeTracker get() {
return SystemProperties.getBoolean("persist.debug.imetracker", false)
? LOGGER
: NOOP_LOGGER;
return LOGGER;
}
/** The singleton IME tracker instance. */
@NonNull
ImeTracker LOGGER = new ImeTracker() {
{
// Set logging flag initial value.
mLogProgress = SystemProperties.getBoolean("persist.debug.imetracker", false);
// Update logging flag dynamically.
SystemProperties.addChangeCallback(() ->
mLogProgress =
SystemProperties.getBoolean("persist.debug.imetracker", false));
}
/** Whether progress should be logged. */
private boolean mLogProgress;
@NonNull
@Override
public void onRequestShow(@Nullable Token token, int origin,
public Token onRequestShow(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onRequestShow at " + originToString(origin)
IBinder binder = IInputMethodManagerGlobalInvoker.onRequestShow(uid, origin, reason);
if (binder == null) binder = new Binder();
final Token token = Token.build(binder, component);
Log.i(TAG, token.mTag + ": onRequestShow at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
return token;
}
@NonNull
@Override
public void onRequestHide(@Nullable Token token, int origin,
public Token onRequestHide(@Nullable String component, int uid, @Origin int origin,
@SoftInputShowHideReason int reason) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onRequestHide at " + originToString(origin)
IBinder binder = IInputMethodManagerGlobalInvoker.onRequestHide(uid, origin, reason);
if (binder == null) binder = new Binder();
final Token token = Token.build(binder, component);
Log.i(TAG, token.mTag + ": onRequestHide at " + Debug.originToString(origin)
+ " reason " + InputMethodDebug.softInputDisplayReasonToString(reason));
return token;
}
@Override
public void onProgress(@Nullable Token token, int phase) {
public void onProgress(@Nullable Token token, @Phase int phase) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onProgress at " + phaseToString(phase));
IInputMethodManagerGlobalInvoker.onProgress(token.mBinder, phase);
if (mLogProgress) {
Log.i(TAG, token.mTag + ": onProgress at " + Debug.phaseToString(phase));
}
}
@Override
public void onFailed(@Nullable Token token, int phase) {
public void onFailed(@Nullable Token token, @Phase int phase) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onFailed at " + phaseToString(phase));
IInputMethodManagerGlobalInvoker.onFailed(token.mBinder, phase);
Log.i(TAG, token.mTag + ": onFailed at " + Debug.phaseToString(phase));
}
@Override
public void onTodo(@Nullable Token token, int phase) {
public void onTodo(@Nullable Token token, @Phase int phase) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onTodo at " + phaseToString(phase));
Log.i(TAG, token.mTag + ": onTodo at " + Debug.phaseToString(phase));
}
@Override
public void onCancelled(@Nullable Token token, int phase) {
public void onCancelled(@Nullable Token token, @Phase int phase) {
if (token == null) return;
Log.i(TAG, token.mTag + ": onCancelled at " + phaseToString(phase));
IInputMethodManagerGlobalInvoker.onCancelled(token.mBinder, 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.mBinder);
Log.i(TAG, token.mTag + ": onShown");
}
@Override
public void onHidden(@Nullable Token token) {
if (token == null) return;
IInputMethodManagerGlobalInvoker.onHidden(token.mBinder);
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;
@NonNull
public final IBinder mBinder;
@NonNull
private final String mTag;
public Token() {
this(ActivityThread.currentProcessName());
@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);
}
public Token(String component) {
this(new Binder(), component + ":" + Integer.toHexString((new Random().nextInt())));
}
private Token(IBinder binder, String tag) {
private Token(@NonNull IBinder binder, @NonNull String tag) {
mBinder = binder;
mTag = tag;
}
@@ -443,10 +525,11 @@ public interface ImeTracker {
@NonNull
public static final Creator<Token> CREATOR = new Creator<>() {
@NonNull
@Override
public Token createFromParcel(Parcel source) {
IBinder binder = source.readStrongBinder();
String tag = source.readString8();
final IBinder binder = source.readStrongBinder();
final String tag = source.readString8();
return new Token(binder, tag);
}
@@ -458,22 +541,34 @@ public interface ImeTracker {
}
/**
* Utilities for mapping phases and origins IntDef values to their names.
* 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.
*/
class Debug {
private static final Map<Integer, String> sTypes =
getFieldMapping(ImeTracker.class, "TYPE_");
private static final Map<Integer, String> sStatus =
getFieldMapping(ImeTracker.class, "STATUS_");
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) {
public static String typeToString(@Type int type) {
return sTypes.getOrDefault(type, "TYPE_" + type);
}
public static String statusToString(@Status int status) {
return sStatus.getOrDefault(status, "STATUS_" + status);
}
public static String originToString(@Origin int origin) {
return sOrigins.getOrDefault(origin, "ORIGIN_" + origin);
}
public static String phaseToString(int phase) {
public static String phaseToString(@Phase int phase) {
return sPhases.getOrDefault(phase, "PHASE_" + phase);
}

View File

@@ -2002,14 +2002,16 @@ public final class InputMethodManager {
* {@link #RESULT_HIDDEN}.
*/
public boolean showSoftInput(View view, int flags, ResultReceiver resultReceiver) {
return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
return showSoftInput(view, null /* statsToken */, flags, resultReceiver,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
}
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);
private boolean showSoftInput(View view, @Nullable ImeTracker.Token statsToken, int flags,
ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
if (statsToken == null) {
statsToken = ImeTracker.get().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT, reason);
}
ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
null /* icProto */);
@@ -2057,8 +2059,8 @@ 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,
final ImeTracker.Token statsToken = ImeTracker.get().onRequestShow(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_SHOW_SOFT_INPUT,
SoftInputShowHideReason.SHOW_SOFT_INPUT);
Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
@@ -2148,9 +2150,8 @@ 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);
final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT, reason);
ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
this, null /* icProto */);
@@ -2283,7 +2284,7 @@ public final class InputMethodManager {
hideSoftInputFromWindow(view.getWindowToken(), hideFlags, null,
SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
} else {
showSoftInput(view, showFlags, null,
showSoftInput(view, null /* statsToken */, showFlags, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
}
}
@@ -2796,8 +2797,8 @@ public final class InputMethodManager {
@UnsupportedAppUsage
void closeCurrentInput() {
final ImeTracker.Token statsToken = new ImeTracker.Token();
ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
final ImeTracker.Token statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT);
synchronized (mH) {
@@ -2856,18 +2857,23 @@ public final class InputMethodManager {
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored and returns {@code false}.
* @param statsToken the token tracking the current IME show request or {@code null} otherwise.
*
* @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
* @hide
*/
public boolean requestImeShow(IBinder windowToken) {
public boolean requestImeShow(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
checkFocus();
synchronized (mH) {
final View servedView = getServedViewLocked();
if (servedView == null || servedView.getWindowToken() != windowToken) {
ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
return false;
}
showSoftInput(servedView, 0 /* flags */, null /* resultReceiver */,
ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
return true;
}
@@ -2878,12 +2884,15 @@ public final class InputMethodManager {
*
* @param windowToken the window from which this request originates. If this doesn't match the
* currently served view, the request is ignored.
* @param statsToken the token tracking the current IME show request or {@code null} otherwise.
* @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);
public void notifyImeHidden(IBinder windowToken, @Nullable ImeTracker.Token statsToken) {
if (statsToken == null) {
statsToken = ImeTracker.get().onRequestHide(null /* component */,
Process.myUid(), ImeTracker.ORIGIN_CLIENT_HIDE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
}
ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
null /* icProto */);
@@ -3548,6 +3557,18 @@ public final class InputMethodManager {
return IInputMethodManagerGlobalInvoker.isInputMethodPickerShownForTest();
}
/**
* A test API for CTS to check whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
* @hide
*/
@TestApi
@RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
public boolean hasPendingImeVisibilityRequests() {
return IInputMethodManagerGlobalInvoker.hasPendingImeVisibilityRequests();
}
/**
* Show the settings for enabling subtypes of the specified input method.
*

View File

@@ -23,6 +23,7 @@ import android.os.IBinder;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.ImeProtoEnums;
import android.view.inputmethod.InputMethodManager;
import java.lang.annotation.Retention;
@@ -64,113 +65,114 @@ import java.lang.annotation.Retention;
SoftInputShowHideReason.HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT,
SoftInputShowHideReason.HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED,
SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION,
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR})
SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR
})
public @interface SoftInputShowHideReason {
/** Show soft input by {@link android.view.inputmethod.InputMethodManager#showSoftInput}. */
int SHOW_SOFT_INPUT = 0;
int SHOW_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_SOFT_INPUT;
/** Show soft input when {@code InputMethodManagerService#attachNewInputLocked} called. */
int ATTACH_NEW_INPUT = 1;
int ATTACH_NEW_INPUT = ImeProtoEnums.REASON_ATTACH_NEW_INPUT;
/** Show soft input by {@code InputMethodManagerService#showMySoftInput}. This is triggered when
* the IME process try to show the keyboard.
*
* @see android.inputmethodservice.InputMethodService#requestShowSelf(int)
*/
int SHOW_SOFT_INPUT_FROM_IME = 2;
int SHOW_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_FROM_IME;
/**
* Hide soft input by
* {@link android.view.inputmethod.InputMethodManager#hideSoftInputFromWindow}.
*/
int HIDE_SOFT_INPUT = 3;
int HIDE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#requestHideSelf(int)}.
*/
int HIDE_SOFT_INPUT_FROM_IME = 4;
int HIDE_SOFT_INPUT_FROM_IME = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_FROM_IME;
/**
* Show soft input when navigated forward to the window (with
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}} which the focused view is text
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION}) which the focused view is text
* editor and system will auto-show the IME when the window can resize or running on a large
* screen.
*/
int SHOW_AUTO_EDITOR_FORWARD_NAV = 5;
int SHOW_AUTO_EDITOR_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_AUTO_EDITOR_FORWARD_NAV;
/**
* Show soft input when navigated forward to the window with
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
* {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE}.
*/
int SHOW_STATE_VISIBLE_FORWARD_NAV = 6;
int SHOW_STATE_VISIBLE_FORWARD_NAV = ImeProtoEnums.REASON_SHOW_STATE_VISIBLE_FORWARD_NAV;
/**
* Show soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE}.
*/
int SHOW_STATE_ALWAYS_VISIBLE = 7;
int SHOW_STATE_ALWAYS_VISIBLE = ImeProtoEnums.REASON_SHOW_STATE_ALWAYS_VISIBLE;
/**
* Show soft input during {@code InputMethodManagerService} receive changes from
* {@code SettingsProvider}.
*/
int SHOW_SETTINGS_ON_CHANGE = 8;
int SHOW_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_SHOW_SETTINGS_ON_CHANGE;
/** Hide soft input during switching user. */
int HIDE_SWITCH_USER = 9;
int HIDE_SWITCH_USER = ImeProtoEnums.REASON_HIDE_SWITCH_USER;
/** Hide soft input when the user is invalid. */
int HIDE_INVALID_USER = 10;
int HIDE_INVALID_USER = ImeProtoEnums.REASON_HIDE_INVALID_USER;
/**
* Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_UNSPECIFIED} which
* the focused view is not text editor.
*/
int HIDE_UNSPECIFIED_WINDOW = 11;
int HIDE_UNSPECIFIED_WINDOW = ImeProtoEnums.REASON_HIDE_UNSPECIFIED_WINDOW;
/**
* Hide soft input when navigated forward to the window with
* {@link LayoutParams#SOFT_INPUT_IS_FORWARD_NAVIGATION} and
* {@link LayoutParams#SOFT_INPUT_STATE_HIDDEN}.
*/
int HIDE_STATE_HIDDEN_FORWARD_NAV = 12;
int HIDE_STATE_HIDDEN_FORWARD_NAV = ImeProtoEnums.REASON_HIDE_STATE_HIDDEN_FORWARD_NAV;
/**
* Hide soft input when the window with {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_HIDDEN}.
*/
int HIDE_ALWAYS_HIDDEN_STATE = 13;
int HIDE_ALWAYS_HIDDEN_STATE = ImeProtoEnums.REASON_HIDE_ALWAYS_HIDDEN_STATE;
/** Hide soft input when "adb shell ime <command>" called. */
int HIDE_RESET_SHELL_COMMAND = 14;
int HIDE_RESET_SHELL_COMMAND = ImeProtoEnums.REASON_HIDE_RESET_SHELL_COMMAND;
/**
* Hide soft input during {@code InputMethodManagerService} receive changes from
* {@code SettingsProvider}.
*/
int HIDE_SETTINGS_ON_CHANGE = 15;
int HIDE_SETTINGS_ON_CHANGE = ImeProtoEnums.REASON_HIDE_SETTINGS_ON_CHANGE;
/**
* Hide soft input from {@link com.android.server.policy.PhoneWindowManager} when setting
* {@link com.android.internal.R.integer#config_shortPressOnPowerBehavior} in config.xml as
* dismiss IME.
*/
int HIDE_POWER_BUTTON_GO_HOME = 16;
int HIDE_POWER_BUTTON_GO_HOME = ImeProtoEnums.REASON_HIDE_POWER_BUTTON_GO_HOME;
/** Hide soft input when attaching docked stack. */
int HIDE_DOCKED_STACK_ATTACHED = 17;
int HIDE_DOCKED_STACK_ATTACHED = ImeProtoEnums.REASON_HIDE_DOCKED_STACK_ATTACHED;
/**
* Hide soft input when {@link com.android.server.wm.RecentsAnimationController} starts
* intercept touch from app window.
*/
int HIDE_RECENTS_ANIMATION = 18;
int HIDE_RECENTS_ANIMATION = ImeProtoEnums.REASON_HIDE_RECENTS_ANIMATION;
/**
* Hide soft input when {@link com.android.wm.shell.bubbles.BubbleController} is expanding,
* switching, or collapsing Bubbles.
*/
int HIDE_BUBBLES = 19;
int HIDE_BUBBLES = ImeProtoEnums.REASON_HIDE_BUBBLES;
/**
* Hide soft input when focusing the same window (e.g. screen turned-off and turn-on) which no
@@ -183,74 +185,78 @@ public @interface SoftInputShowHideReason {
* only the dialog focused as it's the latest window with input focus) makes we need to hide
* soft-input when the same window focused again to align with the same behavior prior to R.
*/
int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR = 20;
int HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR =
ImeProtoEnums.REASON_HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR;
/**
* Hide soft input when a {@link com.android.internal.inputmethod.IInputMethodClient} is
* removed.
*/
int HIDE_REMOVE_CLIENT = 21;
int HIDE_REMOVE_CLIENT = ImeProtoEnums.REASON_HIDE_REMOVE_CLIENT;
/**
* Show soft input when the system invoking
* {@link com.android.server.wm.WindowManagerInternal#shouldRestoreImeVisibility}.
*/
int SHOW_RESTORE_IME_VISIBILITY = 22;
int SHOW_RESTORE_IME_VISIBILITY = ImeProtoEnums.REASON_SHOW_RESTORE_IME_VISIBILITY;
/**
* Show soft input by
* {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
*/
int SHOW_TOGGLE_SOFT_INPUT = 23;
int SHOW_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_SHOW_TOGGLE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.view.inputmethod.InputMethodManager#toggleSoftInput(int, int)};
*/
int HIDE_TOGGLE_SOFT_INPUT = 24;
int HIDE_TOGGLE_SOFT_INPUT = ImeProtoEnums.REASON_HIDE_TOGGLE_SOFT_INPUT;
/**
* Show soft input by
* {@link android.view.InsetsController#show(int)};
*/
int SHOW_SOFT_INPUT_BY_INSETS_API = 25;
int SHOW_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_SHOW_SOFT_INPUT_BY_INSETS_API;
/**
* Hide soft input if Ime policy has been set to {@link WindowManager#DISPLAY_IME_POLICY_HIDE}.
* See also {@code InputMethodManagerService#mImeHiddenByDisplayPolicy}.
*/
int HIDE_DISPLAY_IME_POLICY_HIDE = 26;
int HIDE_DISPLAY_IME_POLICY_HIDE = ImeProtoEnums.REASON_HIDE_DISPLAY_IME_POLICY_HIDE;
/**
* Hide soft input by {@link android.view.InsetsController#hide(int)}.
*/
int HIDE_SOFT_INPUT_BY_INSETS_API = 27;
int HIDE_SOFT_INPUT_BY_INSETS_API = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_INSETS_API;
/**
* Hide soft input by {@link android.inputmethodservice.InputMethodService#handleBack(boolean)}.
*/
int HIDE_SOFT_INPUT_BY_BACK_KEY = 28;
int HIDE_SOFT_INPUT_BY_BACK_KEY = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_BY_BACK_KEY;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#onToggleSoftInput(int, int)}.
*/
int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT = 29;
int HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT =
ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IME_TOGGLE_SOFT_INPUT;
/**
* Hide soft input by
* {@link android.inputmethodservice.InputMethodService#onExtractingInputChanged(EditorInfo)})}.
*/
int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED = 30;
int HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED =
ImeProtoEnums.REASON_HIDE_SOFT_INPUT_EXTRACT_INPUT_CHANGED;
/**
* Hide soft input by the deprecated
* {@link InputMethodManager#hideSoftInputFromInputMethod(IBinder, int)}.
*/
int HIDE_SOFT_INPUT_IMM_DEPRECATION = 31;
int HIDE_SOFT_INPUT_IMM_DEPRECATION = ImeProtoEnums.REASON_HIDE_SOFT_INPUT_IMM_DEPRECATION;
/**
* Hide soft input when the window gained focus without an editor from the IME shown window.
*/
int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR = 32;
int HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR =
ImeProtoEnums.REASON_HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR;
}

View File

@@ -0,0 +1,94 @@
/*
* 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 com.android.internal.view;
import android.view.inputmethod.ImeTracker;
/**
* Interface to the global Ime tracker, 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.
*
* @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.
*/
IBinder onRequestShow(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.
*
* @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.
*/
IBinder onRequestHide(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 phase the new phase the IME request reached.
*/
oneway void onProgress(in IBinder statsToken, int phase);
/**
* Called when the IME request fails.
*
* @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);
/**
* Called when the IME request is cancelled.
*
* @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);
/**
* Called when the IME show request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onShown(in IBinder statsToken);
/**
* Called when the IME hide request is successful.
*
* @param statsToken the token tracking the current IME request.
*/
oneway void onHidden(in IBinder statsToken);
/**
* Checks whether there are any pending IME visibility requests.
*
* @return {@code true} iff there are pending IME visibility requests.
*/
@EnforcePermission("TEST_INPUT_METHOD")
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
boolean hasPendingImeVisibilityRequests();
}

View File

@@ -27,6 +27,7 @@ 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
@@ -158,4 +159,10 @@ interface IInputMethodManager {
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+ "android.Manifest.permission.TEST_INPUT_METHOD)")
void setStylusWindowIdleTimeoutForTest(in IInputMethodClient client, long timeout);
/**
* Returns the singleton instance for the Ime Tracker Service.
* {@hide}
*/
IImeTracker getImeTrackerService();
}

View File

@@ -71,6 +71,7 @@ import android.view.WindowInsetsController.OnControllableInsetsChangedListener;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.animation.LinearInterpolator;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -136,7 +137,8 @@ public class InsetsControllerTest {
private boolean mImeRequestedShow;
@Override
public int requestShow(boolean fromController) {
public int requestShow(boolean fromController,
ImeTracker.Token statsToken) {
if (fromController || mImeRequestedShow) {
mImeRequestedShow = true;
return SHOW_IMMEDIATELY;

View File

@@ -43,6 +43,7 @@ import android.platform.test.annotations.Presubmit;
import android.view.SurfaceControl.Transaction;
import android.view.WindowManager.BadTokenException;
import android.view.WindowManager.LayoutParams;
import android.view.inputmethod.ImeTracker;
import android.widget.TextView;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -221,7 +222,8 @@ public class InsetsSourceConsumerTest {
return new InsetsSourceConsumer(ITYPE_IME, ime(), state,
() -> mMockTransaction, controller) {
@Override
public int requestShow(boolean fromController) {
public int requestShow(boolean fromController,
ImeTracker.Token statsToken) {
return SHOW_IMMEDIATELY;
}
};

View File

@@ -0,0 +1,338 @@
/*
* 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 com.android.server.inputmethod;
import android.Manifest;
import android.annotation.EnforcePermission;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.view.inputmethod.ImeTracker;
import com.android.internal.annotations.GuardedBy;
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;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayDeque;
import java.util.Locale;
import java.util.WeakHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Service for managing and logging {@link ImeTracker.Token} instances.
*
* @implNote Suppresses {@link GuardedBy} warnings, as linter reports that {@link #mHistory}
* interactions are guarded by {@code this} instead of {@code ImeTrackerService.this}, which should
* be identical.
*
* @hide
*/
@SuppressWarnings("GuardedBy")
public final class ImeTrackerService extends IImeTracker.Stub {
static final String TAG = "ImeTrackerService";
/** The threshold in milliseconds after which a history entry is considered timed out. */
private static final long TIMEOUT_MS = 10_000;
/** Handler for registering timeouts for live entries. */
private final Handler mHandler =
new Handler(Looper.myLooper(), null /* callback */, true /* async */);
/** Singleton instance of the History. */
@GuardedBy("ImeTrackerService.this")
private final History mHistory = new History();
@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);
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);
}
}, TIMEOUT_MS);
return binder;
}
@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);
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);
}
}, TIMEOUT_MS);
return binder;
}
@Override
public synchronized void onProgress(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
final History.Entry entry = mHistory.getEntry(statsToken);
if (entry == null) return;
entry.mPhase = phase;
}
@Override
public synchronized void onFailed(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_FAIL, phase);
}
@Override
public synchronized void onCancelled(@NonNull IBinder statsToken, @ImeTracker.Phase int phase) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_CANCEL, phase);
}
@Override
public synchronized void onShown(@NonNull IBinder statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
@Override
public synchronized void onHidden(@NonNull IBinder statsToken) {
mHistory.setFinished(statsToken, ImeTracker.STATUS_SUCCESS, ImeTracker.PHASE_NOT_SET);
}
/**
* Updates the IME request tracking token with new information available in IMMS.
*
* @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,
@NonNull String requestWindowName) {
final History.Entry entry = mHistory.getEntry(statsToken);
if (entry == null) return;
entry.mRequestWindowName = requestWindowName;
}
/** Dumps the contents of the history. */
public synchronized void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
mHistory.dump(pw, prefix);
}
@EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
@Override
public synchronized boolean hasPendingImeVisibilityRequests() {
super.hasPendingImeVisibilityRequests_enforcePermission();
return !mHistory.mLiveEntries.isEmpty();
}
/**
* A circular buffer storing the most recent few {@link ImeTracker.Token} entries information.
*/
private static final class History {
/** The circular buffer's capacity. */
private static final int CAPACITY = 100;
/** Backing store for the circular buffer. */
@GuardedBy("ImeTrackerService.this")
private final ArrayDeque<Entry> mEntries = new ArrayDeque<>(CAPACITY);
/** Backing store for the live entries (i.e. entries that are not finished yet). */
@GuardedBy("ImeTrackerService.this")
private final WeakHashMap<IBinder, Entry> mLiveEntries = new WeakHashMap<>();
/** Latest entry sequence number. */
private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
/** Adds a live entry. */
@GuardedBy("ImeTrackerService.this")
private void addEntry(@NonNull IBinder statsToken, @NonNull Entry entry) {
mLiveEntries.put(statsToken, entry);
}
/** Gets the entry corresponding to the given IME tracking token, if it exists. */
@Nullable
@GuardedBy("ImeTrackerService.this")
private Entry getEntry(@NonNull IBinder statsToken) {
return mLiveEntries.get(statsToken);
}
/**
* Sets the live entry corresponding to the tracking token, if it exists, as finished,
* and uploads the data for metrics.
*
* @param statsToken the token corresponding to the current IME request.
* @param status the finish status of the IME request.
* @param phase the phase the IME request finished at, if it exists
* (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;
entry.mDuration = System.currentTimeMillis() - entry.mStartTime;
entry.mStatus = status;
if (phase != ImeTracker.PHASE_NOT_SET) {
entry.mPhase = phase;
}
// Remove excess entries overflowing capacity (plus one for the new entry).
while (mEntries.size() >= CAPACITY) {
mEntries.remove();
}
mEntries.offer(entry);
// Log newly finished entry.
FrameworkStatsLog.write(FrameworkStatsLog.IME_REQUEST_FINISHED, entry.mUid,
entry.mDuration, entry.mType, entry.mStatus, entry.mReason,
entry.mOrigin, entry.mPhase);
}
/** 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());
pw.print(prefix);
pw.println("ImeTrackerService#History.mLiveEntries:");
for (final Entry entry: mLiveEntries.values()) {
dumpEntry(entry, pw, prefix, formatter);
}
pw.print(prefix);
pw.println("ImeTrackerService#History.mEntries:");
for (final Entry entry: mEntries) {
dumpEntry(entry, pw, prefix, formatter);
}
}
@GuardedBy("ImeTrackerService.this")
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(prefix);
pw.println(" startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
pw.print(prefix);
pw.println(" duration=" + entry.mDuration + "ms");
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);
}
/** A history entry. */
private static final class Entry {
/** The entry's sequence number in the history. */
private final int mSequenceNumber = sSequenceNumber.getAndIncrement();
/** Uid of the client that requested the IME. */
private final int mUid;
/** Clock time in milliseconds when the IME request was created. */
private final long mStartTime = System.currentTimeMillis();
/** Duration in milliseconds of the IME request from start to end. */
private long mDuration = 0;
/** Type of the IME request. */
@ImeTracker.Type
private final int mType;
/** Status of the IME request. */
@ImeTracker.Status
private int mStatus;
/** Origin of the IME request. */
@ImeTracker.Origin
private final int mOrigin;
/** Reason for creating the IME request. */
@SoftInputShowHideReason
private final int mReason;
/** Latest phase of the IME request. */
@ImeTracker.Phase
private int mPhase = ImeTracker.PHASE_NOT_SET;
/**
* Name of the window that created the IME request.
*
* Note: This is later set through {@link #onImmsUpdate(IBinder, String)}.
*/
@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) {
mUid = uid;
mType = type;
mStatus = status;
mOrigin = origin;
mReason = reason;
}
}
}
}

View File

@@ -176,6 +176,7 @@ 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;
@@ -198,11 +199,12 @@ import java.io.IOException;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.security.InvalidParameterException;
import java.text.SimpleDateFormat;
import java.time.Instant;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.Objects;
@@ -919,8 +921,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final SimpleDateFormat dataFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
.withZone(ZoneId.systemDefault());
for (int i = 0; i < mEntries.length; ++i) {
final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -931,7 +934,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
pw.print(prefix);
pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ " (timestamp=" + entry.mTimestamp + ")");
pw.print(prefix);
@@ -999,7 +1002,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private static final int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 32;
/**
* Entry size for non low-RAM devices.
* Entry size for low-RAM devices.
*
* <p>TODO: Consider to follow what other system services have been doing to manage
* constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
@@ -1015,7 +1018,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
/**
* Backing store for the ring bugger.
* Backing store for the ring buffer.
*/
private final Entry[] mEntries = new Entry[getEntrySize()];
@@ -1095,8 +1098,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
}
void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
final SimpleDateFormat dataFormat =
new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
final DateTimeFormatter formatter =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS", Locale.US)
.withZone(ZoneId.systemDefault());
for (int i = 0; i < mEntries.length; ++i) {
final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
@@ -1107,7 +1111,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
pw.println("StartInput #" + entry.mSequenceNumber + ":");
pw.print(prefix);
pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+ " (timestamp=" + entry.mTimestamp + ")"
+ " reason="
+ InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
@@ -1149,6 +1153,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
private final SoftInputShowHideHistory mSoftInputShowHideHistory =
new SoftInputShowHideHistory();
@NonNull
private final ImeTrackerService mImeTrackerService = new ImeTrackerService();
class SettingsObserver extends ContentObserver {
int mUserId;
boolean mRegistered = false;
@@ -3405,13 +3412,11 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
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);
// TODO(b/261565259): to avoid using null, add package name in ClientState
final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
final int uid = mCurClient != null ? mCurClient.mUid : -1;
statsToken = ImeTracker.get().onRequestShow(packageName, uid,
ImeTracker.ORIGIN_SERVER_START_INPUT, reason);
}
mShowRequested = true;
@@ -3461,7 +3466,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
}
onShowHideSoftInputRequested(true /* show */, windowToken, reason);
onShowHideSoftInputRequested(true /* show */, windowToken, reason, statsToken);
}
mInputShown = true;
return true;
@@ -3508,12 +3513,18 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
// Create statsToken is none exists.
if (statsToken == null) {
String packageName = null;
if (mCurEditorInfo != null) {
packageName = mCurEditorInfo.packageName;
// TODO(b/261565259): to avoid using null, add package name in ClientState
final String packageName = (mCurEditorInfo != null) ? mCurEditorInfo.packageName : null;
final int uid;
if (mCurClient != null) {
uid = mCurClient.mUid;
} else if (mCurFocusedWindowClient != null) {
uid = mCurFocusedWindowClient.mUid;
} else {
uid = -1;
}
statsToken = new ImeTracker.Token(packageName);
ImeTracker.get().onRequestHide(statsToken, ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
statsToken = ImeTracker.get().onRequestHide(packageName, uid,
ImeTracker.ORIGIN_SERVER_HIDE_INPUT, reason);
}
if ((flags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
@@ -3565,7 +3576,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
InputMethodDebug.softInputDisplayReasonToString(reason),
InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode));
}
onShowHideSoftInputRequested(false /* show */, windowToken, reason);
onShowHideSoftInputRequested(false /* show */, windowToken, reason, statsToken);
}
res = true;
} else {
@@ -4781,6 +4792,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibility");
synchronized (ImfLock.class) {
if (!calledWithValidTokenLocked(token)) {
ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
return;
}
if (!setVisible) {
@@ -4846,7 +4858,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
/** Called right after {@link com.android.internal.inputmethod.IInputMethod#showSoftInput}. */
@GuardedBy("ImfLock.class")
private void onShowHideSoftInputRequested(boolean show, IBinder requestToken,
@SoftInputShowHideReason int reason) {
@SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken) {
final WindowManagerInternal.ImeTargetInfo info =
mWindowManagerInternal.onToggleImeRequested(
show, mCurFocusedWindow, requestToken, mCurTokenDisplayId);
@@ -4855,6 +4867,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode,
info.requestWindowName, info.imeControlTargetName, info.imeLayerTargetName,
info.imeSurfaceParentName));
mImeTrackerService.onImmsUpdate(statsToken.mBinder, info.requestWindowName);
}
@BinderThread
@@ -5994,6 +6008,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
p.println(" mSoftInputShowHideHistory:");
mSoftInputShowHideHistory.dump(pw, " ");
p.println(" mImeTrackerService#History:");
mImeTrackerService.dump(pw, " ");
}
// Exit here for critical dump, as remaining sections require IPCs to other processes.
@@ -6584,6 +6601,12 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
return true;
}
/** @hide */
@Override
public IImeTracker getImeTrackerService() {
return mImeTrackerService;
}
private static final class InputMethodPrivilegedOperationsImpl
extends IInputMethodPrivilegedOperations.Stub {
private final InputMethodManagerService mImms;