/*
 * Copyright (C) 2007-2008 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.os.Trace.TRACE_TAG_WINDOW_MANAGER;
import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
import static android.view.inputmethod.Flags.initiationWithoutInputConnection;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.EDITOR_INFO;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_INSETS_SOURCE_CONSUMER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_CONNECTION_CALL;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INPUT_METHOD_MANAGER;
import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.VIEW_ROOT_IMPL;
import static android.view.inputmethod.InputMethodManagerProto.ACTIVE;
import static android.view.inputmethod.InputMethodManagerProto.CUR_ID;
import static android.view.inputmethod.InputMethodManagerProto.FULLSCREEN_MODE;
import static android.view.inputmethod.InputMethodManagerProto.NEXT_SERVED_VIEW;
import static android.view.inputmethod.InputMethodManagerProto.SERVED_CONNECTING;
import static android.view.inputmethod.InputMethodManagerProto.SERVED_VIEW;

import static com.android.internal.inputmethod.StartInputReason.BOUND_TO_IMMS;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.DisplayContext;
import android.annotation.DrawableRes;
import android.annotation.DurationMillisLong;
import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresFeature;
import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.TestApi;
import android.annotation.UiThread;
import android.annotation.UserIdInt;
import android.app.ActivityThread;
import android.app.PropertyInvalidatedCache;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.hardware.display.DisplayManager;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Process;
import android.os.ResultReceiver;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.Log;
import android.util.Pair;
import android.util.Pools.Pool;
import android.util.Pools.SimplePool;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.SparseArray;
import android.util.proto.ProtoOutputStream;
import android.view.Display;
import android.view.ImeFocusController;
import android.view.ImeInsetsSourceConsumer;
import android.view.InputChannel;
import android.view.InputEvent;
import android.view.InputEventSender;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
import android.view.autofill.AutofillId;
import android.view.autofill.AutofillManager;
import android.window.ImeOnBackInvokedDispatcher;
import android.window.WindowOnBackInvokedDispatcher;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.inputmethod.DirectBootAwareness;
import com.android.internal.inputmethod.IBooleanListener;
import com.android.internal.inputmethod.IConnectionlessHandwritingCallback;
import com.android.internal.inputmethod.IInputMethodClient;
import com.android.internal.inputmethod.IInputMethodSession;
import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
import com.android.internal.inputmethod.ImeTracing;
import com.android.internal.inputmethod.InputBindResult;
import com.android.internal.inputmethod.InputMethodDebug;
import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry;
import com.android.internal.inputmethod.SoftInputShowHideReason;
import com.android.internal.inputmethod.StartInputFlags;
import com.android.internal.inputmethod.StartInputReason;
import com.android.internal.inputmethod.UnbindReason;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputMethodManager;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Consumer;

/**
 * Central system API to the overall input method framework (IMF) architecture,
 * which arbitrates interaction between applications and the current input method.
 *
 * <p>Topics covered here:
 * <ol>
 * <li><a href="#ArchitectureOverview">Architecture Overview</a>
 * <li><a href="#Applications">Applications</a>
 * <li><a href="#InputMethods">Input Methods</a>
 * <li><a href="#Security">Security</a>
 * </ol>
 *
 * <a name="ArchitectureOverview"></a>
 * <h3>Architecture Overview</h3>
 *
 * <p>There are three primary parties involved in the input method
 * framework (IMF) architecture:</p>
 *
 * <ul>
 * <li> The <strong>input method manager</strong> as expressed by this class
 * is the central point of the system that manages interaction between all
 * other parts.  It is expressed as the client-side API here which exists
 * in each application context and communicates with a global system service
 * that manages the interaction across all processes.
 * <li> An <strong>input method (IME)</strong> implements a particular
 * interaction model allowing the user to generate text.  The system binds
 * to the current input method that is in use, causing it to be created and run,
 * and tells it when to hide and show its UI.  Only one IME is running at a time.
 * <li> Multiple <strong>client applications</strong> arbitrate with the input
 * method manager for input focus and control over the state of the IME.  Only
 * one such client is ever active (working with the IME) at a time.
 * </ul>
 *
 *
 * <a name="Applications"></a>
 * <h3>Applications</h3>
 *
 * <p>In most cases, applications that are using the standard
 * {@link android.widget.TextView} or its subclasses will have little they need
 * to do to work well with soft input methods.  The main things you need to
 * be aware of are:</p>
 *
 * <ul>
 * <li> Properly set the {@link android.R.attr#inputType} in your editable
 * text views, so that the input method will have enough context to help the
 * user in entering text into them.
 * <li> Deal well with losing screen space when the input method is
 * displayed.  Ideally an application should handle its window being resized
 * smaller, but it can rely on the system performing panning of the window
 * if needed.  You should set the {@link android.R.attr#windowSoftInputMode}
 * attribute on your activity or the corresponding values on windows you
 * create to help the system determine whether to pan or resize (it will
 * try to determine this automatically but may get it wrong).
 * <li> You can also control the preferred soft input state (open, closed, etc)
 * for your window using the same {@link android.R.attr#windowSoftInputMode}
 * attribute.
 * </ul>
 *
 * <p>More finer-grained control is available through the APIs here to directly
 * interact with the IMF and its IME -- either showing or hiding the input
 * area, letting the user pick an input method, etc.</p>
 *
 * <p>For the rare people amongst us writing their own text editors, you
 * will need to implement {@link android.view.View#onCreateInputConnection}
 * to return a new instance of your own {@link InputConnection} interface
 * allowing the IME to interact with your editor.</p>
 *
 *
 * <a name="InputMethods"></a>
 * <h3>Input Methods</h3>
 *
 * <p>An input method (IME) is implemented
 * as a {@link android.app.Service}, typically deriving from
 * {@link android.inputmethodservice.InputMethodService}.  It must provide
 * the core {@link InputMethod} interface, though this is normally handled by
 * {@link android.inputmethodservice.InputMethodService} and implementors will
 * only need to deal with the higher-level API there.</p>
 *
 * See the {@link android.inputmethodservice.InputMethodService} class for
 * more information on implementing IMEs.
 *
 *
 * <a name="Security"></a>
 * <h3>Security</h3>
 *
 * <p>There are a lot of security issues associated with input methods,
 * since they essentially have freedom to completely drive the UI and monitor
 * everything the user enters.  The Android input method framework also allows
 * arbitrary third party IMEs, so care must be taken to restrict their
 * selection and interactions.</p>
 *
 * <p>Here are some key points about the security architecture behind the
 * IMF:</p>
 *
 * <ul>
 * <li> <p>Only the system is allowed to directly access an IME's
 * {@link InputMethod} interface, via the
 * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission.  This is
 * enforced in the system by not binding to an input method service that does
 * not require this permission, so the system can guarantee no other untrusted
 * clients are accessing the current input method outside of its control.</p>
 *
 * <li> <p>There may be many client processes of the IMF, but only one may
 * be active at a time.  The inactive clients can not interact with key
 * parts of the IMF through the mechanisms described below.</p>
 *
 * <li> <p>Clients of an input method are only given access to its
 * {@link InputMethodSession} interface.  One instance of this interface is
 * created for each client, and only calls from the session associated with
 * the active client will be processed by the current IME.  This is enforced
 * by {@link android.inputmethodservice.AbstractInputMethodService} for normal
 * IMEs, but must be explicitly handled by an IME that is customizing the
 * raw {@link InputMethodSession} implementation.</p>
 *
 * <li> <p>Only the active client's {@link InputConnection} will accept
 * operations.  The IMF tells each client process whether it is active, and
 * the framework enforces that in inactive processes calls on to the current
 * InputConnection will be ignored.  This ensures that the current IME can
 * only deliver events and text edits to the UI that the user sees as
 * being in focus.</p>
 *
 * <li> <p>An IME can never interact with an {@link InputConnection} while
 * the screen is off.  This is enforced by making all clients inactive while
 * the screen is off, and prevents bad IMEs from driving the UI when the user
 * can not be aware of its behavior.</p>
 *
 * <li> <p>A client application can ask that the system let the user pick a
 * new IME, but can not programmatically switch to one itself.  This avoids
 * malicious applications from switching the user to their own IME, which
 * remains running when the user navigates away to another application.  An
 * IME, on the other hand, <em>is</em> allowed to programmatically switch
 * the system to another IME, since it already has full control of user
 * input.</p>
 *
 * <li> <p>The user must explicitly enable a new IME in settings before
 * they can switch to it, to confirm with the system that they know about it
 * and want to make it available for use.</p>
 * </ul>
 *
 * <p>If your app targets Android 11 (API level 30) or higher, the methods in
 * this class each return a filtered result by the rules of
 * <a href="/training/basics/intents/package-visibility">package visibility</a>,
 * except for the currently connected IME. Apps having a query for the
 * {@link InputMethod#SERVICE_INTERFACE} see all IMEs.</p>
 */
@SystemService(Context.INPUT_METHOD_SERVICE)
@RequiresFeature(PackageManager.FEATURE_INPUT_METHODS)
public final class InputMethodManager {
    private static final boolean DEBUG = false;
    private static final String TAG = "InputMethodManager";

    private static final String PENDING_EVENT_COUNTER = "aq:imm";

    private static final int NOT_A_SUBTYPE_ID = -1;

    /**
     * A constant that represents Voice IME.
     *
     * @see InputMethodSubtype#getMode()
     */
    private static final String SUBTYPE_MODE_VOICE = "voice";

    /**
     * Provide this to {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocus(int,
     * IInputMethodClient, IBinder, int, int, int, EditorInfo,
     * com.android.internal.inputmethod.IRemoteInputConnection, IRemoteAccessibilityInputConnection,
     * int, int, ImeOnBackInvokedDispatcher)} to receive
     * {@link android.window.OnBackInvokedCallback} registrations from IME.
     */
    private final ImeOnBackInvokedDispatcher mImeDispatcher =
            new ImeOnBackInvokedDispatcher(Handler.getMain()) {
        @Override
        public WindowOnBackInvokedDispatcher getReceivingDispatcher() {
            synchronized (mH) {
                return mCurRootView != null ? mCurRootView.getOnBackInvokedDispatcher() : null;
            }
        }
    };

    /**
     * A runnable that reports {@link InputConnection} opened event for calls to
     * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}.
     */
    private abstract static class ReportInputConnectionOpenedRunner implements Runnable {
        /**
         * Sequence number to track startInput requests to
         * {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocusAsync}
         */
        int mSequenceNum;
        ReportInputConnectionOpenedRunner(int sequenceNum) {
            this.mSequenceNum = sequenceNum;
        }
    }
    private ReportInputConnectionOpenedRunner mReportInputConnectionOpenedRunner;

    /**
     * Ensures that {@link #sInstance} becomes non-{@code null} for application that have directly
     * or indirectly relied on {@link #sInstance} via reflection or something like that.
     *
     * <p>Here are scenarios we know and there could be more scenarios we are not
     * aware of right know.</p>
     *
     * <ul>
     *     <li>Apps that directly access {@link #sInstance} via reflection, which is currently
     *     allowed because of {@link UnsupportedAppUsage} annotation.  Currently
     *     {@link android.view.WindowManagerGlobal#getWindowSession()} is likely to guarantee that
     *     {@link #sInstance} is not {@code null} when such an app is accessing it, but removing
     *     that code from {@link android.view.WindowManagerGlobal#getWindowSession()} can reveal
     *     untested code paths in their apps, which probably happen in an early startup time of that
     *     app.</li>
     *     <li>Apps that directly access {@link #peekInstance()} via reflection, which is currently
     *     allowed because of {@link UnsupportedAppUsage} annotation.  Currently
     *     {@link android.view.WindowManagerGlobal#getWindowSession()} is likely to guarantee that
     *     {@link #peekInstance()} returns non-{@code null} object when such an app is calling
     *     {@link #peekInstance()}, but removing that code from
     *     {@link android.view.WindowManagerGlobal#getWindowSession()} can reveal untested code
     *     paths in their apps, which probably happen in an early startup time of that app. The good
     *     news is that unlike {@link #sInstance}'s case we can at least work around this scenario
     *     by changing the semantics of {@link #peekInstance()}, which is currently defined as
     *     "retrieve the global {@link InputMethodManager} instance, if it exists" to something that
     *     always returns non-{@code null} {@link InputMethodManager}.  However, introducing such an
     *     workaround can also trigger different compatibility issues if {@link #peekInstance()} was
     *     called before {@link android.view.WindowManagerGlobal#getWindowSession()} and it expected
     *     {@link #peekInstance()} to return {@code null} as written in the JavaDoc.</li>
     * </ul>
     *
     * <p>Since this is purely a compatibility hack, this method must be used only from
     * {@link android.view.WindowManagerGlobal#getWindowSession()} and {@link #getInstance()}.</p>
     *
     * <p>TODO(Bug 116157766): Remove this method once we clean up {@link UnsupportedAppUsage}.</p>
     * @hide
     */
    public static void ensureDefaultInstanceForDefaultDisplayIfNecessary() {
        // Skip this call if we are in system_server, as the system code should not use this
        // deprecated instance.
        if (!ActivityThread.isSystem()) {
            forContextInternal(Display.DEFAULT_DISPLAY, Looper.getMainLooper());
        }
    }

    private static final Object sLock = new Object();

    /**
     * @deprecated This cannot be compatible with multi-display. Please do not use this.
     */
    @Deprecated
    @GuardedBy("sLock")
    @UnsupportedAppUsage
    static InputMethodManager sInstance;

    /**
     * Global map between display to {@link InputMethodManager}.
     *
     * <p>Currently this map works like a so-called leaky singleton.  Once an instance is registered
     * for the associated display ID, that instance will never be garbage collected.</p>
     *
     * <p>TODO(Bug 116699479): Implement instance clean up mechanism.</p>
     */
    @GuardedBy("sLock")
    private static final SparseArray<InputMethodManager> sInstanceMap = new SparseArray<>();

    /**
     * Timeout in milliseconds for delivering a key to an IME.
     */
    private static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;

    /** @hide */
    public static final int DISPATCH_IN_PROGRESS = -1;

    /** @hide */
    public static final int DISPATCH_NOT_HANDLED = 0;

    /** @hide */
    public static final int DISPATCH_HANDLED = 1;

    /** @hide */
    public static final int SHOW_IM_PICKER_MODE_AUTO = 0;
    /** @hide */
    public static final int SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES = 1;
    /** @hide */
    public static final int SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES = 2;

    /**
     * Clear {@link #SHOW_FORCED} flag when the next IME focused application changed.
     *
     * <p>
     * Note that when this flag enabled in server side, {@link #SHOW_FORCED} will no longer
     * affect the next focused application to keep showing IME, in case of unexpected IME visible
     * when the next focused app isn't be the IME requester. </p>
     *
     * @hide
     */
    @TestApi
    @ChangeId
    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.TIRAMISU)
    public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // This is a bug id.

    /**
     * If {@code true}, avoid calling the
     * {@link com.android.server.inputmethod.InputMethodManagerService InputMethodManagerService}
     * by skipping the call to {@link IInputMethodManager#startInputOrWindowGainedFocus}
     * when we are switching focus between two non-editable views. This saves the cost of a binder
     * call into the system server.
     * <p><b>Note:</b>
     * The default value is {@code true}.
     */
    private static final boolean OPTIMIZE_NONEDITABLE_VIEWS =
            SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true);

    /** @hide */
    @IntDef(flag = true, prefix = { "HANDWRITING_DELEGATE_FLAG_" }, value = {
            HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface HandwritingDelegateFlags {}

    /**
     * Flag indicating that views from the default home screen ({@link Intent#CATEGORY_HOME}) may
     * act as a handwriting delegator for the delegate editor view. If set, views from the home
     * screen package will be trusted for handwriting delegation, in addition to views in the {@code
     * delegatorPackageName} passed to
     * {@link #acceptStylusHandwritingDelegation(View, String, int, Executor, Consumer)} .
     */
    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
    public static final int HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED = 0x0001;

    /**
     * @deprecated Use {@link IInputMethodManagerGlobalInvoker} instead.
     */
    @Deprecated
    @UnsupportedAppUsage
    final IInputMethodManager mService;
    private final Looper mMainLooper;

    // For scheduling work on the main thread.  This also serves as our
    // global lock.
    // Remark on @UnsupportedAppUsage: there were context leaks on old versions
    // of android (b/37043700), so developers used this field to perform manual clean up.
    // Leaks were fixed, hacks were backported to AppCompatActivity,
    // so an access to the field is closed.
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
    final H mH;

    // Our generic input connection if the current target does not have its own.
    private final RemoteInputConnectionImpl mFallbackInputConnection;

    private final int mDisplayId;

    /**
     * True if this input method client is active, initially false.
     */
    @GuardedBy("mH")
    private boolean mActive = false;

    /**
     * {@code true} if next {@link ImeFocusController#onPostWindowFocus} needs to
     * restart input.
     */
    @GuardedBy("mH")
    private boolean mRestartOnNextWindowFocus = true;

    /**
     * As reported by IME through InputConnection.
     */
    @GuardedBy("mH")
    private boolean mFullscreenMode;

    // -----------------------------------------------------------

    /**
     * This is the view that should currently be served by an input method,
     * regardless of the state of setting that up.
     */
    @Nullable
    @GuardedBy("mH")
    private View mServedView;

    /**
     * This is the next view that will be served by the input method, when
     * we get around to updating things.
     */
    @Nullable
    @GuardedBy("mH")
    private View mNextServedView;

    /**
     * The latest {@link ViewRootImpl} that has, or most recently had, input method focus.
     *
     * <p>This value will be cleared when it becomes inactive and no longer has window focus.
     */
    @Nullable
    @GuardedBy("mH")
    ViewRootImpl mCurRootView;

    /**
     * Whether the {@link #mCurRootView} currently has window focus.
     */
    @GuardedBy("mH")
    boolean mCurRootViewWindowFocused;

    /**
     * This is set when we are in the process of connecting, to determine
     * when we have actually finished.
     */
    @GuardedBy("mH")
    private boolean mServedConnecting;

    /**
     * This is non-null when we have connected the served view; it holds
     * the attributes that were last retrieved from the served view and given
     * to the input connection.
     */
    @GuardedBy("mH")
    private EditorInfo mCurrentEditorInfo;

    @GuardedBy("mH")
    @Nullable
    private ViewFocusParameterInfo mPreviousViewFocusParameters;

    /**
     * The InputConnection that was last retrieved from the served view.
     */
    @GuardedBy("mH")
    private RemoteInputConnectionImpl mServedInputConnection;

    /**
     * The completions that were last provided by the served view.
     */
    @GuardedBy("mH")
    private CompletionInfo[] mCompletions;

    // Cursor position on the screen.
    @GuardedBy("mH")
    @UnsupportedAppUsage
    Rect mTmpCursorRect = new Rect();

    @GuardedBy("mH")
    @UnsupportedAppUsage
    Rect mCursorRect = new Rect();

    /** Cached value for {@link #isStylusHandwritingAvailable} for userId. */
    @GuardedBy("mH")
    private PropertyInvalidatedCache<Integer, Boolean> mStylusHandwritingAvailableCache;

    /** Cached value for {@link #isConnectionlessStylusHandwritingAvailable} for userId. */
    @GuardedBy("mH")
    private PropertyInvalidatedCache<Integer, Boolean>
            mConnectionlessStylusHandwritingAvailableCache;

    private static final String CACHE_KEY_STYLUS_HANDWRITING_PROPERTY =
            "cache_key.system_server.stylus_handwriting";
    private static final String CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY =
            "cache_key.system_server.connectionless_stylus_handwriting";

    @GuardedBy("mH")
    private int mCursorSelStart;
    @GuardedBy("mH")
    private int mCursorSelEnd;
    @GuardedBy("mH")
    private int mCursorCandStart;
    @GuardedBy("mH")
    private int mCursorCandEnd;
    @GuardedBy("mH")
    private int mInitialSelStart;
    @GuardedBy("mH")
    private int mInitialSelEnd;

    /**
     * Handler for {@link RemoteInputConnectionImpl#getInputConnection()}.
     */
    @GuardedBy("mH")
    private Handler mServedInputConnectionHandler;

    /**
     * The instance that has previously been sent to the input method.
     */
    @GuardedBy("mH")
    private CursorAnchorInfo mCursorAnchorInfo = null;

    // -----------------------------------------------------------

    /**
     * ID of the method we are bound to.
     *
     * @deprecated New code should use {@code mCurBindState.mImeId}.
     */
    @Deprecated
    @GuardedBy("mH")
    @UnsupportedAppUsage(trackingBug = 236937383,
            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
            publicAlternatives = "Apps should not change behavior based on the currently connected"
                    + " IME. If absolutely needed, use {@link InputMethodInfo#getId()} instead.")
    String mCurId;

    /**
     * Kept for {@link UnsupportedAppUsage}.  Not officially maintained.
     *
     * @deprecated New code should use {@code mCurBindState.mImeSession}.
     */
    @Deprecated
    @GuardedBy("mH")
    @Nullable
    @UnsupportedAppUsage(trackingBug = 236937383,
            maxTargetSdk = Build.VERSION_CODES.UPSIDE_DOWN_CAKE,
            publicAlternatives = "Use methods on {@link InputMethodManager} instead.")
    IInputMethodSession mCurMethod;

    /**
     * Encapsulates per-binding state from {@link InputBindResult}.
     */
    @GuardedBy("mH")
    @Nullable
    private BindState mCurBindState;

    /**
     * Encapsulates IPCs to the currently connected AccessibilityServices.
     */
    @Nullable
    @GuardedBy("mH")
    private final SparseArray<IAccessibilityInputMethodSessionInvoker>
            mAccessibilityInputMethodSession = new SparseArray<>();

    @GuardedBy("mH")
    private InputChannel mCurChannel;
    @GuardedBy("mH")
    private ImeInputEventSender mCurSender;

    private static final int REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE = 0x0;

    /**
     * The monitor mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
     * @deprecated This is kept for {@link UnsupportedAppUsage}.  Must not be used.
     */
    @Deprecated
    @GuardedBy("mH")
    private int mRequestUpdateCursorAnchorInfoMonitorMode = REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;

    /**
     * Applies the IME visibility and listens for other state changes.
     */
    @GuardedBy("mH")
    private ImeInsetsSourceConsumer mImeInsetsConsumer;

    @GuardedBy("mH")
    private final Pool<PendingEvent> mPendingEventPool = new SimplePool<>(20);
    @GuardedBy("mH")
    private final SparseArray<PendingEvent> mPendingEvents = new SparseArray<>(20);

    private final DelegateImpl mDelegate = new DelegateImpl();

    private static boolean sPreventImeStartupUnlessTextEditor;

    // -----------------------------------------------------------

    private static final int MSG_DUMP = 1;
    private static final int MSG_BIND = 2;
    private static final int MSG_UNBIND = 3;
    private static final int MSG_SET_ACTIVE = 4;
    private static final int MSG_SEND_INPUT_EVENT = 5;
    private static final int MSG_TIMEOUT_INPUT_EVENT = 6;
    private static final int MSG_FLUSH_INPUT_EVENT = 7;
    private static final int MSG_REPORT_FULLSCREEN_MODE = 10;
    private static final int MSG_BIND_ACCESSIBILITY_SERVICE = 11;
    private static final int MSG_UNBIND_ACCESSIBILITY_SERVICE = 12;
    private static final int MSG_SET_INTERACTIVE = 13;
    private static final int MSG_SET_VISIBILITY = 14;
    private static final int MSG_ON_SHOW_REQUESTED = 31;
    private static final int MSG_START_INPUT_RESULT = 40;

    /**
     * Calling this will invalidate Local stylus handwriting availability Cache which
     * forces the next query in any process to recompute the cache.
     * @hide
     */
    public static void invalidateLocalStylusHandwritingAvailabilityCaches() {
        PropertyInvalidatedCache.invalidateCache(CACHE_KEY_STYLUS_HANDWRITING_PROPERTY);
    }

    /**
     * Calling this will invalidate the local connectionless stylus handwriting availability cache,
     * which forces the next query in any process to recompute the cache.
     *
     * @hide
     */
    public static void invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches() {
        PropertyInvalidatedCache.invalidateCache(
                CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY);
    }

    private static boolean isAutofillUIShowing(View servedView) {
        AutofillManager afm = servedView.getContext().getSystemService(AutofillManager.class);
        return afm != null && afm.isAutofillUiShowing();
    }

    /**
     * Returns fallback {@link InputMethodManager} if the called one is not likely to be compatible
     * with the given {@code view}.
     *
     * @param view {@link View} to be checked.
     * @return {@code null} when it is unnecessary (or impossible) to use fallback
     *         {@link InputMethodManager} to which IME API calls need to be re-dispatched.
     *          Non-{@code null} {@link InputMethodManager} if this method believes it'd be safer to
     *          re-dispatch IME APIs calls on it.
     */
    @Nullable
    private InputMethodManager getFallbackInputMethodManagerIfNecessary(@Nullable View view) {
        if (view == null) {
            return null;
        }
        // As evidenced in Bug 118341760, view.getViewRootImpl().getDisplayId() is supposed to be
        // more reliable to determine with which display the given view is interacting than
        // view.getContext().getDisplayId() / view.getContext().getSystemService(), which can be
        // easily messed up by app developers (or library authors) by creating inconsistent
        // ContextWrapper objects that re-dispatch those methods to other Context such as
        // ApplicationContext.
        final ViewRootImpl viewRootImpl = view.getViewRootImpl();
        if (viewRootImpl == null) {
            return null;
        }
        final int viewRootDisplayId = viewRootImpl.getDisplayId();
        if (viewRootDisplayId == mDisplayId) {
            // Expected case.  Good to go.
            return null;
        }
        final InputMethodManager fallbackImm =
                viewRootImpl.mContext.getSystemService(InputMethodManager.class);
        if (fallbackImm == null) {
            Log.v(TAG, "b/117267690: Failed to get non-null fallback IMM. view=" + view);
            return null;
        }
        if (fallbackImm.mDisplayId != viewRootDisplayId) {
            Log.v(TAG, "b/117267690: Failed to get fallback IMM with expected displayId="
                    + viewRootDisplayId + " actual IMM#displayId=" + fallbackImm.mDisplayId
                    + " view=" + view);
            return null;
        }
        Log.v(TAG, "b/117267690: Display ID mismatch found."
                + " ViewRootImpl displayId=" + viewRootDisplayId
                + " InputMethodManager displayId=" + mDisplayId
                + ". Use the right InputMethodManager instance to avoid performance overhead.",
                new Throwable());
        return fallbackImm;
    }

    /**
     * An internal API that returns the {@link Context} of the current served view connected to
     * an input method.
     * @hide
     */
    Context getFallbackContextFromServedView() {
        synchronized (mH) {
            if (mCurRootView == null) {
                return null;
            }
            return mServedView != null ? mServedView.getContext() : null;
        }
    }

    private static boolean canStartInput(View servedView) {
        // We can start input ether the servedView has window focus
        // or the activity is showing autofill ui.
        return servedView.hasWindowFocus() || isAutofillUIShowing(servedView);
    }

    /**
     * Reports whether the IME is currently perceptible or not, according to the leash applied by
     * {@link android.view.WindowInsetsController}.
     * @hide
     */
    public void reportPerceptible(@NonNull IBinder windowToken, boolean perceptible) {
        IInputMethodManagerGlobalInvoker.reportPerceptibleAsync(windowToken, perceptible);
    }

    private final class DelegateImpl implements
            ImeFocusController.InputMethodManagerDelegate {

        @Override
        public void onPreWindowGainedFocus(ViewRootImpl viewRootImpl) {
            synchronized (mH) {
                setCurrentRootViewLocked(viewRootImpl);
                mCurRootViewWindowFocused = true;
            }
        }

        @Override
        public void onPostWindowGainedFocus(View viewForWindowFocus,
                @NonNull WindowManager.LayoutParams windowAttribute) {
            boolean forceFocus = false;
            synchronized (mH) {
                // Update mNextServedView when focusedView changed.
                onViewFocusChangedInternal(viewForWindowFocus, true);

                // Starting new input when the next focused view is same as served view but the
                // currently active connection (if any) is not associated with it.
                final boolean nextFocusIsServedView = mServedView == viewForWindowFocus;

                if (nextFocusIsServedView
                        && !hasActiveInputConnectionInternal(viewForWindowFocus)) {
                    forceFocus = true;
                }
            }

            final int softInputMode = windowAttribute.softInputMode;
            final int windowFlags = windowAttribute.flags;

            int startInputFlags = getStartInputFlags(viewForWindowFocus, 0);
            startInputFlags |= StartInputFlags.WINDOW_GAINED_FOCUS;

            ImeTracing.getInstance().triggerClientDump(
                    "InputMethodManager.DelegateImpl#startInputAsyncOnWindowFocusGain",
                    InputMethodManager.this, null /* icProto */);

            boolean checkFocusResult;
            synchronized (mH) {
                if (mCurRootView == null) {
                    return;
                }
                if (mRestartOnNextWindowFocus) {
                    if (DEBUG) Log.v(TAG, "Restarting due to mRestartOnNextWindowFocus as true");
                    mRestartOnNextWindowFocus = false;
                    forceFocus = true;
                }
                checkFocusResult = checkFocusInternalLocked(forceFocus, mCurRootView);
            }

            if (checkFocusResult) {
                // We need to restart input on the current focus view.  This
                // should be done in conjunction with telling the system service
                // about the window gaining focus, to help make the transition
                // smooth.
                if (startInputOnWindowFocusGainInternal(StartInputReason.WINDOW_FOCUS_GAIN,
                        viewForWindowFocus, startInputFlags, softInputMode, windowFlags)) {
                    return;
                }
            }

            synchronized (mH) {
                // For some reason we didn't do a startInput + windowFocusGain, so
                // we'll just do a window focus gain and call it a day.
                if (DEBUG) {
                    Log.v(TAG, "Reporting focus gain, without startInput");
                }

                // ignore the result
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");
                IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                        StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
                        viewForWindowFocus.getWindowToken(), startInputFlags, softInputMode,
                        windowFlags,
                        null,
                        null, null,
                        mCurRootView.mContext.getApplicationInfo().targetSdkVersion,
                        UserHandle.myUserId(), mImeDispatcher);
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            }
        }

        @Override
        public void onWindowLostFocus(@NonNull ViewRootImpl viewRootImpl) {
            synchronized (mH) {
                if (mCurRootView == viewRootImpl) {
                    mCurRootViewWindowFocused = false;

                    if (Flags.refactorInsetsController() && mCurRootView != null) {
                        final int softInputMode = mCurRootView.mWindowAttributes.softInputMode;
                        final int state =
                                softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE;
                        if (state == WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN) {
                            // when losing focus (e.g., by going to another window), we reset the
                            // requestedVisibleTypes of WindowInsetsController by hiding the IME
                            if (DEBUG) {
                                Log.d(TAG, "onWindowLostFocus, hiding IME because "
                                        + "of STATE_ALWAYS_HIDDEN");
                            }
                            mCurRootView.getInsetsController().hide(WindowInsets.Type.ime());
                        }
                    }

                    clearCurRootViewIfNeeded();
                }
            }
        }

        @Override
        public void onViewFocusChanged(@Nullable View view, boolean hasFocus) {
            onViewFocusChangedInternal(view, hasFocus);
        }

        @Override
        public void onScheduledCheckFocus(ViewRootImpl viewRootImpl) {
            synchronized (mH) {
                if (!checkFocusInternalLocked(false, viewRootImpl)) {
                    return;
                }
            }
            startInputOnWindowFocusGainInternal(StartInputReason.SCHEDULED_CHECK_FOCUS,
                    null /* focusedView */, 0 /* startInputFlags */, 0 /* softInputMode */,
                    0 /* windowFlags */);
        }

        @Override
        public void onViewDetachedFromWindow(View view, ViewRootImpl viewRootImpl) {
            synchronized (mH) {
                if (mCurRootView != view.getViewRootImpl()) {
                    return;
                }
                if (mNextServedView == view) {
                    mNextServedView = null;
                }
                if (mServedView == view) {
                    viewRootImpl.dispatchCheckFocus();
                }
            }
        }

        @Override
        public void onWindowDismissed(ViewRootImpl viewRootImpl) {
            synchronized (mH) {
                if (mCurRootView != viewRootImpl) {
                    return;
                }
                if (mServedView != null) {
                    finishInputLocked();
                }
                setCurrentRootViewLocked(null);
            }
        }

        @GuardedBy("mH")
        private void setCurrentRootViewLocked(ViewRootImpl rootView) {
            mImeDispatcher.switchRootView(mCurRootView, rootView);
            mCurRootView = rootView;
        }
    }

    /** @hide */
    public DelegateImpl getDelegate() {
        return mDelegate;
    }

    /**
     * Checks whether the active input connection (if any) is for the given view.
     *
     * <p>Note that {@code view} parameter does not take
     * {@link View#checkInputConnectionProxy(View)} into account. This method returns {@code true}
     * when and only when the specified {@code view} is the actual {@link View} instance that is
     * connected to the IME.</p>
     *
     * @param view {@link View} to be checked.
     * @return {@code true} if {@code view} is currently interacting with IME.
     * @hide
     */
    @TestApi
    public boolean hasActiveInputConnection(@Nullable View view) {
        synchronized (mH) {
            return mCurRootView != null
                    && view != null
                    && mServedView == view
                    && mServedInputConnection != null
                    && mServedInputConnection.isAssociatedWith(view)
                    && isImeSessionAvailableLocked();
        }
    }

    /**
     * Checks whether the active input connection (if any) is for the given view.
     *
     * Note that this method is only intended for restarting input after focus gain
     * (e.g. b/160391516), DO NOT leverage this method to do another check.
     */
    private boolean hasActiveInputConnectionInternal(@Nullable View view) {
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || !isImeSessionAvailableLocked()) {
                return false;
            }

            return mServedInputConnection != null
                    && mServedInputConnection.isAssociatedWith(view);
        }
    }

    private boolean startInputOnWindowFocusGainInternal(@StartInputReason int startInputReason,
            View focusedView, @StartInputFlags int startInputFlags,
            @SoftInputModeFlags int softInputMode, int windowFlags) {
        synchronized (mH) {
            mCurrentEditorInfo = null;
            mCompletions = null;
            mServedConnecting = true;
        }
        return startInputInner(startInputReason,
                focusedView != null ? focusedView.getWindowToken() : null, startInputFlags,
                softInputMode, windowFlags);
    }

    @GuardedBy("mH")
    private View getServedViewLocked() {
        return mCurRootView != null ? mServedView : null;
    }

    @GuardedBy("mH")
    private View getNextServedViewLocked() {
        return mCurRootView != null ? mNextServedView : null;
    }

    /**
     * Returns {@code true} when the given view has been served by Input Method.
     */
    @GuardedBy("mH")
    private boolean hasServedByInputMethodLocked(View view) {
        final View servedView = getServedViewLocked();
        return (servedView == view
                || (servedView != null && servedView.checkInputConnectionProxy(view)));
    }

    class H extends Handler {
        H(Looper looper) {
            super(looper, null, true);
        }

        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_DUMP: {
                    SomeArgs args = (SomeArgs)msg.obj;
                    try {
                        doDump((FileDescriptor)args.arg1,
                                (PrintWriter)args.arg2, (String[])args.arg3);
                    } catch (RuntimeException e) {
                        ((PrintWriter)args.arg2).println("Exception: " + e);
                    }
                    synchronized (args.arg4) {
                        ((CountDownLatch)args.arg4).countDown();
                    }
                    args.recycle();
                    return;
                }
                case MSG_BIND: {
                    final InputBindResult res = (InputBindResult) msg.obj;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_BIND " + res.sequence + "," + res.id);
                    }
                    synchronized (mH) {
                        final int curBindSequence = getBindSequenceLocked();
                        if (curBindSequence < 0 || curBindSequence != res.sequence) {
                            Log.w(TAG, "Ignoring onBind: cur seq=" + curBindSequence
                                    + ", given seq=" + res.sequence);
                            if (res.channel != null && res.channel != mCurChannel) {
                                res.channel.dispose();
                            }
                            return;
                        }

                        mRequestUpdateCursorAnchorInfoMonitorMode =
                                REQUEST_UPDATE_CURSOR_ANCHOR_INFO_NONE;

                        updateInputChannelLocked(res.channel);
                        mCurMethod = res.method; // for @UnsupportedAppUsage
                        mCurBindState = new BindState(res);
                        mCurId = res.id; // for @UnsupportedAppUsage
                    }
                    startInputInner(StartInputReason.BOUND_TO_IMMS, null, 0, 0, 0);
                    return;
                }

                case MSG_START_INPUT_RESULT: {
                    final InputBindResult res = (InputBindResult) msg.obj;
                    final int startInputSeq = msg.arg1;
                    if (res == null) {
                        // IMMS logs .wtf already.
                        return;
                    }
                    if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
                    synchronized (mH) {
                        if (res.id != null) {
                            updateInputChannelLocked(res.channel);
                            mCurMethod = res.method; // for @UnsupportedAppUsage
                            mCurBindState = new BindState(res);
                            mAccessibilityInputMethodSession.clear();
                            if (res.accessibilitySessions != null) {
                                for (int i = 0; i < res.accessibilitySessions.size(); i++) {
                                    IAccessibilityInputMethodSessionInvoker wrapper =
                                            IAccessibilityInputMethodSessionInvoker.createOrNull(
                                                    res.accessibilitySessions.valueAt(i));
                                    if (wrapper != null) {
                                        mAccessibilityInputMethodSession.append(
                                                res.accessibilitySessions.keyAt(i), wrapper);
                                    }
                                }
                            }
                            mCurId = res.id; // for @UnsupportedAppUsage
                        } else if (res.channel != null && res.channel != mCurChannel) {
                            res.channel.dispose();
                        }
                        switch (res.result) {
                            case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                                mRestartOnNextWindowFocus = true;
                                mServedView = null;
                                break;
                        }
                        if (mCompletions != null) {
                            if (isImeSessionAvailableLocked()) {
                                mCurBindState.mImeSession.displayCompletions(mCompletions);
                            }
                        }

                        if (res != null
                                && res.method != null
                                && mServedView != null
                                && mReportInputConnectionOpenedRunner != null
                                && mReportInputConnectionOpenedRunner.mSequenceNum
                                        == startInputSeq) {
                            mReportInputConnectionOpenedRunner.run();
                        }
                        mReportInputConnectionOpenedRunner = null;
                    }
                    return;
                }
                case MSG_UNBIND: {
                    final int sequence = msg.arg1;
                    @UnbindReason
                    final int reason = msg.arg2;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_UNBIND " + sequence +
                                " reason=" + InputMethodDebug.unbindReasonToString(reason));
                    }
                    final boolean startInput;
                    synchronized (mH) {
                        if (reason == UnbindReason.DISCONNECT_IME) {
                            mImeDispatcher.clear();
                        }
                        if (getBindSequenceLocked() != sequence) {
                            return;
                        }
                        clearAllAccessibilityBindingLocked();
                        clearBindingLocked();
                        // If we were actively using the last input method, then
                        // we would like to re-connect to the next input method.
                        final View servedView = getServedViewLocked();
                        if (servedView != null && servedView.isFocused()) {
                            mServedConnecting = true;
                        }
                        startInput = mActive;
                    }
                    if (startInput) {
                        startInputInner(
                                StartInputReason.UNBOUND_FROM_IMMS, null, 0, 0, 0);
                    }
                    return;
                }
                case MSG_BIND_ACCESSIBILITY_SERVICE: {
                    final int id = msg.arg1;
                    final InputBindResult res = (InputBindResult) msg.obj;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_BIND_ACCESSIBILITY " + res.sequence
                                + "," + res.id);
                    }
                    synchronized (mH) {
                        final int curBindSequence = getBindSequenceLocked();
                        if (curBindSequence < 0 || curBindSequence != res.sequence) {
                            Log.w(TAG, "Ignoring onBind: cur seq=" + curBindSequence
                                    + ", given seq=" + res.sequence);
                            if (res.channel != null && res.channel != mCurChannel) {
                                res.channel.dispose();
                            }
                            return;
                        }

                        // Since IMM can start inputting text before a11y sessions are back,
                        // we send a notification so that the a11y service knows the session is
                        // registered and update the a11y service with the current cursor positions.
                        if (res.accessibilitySessions != null) {
                            IAccessibilityInputMethodSessionInvoker invoker =
                                    IAccessibilityInputMethodSessionInvoker.createOrNull(
                                            res.accessibilitySessions.get(id));
                            if (invoker != null) {
                                mAccessibilityInputMethodSession.put(id, invoker);
                                if (mServedInputConnection != null) {
                                    invoker.updateSelection(mInitialSelStart, mInitialSelEnd,
                                            mCursorSelStart, mCursorSelEnd, mCursorCandStart,
                                            mCursorCandEnd);
                                } else {
                                    // If an a11y service binds before input starts, we should still
                                    // send a notification because the a11y service doesn't know it
                                    // binds before or after input starts, it may wonder if it binds
                                    // after input starts, why it doesn't receive a notification of
                                    // the current cursor positions.
                                    invoker.updateSelection(-1, -1, -1, -1, -1, -1);
                                }
                            }
                        }
                    }
                    startInputInner(StartInputReason.BOUND_ACCESSIBILITY_SESSION_TO_IMMS, null,
                            0, 0, 0);
                    return;
                }
                case MSG_UNBIND_ACCESSIBILITY_SERVICE: {
                    final int sequence = msg.arg1;
                    final int id = msg.arg2;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_UNBIND_ACCESSIBILITY_SERVICE "
                                + sequence + " id=" + id);
                    }
                    synchronized (mH) {
                        if (getBindSequenceLocked() != sequence) {
                            if (DEBUG) {
                                Log.i(TAG, "current BindSequence =" + getBindSequenceLocked()
                                        + " sequence =" + sequence + " id=" + id);
                            }
                            return;
                        }
                        clearAccessibilityBindingLocked(id);
                    }
                    return;
                }
                case MSG_SET_ACTIVE: {
                    final boolean active = msg.arg1 != 0;
                    final boolean fullscreen = msg.arg2 != 0;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive);
                    }
                    synchronized (mH) {
                        mActive = active;
                        mFullscreenMode = fullscreen;

                        if (!active) {
                            // Some other client has starting using the IME, so note
                            // that this happened and make sure our own editor's
                            // state is reset.
                            mRestartOnNextWindowFocus = true;
                            // Note that finishComposingText() is allowed to run
                            // even when we are not active.
                            mFallbackInputConnection.finishComposingTextFromImm();

                            if (clearCurRootViewIfNeeded()) {
                                return;
                            }
                        }
                        // Check focus again in case that "onWindowFocus" is called before
                        // handling this message.
                        final View servedView = getServedViewLocked();
                        if (servedView == null || !canStartInput(servedView)) {
                            return;
                        }
                        if (mCurRootView == null) {
                            return;
                        }
                        if (!checkFocusInternalLocked(mRestartOnNextWindowFocus, mCurRootView)) {
                            return;
                        }
                        mCurrentEditorInfo = null;
                        mCompletions = null;
                        mServedConnecting = true;
                    }
                    final int reason = active ? StartInputReason.ACTIVATED_BY_IMMS
                            : StartInputReason.DEACTIVATED_BY_IMMS;
                    startInputInner(reason, null, 0, 0, 0);
                    return;
                }
                case MSG_SET_INTERACTIVE: {
                    final boolean interactive = msg.arg1 != 0;
                    final boolean fullscreen = msg.arg2 != 0;
                    if (DEBUG) {
                        Log.i(TAG, "handleMessage: MSG_SET_INTERACTIVE " + interactive
                                + ", was " + mActive);
                    }
                    synchronized (mH) {
                        mActive = interactive;
                        mFullscreenMode = fullscreen;
                        if (interactive) {
                            // Find the next view focus to start the input connection when the
                            // device was interactive.
                            final View rootView =
                                    mCurRootView != null ? mCurRootView.getView() : null;
                            if (rootView == null) {
                                // No window focused or view was removed, ignore request.
                                return;
                            }
                            final ViewRootImpl currentViewRootImpl = mCurRootView;
                            // Post this on UI thread as required for view focus code.
                            rootView.post(() -> {
                                synchronized (mH) {
                                    if (mCurRootView != currentViewRootImpl) {
                                        // Focused window changed since posting, ignore request.
                                        return;
                                    }
                                }
                                final View curRootView = currentViewRootImpl.getView();
                                if (curRootView == null) {
                                    // View was removed, ignore request.
                                    return;
                                }
                                final View focusedView = curRootView.findFocus();
                                onViewFocusChangedInternal(focusedView, focusedView != null);
                            });
                        } else {
                            // Finish input connection when device becomes non-interactive.
                            finishInputLocked();
                            if (isImeSessionAvailableLocked()) {
                                mCurBindState.mImeSession.finishInput();
                            }
                            forAccessibilitySessionsLocked(
                                    IAccessibilityInputMethodSessionInvoker::finishInput);
                        }
                    }
                    return;
                }
                case MSG_SET_VISIBILITY:
                    final boolean visible = msg.arg1 != 0;
                    synchronized (mH) {
                        if (visible) {
                            showSoftInput(mServedView, /* flags */ 0);
                        } else {
                            if (mCurRootView != null
                                    && mCurRootView.getInsetsController() != null) {
                                mCurRootView.getInsetsController().hide(WindowInsets.Type.ime());
                            }
                        }
                    }
                    break;
                case MSG_SEND_INPUT_EVENT: {
                    sendInputEventAndReportResultOnMainLooper((PendingEvent)msg.obj);
                    return;
                }
                case MSG_TIMEOUT_INPUT_EVENT: {
                    finishedInputEvent(msg.arg1, false, true);
                    return;
                }
                case MSG_FLUSH_INPUT_EVENT: {
                    finishedInputEvent(msg.arg1, false, false);
                    return;
                }
                case MSG_REPORT_FULLSCREEN_MODE: {
                    final boolean fullscreen = msg.arg1 != 0;
                    RemoteInputConnectionImpl ic = null;
                    synchronized (mH) {
                        if (mFullscreenMode != fullscreen && mServedInputConnection != null) {
                            ic = mServedInputConnection;
                            mFullscreenMode = fullscreen;
                        }
                    }
                    if (ic != null) {
                        ic.dispatchReportFullscreenMode(fullscreen);
                    }
                    return;
                }
                case MSG_ON_SHOW_REQUESTED: {
                    synchronized (mH) {
                        if (mImeInsetsConsumer != null) {
                            mImeInsetsConsumer.onShowRequested();
                        }
                    }
                    return;
                }
            }
        }
    }

    private final IInputMethodClient.Stub mClient = new IInputMethodClient.Stub() {
        @Override
        protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) {
            // No need to check for dump permission, since we only give this
            // interface to the system.
            CountDownLatch latch = new CountDownLatch(1);
            SomeArgs sargs = SomeArgs.obtain();
            sargs.arg1 = fd;
            sargs.arg2 = fout;
            sargs.arg3 = args;
            sargs.arg4 = latch;
            mH.sendMessage(mH.obtainMessage(MSG_DUMP, sargs));
            try {
                if (!latch.await(5, TimeUnit.SECONDS)) {
                    fout.println("Timeout waiting for dump");
                }
            } catch (InterruptedException e) {
                fout.println("Interrupted waiting for dump");
            }
        }

        @Override
        public void onBindMethod(InputBindResult res) {
            mH.obtainMessage(MSG_BIND, res).sendToTarget();
        }

        @Override
        public void onStartInputResult(InputBindResult res, int startInputSeq) {
            mH.obtainMessage(MSG_START_INPUT_RESULT, startInputSeq, -1 /* unused */, res)
                    .sendToTarget();
        }

        @Override
        public void onBindAccessibilityService(InputBindResult res, int id) {
            mH.obtainMessage(MSG_BIND_ACCESSIBILITY_SERVICE, id, 0, res).sendToTarget();
        }

        @Override
        public void onUnbindMethod(int sequence, @UnbindReason int unbindReason) {
            mH.obtainMessage(MSG_UNBIND, sequence, unbindReason).sendToTarget();
        }

        @Override
        public void onUnbindAccessibilityService(int sequence, int id) {
            mH.obtainMessage(MSG_UNBIND_ACCESSIBILITY_SERVICE, sequence, id).sendToTarget();
        }

        @Override
        public void setActive(boolean active, boolean fullscreen) {
            mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0).sendToTarget();
        }

        @Override
        public void setInteractive(boolean interactive, boolean fullscreen) {
            mH.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, fullscreen ? 1 : 0)
                    .sendToTarget();
        }

        @Override
        public void setImeVisibility(boolean visible) {
            mH.obtainMessage(MSG_SET_VISIBILITY, visible ? 1 : 0, 0).sendToTarget();
        }

        @Override
        public void scheduleStartInputIfNecessary(boolean fullscreen) {
            // TODO(b/149859205): See if we can optimize this by having a fused dedicated operation.
            mH.obtainMessage(MSG_SET_ACTIVE, 0 /* active */, fullscreen ? 1 : 0).sendToTarget();
            mH.obtainMessage(MSG_SET_ACTIVE, 1 /* active */, fullscreen ? 1 : 0).sendToTarget();
        }

        @Override
        public void reportFullscreenMode(boolean fullscreen) {
            mH.obtainMessage(MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, 0)
                    .sendToTarget();
        }

        @Override
        public void setImeTraceEnabled(boolean enabled) {
            ImeTracing.getInstance().setEnabled(enabled);
        }

        @Override
        public void throwExceptionFromSystem(String message) {
            throw new RuntimeException(message);
        }
    };

    /**
     * For layoutlib to clean up static objects inside {@link InputMethodManager}.
     */
    static void tearDownEditMode() {
        if (!isInEditMode()) {
            throw new UnsupportedOperationException(
                    "This method must be called only from layoutlib");
        }
        synchronized (sLock) {
            sInstance = null;
        }
    }

    /**
     * For layoutlib to override this method to return {@code true}.
     *
     * @return {@code true} if the process is running for developer tools
     * @see View#isInEditMode()
     */
    private static boolean isInEditMode() {
        return false;
    }

    static boolean isInEditModeInternal() {
        return isInEditMode();
    }

    @NonNull
    private static InputMethodManager createInstance(int displayId, Looper looper) {
        return isInEditMode() ? createStubInstance(displayId, looper)
                : createRealInstance(displayId, looper);
    }

    @NonNull
    private static InputMethodManager createRealInstance(int displayId, Looper looper) {
        final IInputMethodManager service = IInputMethodManagerGlobalInvoker.getService();
        if (service == null) {
            throw new IllegalStateException("IInputMethodManager is not available");
        }
        final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
        // InputMethodManagerService#addClient() relies on Binder.getCalling{Pid, Uid}() to
        // associate PID/UID with each IME client. This means:
        //  A. if this method call will be handled as an IPC, there is no problem.
        //  B. if this method call will be handled as an in-proc method call, we need to
        //     ensure that Binder.getCalling{Pid, Uid}() return Process.my{Pid, Uid}()
        // Either ways we can always call Binder.{clear, restore}CallingIdentity() because
        // 1) doing so has no effect for A and 2) doing so is sufficient for B.
        final long identity = Binder.clearCallingIdentity();
        try {
            IInputMethodManagerGlobalInvoker.addClient(imm.mClient, imm.mFallbackInputConnection,
                    displayId);
        } finally {
            Binder.restoreCallingIdentity(identity);
        }
        return imm;
    }

    @NonNull
    private static InputMethodManager createStubInstance(int displayId, Looper looper) {
        // If InputMethodManager is running for layoutlib, stub out IPCs into IMMS.
        final Class<IInputMethodManager> c = IInputMethodManager.class;
        final IInputMethodManager stubInterface =
                (IInputMethodManager) Proxy.newProxyInstance(c.getClassLoader(),
                        new Class[]{c}, (proxy, method, args) -> {
                            final Class<?> returnType = method.getReturnType();
                            if (returnType == boolean.class) {
                                return false;
                            } else if (returnType == int.class) {
                                return 0;
                            } else if (returnType == long.class) {
                                return 0L;
                            } else if (returnType == short.class) {
                                return 0;
                            } else if (returnType == char.class) {
                                return 0;
                            } else if (returnType == byte.class) {
                                return 0;
                            } else if (returnType == float.class) {
                                return 0f;
                            } else if (returnType == double.class) {
                                return 0.0;
                            } else {
                                return null;
                            }
                        });
        return new InputMethodManager(stubInterface, displayId, looper);
    }

    private InputMethodManager(@NonNull IInputMethodManager service, int displayId, Looper looper) {
        mService = service;  // For @UnsupportedAppUsage
        mMainLooper = looper;
        mH = new H(looper);
        mDisplayId = displayId;
        mFallbackInputConnection = new RemoteInputConnectionImpl(looper,
                new BaseInputConnection(this, false), this, null);
    }

    /**
     * Retrieve an instance for the given {@link Context}, creating it if it doesn't already exist.
     *
     * @param context {@link Context} for which IME APIs need to work
     * @return {@link InputMethodManager} instance
     * @hide
     */
    @NonNull
    public static InputMethodManager forContext(@DisplayContext Context context) {
        final int displayId = context.getDisplayId();
        // For better backward compatibility, we always use Looper.getMainLooper() for the default
        // display case.
        final Looper looper = displayId == Display.DEFAULT_DISPLAY
                ? Looper.getMainLooper() : context.getMainLooper();
        // Keep track of whether to expect the IME to be unavailable so as to avoid log spam in
        // sendInputEventOnMainLooperLocked() by not logging a verbose message on every DPAD event
        sPreventImeStartupUnlessTextEditor = context.getResources().getBoolean(
                com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
        return forContextInternal(displayId, looper);
    }

    @NonNull
    private static InputMethodManager forContextInternal(int displayId, Looper looper) {
        final boolean isDefaultDisplay = displayId == Display.DEFAULT_DISPLAY;
        synchronized (sLock) {
            InputMethodManager instance = sInstanceMap.get(displayId);
            if (instance != null) {
                return instance;
            }
            instance = createInstance(displayId, looper);
            // For backward compatibility, store the instance also to sInstance for default display.
            if (sInstance == null && isDefaultDisplay) {
                sInstance = instance;
            }
            sInstanceMap.put(displayId, instance);
            return instance;
        }
    }

    /**
     * Deprecated. Do not use.
     *
     * @return global {@link InputMethodManager} instance
     * @deprecated Use {@link Context#getSystemService(Class)} instead. This method cannot fully
     *             support multi-display scenario.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public static InputMethodManager getInstance() {
        Log.w(TAG, "InputMethodManager.getInstance() is deprecated because it cannot be"
                        + " compatible with multi-display."
                        + " Use context.getSystemService(InputMethodManager.class) instead.",
                new Throwable());
        ensureDefaultInstanceForDefaultDisplayIfNecessary();
        return peekInstance();
    }

    /**
     * Deprecated. Do not use.
     *
     * @return {@link #sInstance}
     * @deprecated Use {@link Context#getSystemService(Class)} instead. This method cannot fully
     *             support multi-display scenario.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public static InputMethodManager peekInstance() {
        Log.w(TAG, "InputMethodManager.peekInstance() is deprecated because it cannot be"
                        + " compatible with multi-display."
                        + " Use context.getSystemService(InputMethodManager.class) instead.",
                new Throwable());
        synchronized (sLock) {
            return sInstance;
        }
    }

    /**
     * Returns the list of installed input methods.
     *
     * <p>On multi user environment, this API returns a result for the calling process user.</p>
     *
     * @return {@link List} of {@link InputMethodInfo}.
     */
    @NonNull
    public List<InputMethodInfo> getInputMethodList() {
        // We intentionally do not use UserHandle.getCallingUserId() here because for system
        // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
        // instead.
        return IInputMethodManagerGlobalInvoker.getInputMethodList(UserHandle.myUserId(),
                DirectBootAwareness.AUTO);
    }

    /**
     * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled.
     * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
     * called and Stylus touch should continue as normal touch input.
     *
     * @see #startStylusHandwriting(View)
     */
    public boolean isStylusHandwritingAvailable() {
        return isStylusHandwritingAvailableAsUser(UserHandle.of(UserHandle.myUserId()));
    }

    /**
     * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
     * the given userId.
     *
     * <p>If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
     * called and Stylus touch should continue as normal touch input.</p>
     *
     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
     * {@code user} is different from the user of the current process.</p>
     *
     * @see #startStylusHandwriting(View)
     * @param user UserHandle to query.
     * @hide
     */
    @NonNull
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    @TestApi
    @SuppressLint("UserHandle")
    public boolean isStylusHandwritingAvailableAsUser(@NonNull UserHandle user) {
        final Context fallbackContext = ActivityThread.currentApplication();
        if (fallbackContext == null) {
            return false;
        }
        boolean isAvailable;
        synchronized (mH) {
            if (mStylusHandwritingAvailableCache == null) {
                mStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
                        4 /* maxEntries */, CACHE_KEY_STYLUS_HANDWRITING_PROPERTY) {
                    @Override
                    public Boolean recompute(Integer userId) {
                        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
                                userId, /* connectionless= */ false);
                    }
                };
            }
            isAvailable = mStylusHandwritingAvailableCache.query(user.getIdentifier());
        }
        return isAvailable;
    }

    /**
     * Returns {@code true} if the currently selected IME supports connectionless stylus handwriting
     * sessions and is enabled.
     */
    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
    public boolean isConnectionlessStylusHandwritingAvailable() {
        if (ActivityThread.currentApplication() == null) {
            return false;
        }
        synchronized (mH) {
            if (mConnectionlessStylusHandwritingAvailableCache == null) {
                mConnectionlessStylusHandwritingAvailableCache = new PropertyInvalidatedCache<>(
                        /* maxEntries= */ 4, CACHE_KEY_CONNECTIONLESS_STYLUS_HANDWRITING_PROPERTY) {
                    @Override
                    public Boolean recompute(@NonNull Integer userId) {
                        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(
                                userId, /* connectionless= */ true);
                    }
                };
            }
            return mConnectionlessStylusHandwritingAvailableCache.query(UserHandle.myUserId());
        }
    }

    /**
     * Returns the list of installed input methods for the specified user.
     *
     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
     * {@code userId} is different from the user id of the current process.</p>
     *
     * @param userId user ID to query
     * @return {@link List} of {@link InputMethodInfo}.
     * @hide
     */
    @TestApi
    @NonNull
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
        return IInputMethodManagerGlobalInvoker.getInputMethodList(userId,
                DirectBootAwareness.AUTO);
    }

    /**
     * Returns the list of installed input methods for the specified user.
     *
     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
     * {@code userId} is different from the user id of the current process.</p>
     *
     * @param userId user ID to query
     * @param directBootAwareness {@code true} if caller want to query installed input methods list
     * on user locked state.
     * @return {@link List} of {@link InputMethodInfo}.
     * @hide
     */
    @NonNull
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
            @DirectBootAwareness int directBootAwareness) {
        return IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
    }

    /**
     * Returns the {@link InputMethodInfo} of the currently selected input method (for the process's
     * user).
     *
     * <p>On multi user environment, this API returns a result for the calling process user.</p>
     */
    @Nullable
    public InputMethodInfo getCurrentInputMethodInfo() {
        // We intentionally do not use UserHandle.getCallingUserId() here because for system
        // services InputMethodManagerInternal.getCurrentInputMethodInfoForUser() should be used
        // instead.
        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
                UserHandle.myUserId());
    }

    /**
     * Returns the {@link InputMethodInfo} for currently selected input method for the given user.
     *
     * @param user user to query.
     * @hide
     */
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL)
    @Nullable
    @SystemApi
    @SuppressLint("UserHandle")
    public InputMethodInfo getCurrentInputMethodInfoAsUser(@NonNull UserHandle user) {
        Objects.requireNonNull(user);
        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodInfoAsUser(
                user.getIdentifier());
    }

    /**
     * Returns the list of enabled input methods.
     *
     * <p>On multi user environment, this API returns a result for the calling process user.</p>
     *
     * @return {@link List} of {@link InputMethodInfo}.
     */
    @NonNull
    public List<InputMethodInfo> getEnabledInputMethodList() {
        // We intentionally do not use UserHandle.getCallingUserId() here because for system
        // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
        // instead.
        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(UserHandle.myUserId());
    }

    /**
     * Returns the list of enabled input methods for the specified user.
     *
     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
     * {@code user} is different from the user of the current process.</p>
     *
     * @param user UserHandle to query
     * @return {@link List} of {@link InputMethodInfo}.
     * @see #getEnabledInputMethodSubtypeListAsUser(String, boolean, UserHandle)
     * @hide
     */
    @NonNull
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    @TestApi
    @SuppressLint("UserHandle")
    public List<InputMethodInfo> getEnabledInputMethodListAsUser(@NonNull UserHandle user) {
        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(user.getIdentifier());
    }

    /**
     * Returns a list of enabled input method subtypes for the specified input method info.
     *
     * <p>On multi user environment, this API returns a result for the calling process user.</p>
     *
     * @param imi The {@link InputMethodInfo} whose subtypes list will be returned. If {@code null},
     * returns enabled subtypes for the currently selected {@link InputMethodInfo}.
     * @param allowsImplicitlyEnabledSubtypes A boolean flag to allow to return the implicitly
     * enabled subtypes. If an input method info doesn't have enabled subtypes, the framework
     * will implicitly enable subtypes according to the current system language.
     */
    @NonNull
    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable InputMethodInfo imi,
            boolean allowsImplicitlyEnabledSubtypes) {
        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
                imi == null ? null : imi.getId(),
                allowsImplicitlyEnabledSubtypes,
                UserHandle.myUserId());
    }

    /**
     * Returns a list of enabled input method subtypes for the specified input method info for the
     * specified user.
     *
     * @param imeId IME ID to be queried about.
     * @param allowsImplicitlyEnabledSubtypes {@code true} to include implicitly enabled subtypes.
     * @param user UserHandle to be queried about.
     *               {@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required if this is
     *               different from the calling process user ID.
     * @return {@link List} of {@link InputMethodSubtype}.
     * @see #getEnabledInputMethodListAsUser(UserHandle)
     * @hide
     */
    @NonNull
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    @TestApi
    @SuppressLint("UserHandle")
    public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser(
            @NonNull String imeId, boolean allowsImplicitlyEnabledSubtypes,
            @NonNull UserHandle user) {
        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
                Objects.requireNonNull(imeId), allowsImplicitlyEnabledSubtypes,
                user.getIdentifier());
    }

    /**
     * @deprecated Use {@link InputMethodService#showStatusIcon(int)} instead. This method was
     * intended for IME developers who should be accessing APIs through the service. APIs in this
     * class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void showStatusIcon(IBinder imeToken, String packageName, @DrawableRes int iconId) {
        InputMethodPrivilegedOperationsRegistry.get(
                imeToken).updateStatusIconAsync(packageName, iconId);
    }

    /**
     * @deprecated Use {@link InputMethodService#hideStatusIcon()} instead. This method was
     * intended for IME developers who should be accessing APIs through the service. APIs in
     * this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void hideStatusIcon(IBinder imeToken) {
        InputMethodPrivilegedOperationsRegistry.get(imeToken).updateStatusIconAsync(null, 0);
    }

    /**
     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
     *
     * @param spans will be ignored.
     *
     * @deprecated Do not use.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
        Log.w(TAG, "registerSuggestionSpansForNotification() is deprecated.  Does nothing.");
    }

    /**
     * This hidden API is deprecated in {@link android.os.Build.VERSION_CODES#Q}. Does nothing.
     *
     * @deprecated Do not use.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage
    public void notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
        Log.w(TAG, "notifySuggestionPicked() is deprecated.  Does nothing.");
    }

    /**
     * Allows you to discover whether the attached input method is running
     * in fullscreen mode.  Return true if it is fullscreen, entirely covering
     * your UI, else returns false.
     */
    public boolean isFullscreenMode() {
        synchronized (mH) {
            return mFullscreenMode;
        }
    }

    /**
     * Return {@code true} if the given view is the currently active view for the input method.
     */
    public boolean isActive(View view) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            return fallbackImm.isActive(view);
        }

        checkFocus();
        synchronized (mH) {
            return hasServedByInputMethodLocked(view) && mCurrentEditorInfo != null;
        }
    }

    /**
     * Return {@code true} if any view is currently active for the input method.
     */
    public boolean isActive() {
        checkFocus();
        synchronized (mH) {
            return getServedViewLocked() != null && mCurrentEditorInfo != null;
        }
    }

    /**
     * Returns {@code true} if the given view's {@link ViewRootImpl} is the currently active one
     * for the {@code InputMethodManager}.
     *
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public boolean isCurrentRootView(@NonNull View attachedView) {
        synchronized (mH) {
            return mCurRootView == attachedView.getViewRootImpl();
        }
    }

    /**
     * Return {@code true} if the currently served view is accepting full text edits.
     * If {@code false}, it has no input connection, so it can only handle raw key events.
     */
    public boolean isAcceptingText() {
        checkFocus();
        synchronized (mH) {
            return mServedInputConnection != null;
        }
    }

    /**
     * Return {@code true} if the input method is suppressing system spell checker.
     */
    public boolean isInputMethodSuppressingSpellChecker() {
        synchronized (mH) {
            return mCurBindState != null
                    && mCurBindState.mIsInputMethodSuppressingSpellChecker;
        }
    }

    /**
     * Reset all of the state associated with being bound to an input method.
     */
    @GuardedBy("mH")
    private void clearBindingLocked() {
        if (DEBUG) Log.v(TAG, "Clearing binding!");
        clearConnectionLocked();
        updateInputChannelLocked(null);
        mCurId = null; // for @UnsupportedAppUsage
        mCurMethod = null; // for @UnsupportedAppUsage
        // We only reset sequence number for input method, but not accessibility.
        mCurBindState = null;
    }

    /**
     * Reset all of the state associated with being bound to an accessibility service.
     */
    @GuardedBy("mH")
    private void clearAccessibilityBindingLocked(int id) {
        if (DEBUG) Log.v(TAG, "Clearing accessibility binding " + id);
        mAccessibilityInputMethodSession.remove(id);
    }

    /**
     * Reset all of the state associated with being bound to all accessibility services.
     */
    @GuardedBy("mH")
    private void clearAllAccessibilityBindingLocked() {
        if (DEBUG) Log.v(TAG, "Clearing all accessibility bindings");
        mAccessibilityInputMethodSession.clear();
    }

    @GuardedBy("mH")
    private void updateInputChannelLocked(InputChannel channel) {
        if (areSameInputChannel(mCurChannel, channel)) {
            return;
        }
        // TODO(b/238720598) : Requirements when design a new protocol for InputChannel
        // channel is a dupe of 'mCurChannel', because they have the same token, and represent
        // the same connection. Ignore the incoming channel and keep using 'mCurChannel' to
        // avoid confusing the InputEventReceiver.
        if (mCurSender != null) {
            flushPendingEventsLocked();
            mCurSender.dispose();
            mCurSender = null;
        }

        if (mCurChannel != null) {
            mCurChannel.dispose();
        }
        mCurChannel = channel;
    }

    private static boolean areSameInputChannel(@Nullable InputChannel lhs,
            @Nullable InputChannel rhs) {
        if (lhs == rhs) {
            return true;
        }
        if (lhs == null || rhs == null) {
            return false;
        }
        return lhs.getToken() == rhs.getToken();
    }

    /**
     * Reset all of the state associated with a served view being connected
     * to an input method
     */
    @GuardedBy("mH")
    private void clearConnectionLocked() {
        mCurrentEditorInfo = null;
        mPreviousViewFocusParameters = null;
        if (mServedInputConnection != null) {
            mServedInputConnection.deactivate();
            mServedInputConnection = null;
            mServedInputConnectionHandler = null;
        }
    }

    /**
     * Disconnect any existing input connection, clearing the served view.
     */
    @UnsupportedAppUsage
    @GuardedBy("mH")
    void finishInputLocked() {
        View clearedView = null;
        mNextServedView = null;
        if (mServedView != null) {
            clearedView = mServedView;
            mServedView = null;
            if (initiationWithoutInputConnection() && clearedView.getViewRootImpl() != null) {
                clearedView.getViewRootImpl().getHandwritingInitiator()
                        .clearFocusedView(clearedView);
            }
        }
        if (clearedView != null) {
            if (DEBUG) {
                Log.v(TAG, "FINISH INPUT: mServedView="
                        + InputMethodDebug.dumpViewInfo(clearedView));
            }
            mCompletions = null;
            mServedConnecting = false;
            clearConnectionLocked();
        }
        mReportInputConnectionOpenedRunner = null;
        // Clear the back callbacks held by the ime dispatcher to avoid memory leaks.
        mImeDispatcher.clear();
    }

    /**
     * Clears the {@link #mCurRootView} if it's no longer window focused and the connection is
     * no longer active.
     *
     * @return {@code} true iff it was cleared.
     */
    @GuardedBy("mH")
    private boolean clearCurRootViewIfNeeded() {
        if (!mActive && !mCurRootViewWindowFocused) {
            finishInputLocked();
            mDelegate.setCurrentRootViewLocked(null);

            return true;
        }

        return false;
    }

    public void displayCompletions(View view, CompletionInfo[] completions) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.displayCompletions(view, completions);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view)) {
                return;
            }

            mCompletions = completions;
            if (isImeSessionAvailableLocked()) {
                mCurBindState.mImeSession.displayCompletions(mCompletions);
            }
        }
    }

    public void updateExtractedText(View view, int token, ExtractedText text) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.updateExtractedText(view, token, text);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view)) {
                return;
            }

            if (isImeSessionAvailableLocked()) {
                mCurBindState.mImeSession.updateExtractedText(token, text);
            }
        }
    }

    /** @hide */
    @IntDef(flag = true, prefix = { "SHOW_" }, value = {
            SHOW_IMPLICIT,
            SHOW_FORCED,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface ShowFlags {}

    /**
     * Flag for {@link #showSoftInput} to indicate that this is an implicit
     * request to show the input window, not as the result of a direct request
     * by the user.  The window may not be shown in this case.
     */
    public static final int SHOW_IMPLICIT = 0x0001;

    /**
     * Flag for {@link #showSoftInput} to indicate that the user has forced
     * the input method open (such as by long-pressing menu) so it should
     * not be closed until they explicitly do so.
     *
     * @deprecated Use {@link #showSoftInput} without this flag instead. Using this flag can lead
     * to the soft input remaining visible even when the calling application is closed. The
     * use of this flag can make the soft input remain visible globally. Starting in
     * {@link Build.VERSION_CODES#TIRAMISU Android T}, this flag only has an effect while the
     * caller is currently focused.
     */
    @Deprecated
    public static final int SHOW_FORCED = 0x0002;

    /**
     * Synonym for {@link #showSoftInput(View, int, ResultReceiver)} without
     * a result receiver: explicitly request that the current input method's
     * soft input area be shown to the user, if needed.
     *
     * @param view The currently focused view, which would like to receive soft keyboard input.
     *             Note that this view is only considered focused here if both it itself has
     *             {@link View#isFocused view focus}, and its containing window has
     *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
     *             returns {@code false}.
     */
    public boolean showSoftInput(View view, @ShowFlags int flags) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            return fallbackImm.showSoftInput(view, flags);
        }

        return showSoftInput(view, flags, null);
    }

    /**
     * Flag for the {@link ResultReceiver} result code from
     * {@link #showSoftInput(View, int, ResultReceiver)} and
     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
     * state of the soft input window was unchanged and remains shown.
     */
    public static final int RESULT_UNCHANGED_SHOWN = 0;

    /**
     * Flag for the {@link ResultReceiver} result code from
     * {@link #showSoftInput(View, int, ResultReceiver)} and
     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
     * state of the soft input window was unchanged and remains hidden.
     */
    public static final int RESULT_UNCHANGED_HIDDEN = 1;

    /**
     * Flag for the {@link ResultReceiver} result code from
     * {@link #showSoftInput(View, int, ResultReceiver)} and
     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
     * state of the soft input window changed from hidden to shown.
     */
    public static final int RESULT_SHOWN = 2;

    /**
     * Flag for the {@link ResultReceiver} result code from
     * {@link #showSoftInput(View, int, ResultReceiver)} and
     * {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}: the
     * state of the soft input window changed from shown to hidden.
     */
    public static final int RESULT_HIDDEN = 3;

    /**
     * Explicitly request that the current input method's soft input area be
     * shown to the user, if needed.  Call this if the user interacts with
     * your view in such a way that they have expressed they would like to
     * start performing input into it.
     *
     * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
     * this method can be a long-lived object, because it may not be
     * garbage-collected until all the corresponding {@link ResultReceiver}
     * objects transferred to different processes get garbage-collected.
     * Follow the general patterns to avoid memory leaks in Android.
     * Consider to use {@link java.lang.ref.WeakReference} so that application
     * logic objects such as {@link android.app.Activity} and {@link Context}
     * can be garbage collected regardless of the lifetime of
     * {@link ResultReceiver}.
     *
     * @param view The currently focused view, which would like to receive soft keyboard input.
     *             Note that this view is only considered focused here if both it itself has
     *             {@link View#isFocused view focus}, and its containing window has
     *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
     *             returns {@code false}.
     * @param resultReceiver If non-null, this will be called by the IME when
     * it has processed your request to tell you what it has done.  The result
     * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
     * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
     * {@link #RESULT_HIDDEN}.
     */
    public boolean showSoftInput(View view, @ShowFlags int flags, ResultReceiver resultReceiver) {
        return showSoftInput(view, flags, resultReceiver, SoftInputShowHideReason.SHOW_SOFT_INPUT);
    }

    private boolean showSoftInput(View view, @ShowFlags int flags,
            @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
        // TODO(b/303041796): handle tracking physical keyboard and DPAD as user interactions
        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
                ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
        return showSoftInput(view, statsToken, flags, resultReceiver, reason);
    }

    private boolean showSoftInput(View view, @NonNull ImeTracker.Token statsToken,
            @ShowFlags int flags, @Nullable ResultReceiver resultReceiver,
            @SoftInputShowHideReason int reason) {
        ImeTracker.forLatency().onRequestShow(statsToken,
                ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
        ImeTracing.getInstance().triggerClientDump("InputMethodManager#showSoftInput", this,
                null /* icProto */);
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            return fallbackImm.showSoftInput(view, statsToken, flags, resultReceiver, reason);
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view)) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                ImeTracker.forLatency().onShowFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                Log.w(TAG, "Ignoring showSoftInput() as view=" + view + " is not served.");
                return false;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            if (Flags.refactorInsetsController()) {
                // In case of a running show IME animation, it should not be requested visible,
                // otherwise the animation would jump and not be controlled by the user anymore
                if ((mCurRootView.getInsetsController().computeUserAnimatingTypes()
                        & WindowInsets.Type.ime()) == 0) {
                    // TODO(b/322992891) handle case of SHOW_IMPLICIT
                    view.getWindowInsetsController().show(WindowInsets.Type.ime());
                    return true;
                } else {
                    return false;
                }
            } else {
                // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
                // TODO(b/229426865): call WindowInsetsController#show instead.
                mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
                Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
                        + InputMethodDebug.softInputDisplayReasonToString(reason));
                return IInputMethodManagerGlobalInvoker.showSoftInput(
                        mClient,
                        view.getWindowToken(),
                        statsToken,
                        flags,
                        mCurRootView.getLastClickToolType(),
                        resultReceiver,
                        reason);
            }
        }
    }

    /**
     * This method is still kept for a while until androidx.appcompat.widget.SearchView ver. 26.0
     * is publicly released because previous implementations of that class had relied on this method
     * via reflection.
     *
     * @deprecated This is a hidden API. You should never use this.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 123768499)
    public void showSoftInputUnchecked(@ShowFlags int flags, ResultReceiver resultReceiver) {
        synchronized (mH) {
            final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT;
            final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
                    ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);

            Log.w(TAG, "showSoftInputUnchecked() is a hidden method, which will be"
                    + " removed soon. If you are using androidx.appcompat.widget.SearchView,"
                    + " please update to version 26.0 or newer version.");
            final View rootView = mCurRootView != null ? mCurRootView.getView() : null;
            if (rootView == null) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                Log.w(TAG, "No current root view, ignoring showSoftInputUnchecked()");
                return;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
            // TODO(b/229426865): call WindowInsetsController#show instead.
            mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
            IInputMethodManagerGlobalInvoker.showSoftInput(
                    mClient,
                    rootView.getWindowToken(),
                    statsToken,
                    flags,
                    mCurRootView.getLastClickToolType(),
                    resultReceiver,
                    reason);
        }
    }

    /** @hide */
    @IntDef(flag = true, prefix = { "HIDE_" }, value = {
            HIDE_IMPLICIT_ONLY,
            HIDE_NOT_ALWAYS,
    })
    @Retention(RetentionPolicy.SOURCE)
    public @interface HideFlags {}

    /**
     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestHideSelf(int)}
     * to indicate that the soft input window should only be hidden if it was not explicitly shown
     * by the user.
     */
    public static final int HIDE_IMPLICIT_ONLY = 0x0001;

    /**
     * Flag for {@link #hideSoftInputFromWindow} and {@link InputMethodService#requestShowSelf(int)}
     * to indicate that the soft input window should normally be hidden, unless it was originally
     * shown with {@link #SHOW_FORCED}.
     */
    public static final int HIDE_NOT_ALWAYS = 0x0002;

    /**
     * Synonym for {@link #hideSoftInputFromWindow(IBinder, int, ResultReceiver)}
     * without a result: request to hide the soft input window from the
     * context of the window that is currently accepting input.
     *
     * @param windowToken The token of the window that is making the request,
     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
     */
    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags) {
        return hideSoftInputFromWindow(windowToken, flags, null);
    }

    /**
     * Request to hide the soft input window from the context of the window
     * that is currently accepting input.  This should be called as a result
     * of the user doing some actually than fairly explicitly requests to
     * have the input window hidden.
     *
     * <p><strong>Caveat:</strong> {@link ResultReceiver} instance passed to
     * this method can be a long-lived object, because it may not be
     * garbage-collected until all the corresponding {@link ResultReceiver}
     * objects transferred to different processes get garbage-collected.
     * Follow the general patterns to avoid memory leaks in Android.
     * Consider to use {@link java.lang.ref.WeakReference} so that application
     * logic objects such as {@link android.app.Activity} and {@link Context}
     * can be garbage collected regardless of the lifetime of
     * {@link ResultReceiver}.
     *
     * @param windowToken The token of the window that is making the request,
     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
     * @param resultReceiver If non-null, this will be called by the IME when
     * it has processed your request to tell you what it has done.  The result
     * code you receive may be either {@link #RESULT_UNCHANGED_SHOWN},
     * {@link #RESULT_UNCHANGED_HIDDEN}, {@link #RESULT_SHOWN}, or
     * {@link #RESULT_HIDDEN}.
     */
    public boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
            ResultReceiver resultReceiver) {
        return hideSoftInputFromWindow(windowToken, flags, resultReceiver,
                SoftInputShowHideReason.HIDE_SOFT_INPUT);
    }

    private boolean hideSoftInputFromWindow(IBinder windowToken, @HideFlags int flags,
            ResultReceiver resultReceiver, @SoftInputShowHideReason int reason) {
        // Get served view initially for statsToken creation.
        final View initialServedView;
        synchronized (mH) {
            initialServedView = getServedViewLocked();
        }

        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(initialServedView));
        ImeTracker.forLatency().onRequestHide(statsToken,
                ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
        ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromWindow",
                this, null /* icProto */);
        checkFocus();
        synchronized (mH) {
            // Get served view again in case it changed between the synchronized blocks.
            final View servedView = getServedViewLocked();
            if (servedView == null || servedView.getWindowToken() != windowToken) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                ImeTracker.forLatency().onHideFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                return false;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            if (Flags.refactorInsetsController()) {
                // TODO(b/322992891) handle case of HIDE_IMPLICIT_ONLY
                servedView.getWindowInsetsController().hide(WindowInsets.Type.ime());
                return true;
            } else {
                return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken,
                        statsToken, flags, resultReceiver, reason);
            }
        }
    }

    /**
     * Synonym for {@link #hideSoftInputFromWindow(IBinder, int)} but takes a {@link View} as a
     * parameter to be a counterpart of {@link #showSoftInput(View, int)}.
     *
     * @param view {@link View} to be used to conditionally issue hide request when and only when
     *             this {@link View} is serving as an IME target.
     * @hide
     */
    public boolean hideSoftInputFromView(@NonNull View view, @HideFlags int flags) {
        checkFocus();
        final boolean isFocusedAndWindowFocused = view.hasWindowFocus() && view.isFocused();
        synchronized (mH) {
            final boolean hasServedByInputMethod = hasServedByInputMethodLocked(view);
            if (!isFocusedAndWindowFocused && !hasServedByInputMethod) {
                // Fail early if the view is not focused and not served
                // to avoid logging many erroneous calls.
                return false;
            }

            final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_FROM_VIEW;
            final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                    ImeTracker.ORIGIN_CLIENT, reason, ImeTracker.isFromUser(view));
            ImeTracker.forLatency().onRequestHide(statsToken,
                    ImeTracker.ORIGIN_CLIENT, reason, ActivityThread::currentApplication);
            ImeTracing.getInstance().triggerClientDump("InputMethodManager#hideSoftInputFromView",
                    this, null /* icProto */);

            if (!hasServedByInputMethod) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                ImeTracker.forLatency().onShowFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                Log.w(TAG, "Ignoring hideSoftInputFromView() as view=" + view + " is not served.");
                return false;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, view.getWindowToken(),
                    statsToken, flags, null, reason);
        }
    }

    /**
     * A test API for CTS to request hiding the current soft input window, with the request origin
     * on the server side.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public void hideSoftInputFromServerForTest() {
        IInputMethodManagerGlobalInvoker.hideSoftInputFromServerForTest();
    }

    /**
     * Start stylus handwriting session.
     *
     * If supported by the current input method, a stylus handwriting session is started on the
     * given View, capturing all stylus input and converting it to InputConnection commands.
     *
     * If handwriting mode is started successfully by the IME, any currently dispatched stylus
     * pointers will be {@code android.view.MotionEvent#FLAG_CANCELED} cancelled.
     *
     * If Stylus handwriting mode is not supported or cannot be fulfilled for any reason by IME,
     * request will be ignored and Stylus touch will continue as normal touch input. Ideally,
     * {@link #isStylusHandwritingAvailable()} should be called first to determine if stylus
     * handwriting is supported by IME.
     *
     * @param view the View for which stylus handwriting is requested. It and
     * {@link View#hasWindowFocus its window} must be {@link View#hasFocus focused}.
     * @see #isStylusHandwritingAvailable()
     */
    public void startStylusHandwriting(@NonNull View view) {
        startStylusHandwritingInternal(
                view, /* delegatorPackageName= */ null, /* handwritingDelegateFlags= */ 0);
    }

    private void sendFailureCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<Boolean> callback) {
        if (executor == null || callback == null) {
            return;
        }
        executor.execute(() -> callback.accept(false));
    }

    private boolean startStylusHandwritingInternal(
            @NonNull View view, @Nullable String delegatorPackageName,
            @HandwritingDelegateFlags int handwritingDelegateFlags) {
        return startStylusHandwritingInternal(
                view, delegatorPackageName, handwritingDelegateFlags,
                null /* executor */, null /* callback */);
    }

    private boolean startStylusHandwritingInternal(
            @NonNull View view, @Nullable String delegatorPackageName,
            @HandwritingDelegateFlags int handwritingDelegateFlags, Executor executor,
            Consumer<Boolean> callback) {
        Objects.requireNonNull(view);
        boolean useCallback = callback != null;

        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.startStylusHandwritingInternal(
                    view, delegatorPackageName, handwritingDelegateFlags, executor, callback);
        }

        boolean useDelegation = !TextUtils.isEmpty(delegatorPackageName);

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view)) {
                Log.w(TAG,
                        "Ignoring startStylusHandwriting as view=" + view + " is not served.");
                sendFailureCallback(executor, callback);
                return false;
            }
            if (view.getViewRootImpl() != mCurRootView) {
                Log.w(TAG,
                        "Ignoring startStylusHandwriting: View's window does not have focus.");
                sendFailureCallback(executor, callback);
                return false;
            }
            if (useDelegation) {
                WeakReference<Executor> executorRef = new WeakReference<>(executor);
                WeakReference<Consumer<Boolean>> callbackRef = new WeakReference<>(callback);
                if (useCallback) {
                    IBooleanListener listener = new IBooleanListener.Stub() {
                        @Override
                        public void onResult(boolean value) {
                            Executor executor = executorRef.get();
                            Consumer<Boolean> callback = callbackRef.get();
                            if (executor != null && callback != null) {
                                executor.execute(() -> callback.accept(value));
                            }
                        }
                    };
                    if (!IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegationAsync(
                            mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
                            delegatorPackageName, handwritingDelegateFlags, listener)) {
                        sendFailureCallback(executor, callback);
                    }
                    return true;
                } else {
                    return IInputMethodManagerGlobalInvoker.acceptStylusHandwritingDelegation(
                            mClient, UserHandle.myUserId(), view.getContext().getOpPackageName(),
                            delegatorPackageName, handwritingDelegateFlags);
                }
            } else {
                IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
                return false;
            }
        }
    }

    /**
     * Starts a connectionless stylus handwriting session. A connectionless session differs from a
     * regular stylus handwriting session in that the IME does not use an input connection to
     * communicate with a text editor. Instead, the IME directly returns recognised handwritten text
     * via a callback.
     *
     * <p>The {code cursorAnchorInfo} may be used by the IME to improve the handwriting recognition
     * accuracy and user experience of the handwriting session. Usually connectionless handwriting
     * is used for a view which appears like a text editor but does not really support text editing.
     * For best results, the {code cursorAnchorInfo} should be populated as it would be for a real
     * text editor (for example, the insertion marker location can be set to where the user would
     * expect it to be, even if there is no visible cursor).
     *
     * @param view the view receiving stylus events
     * @param cursorAnchorInfo positional information about the view receiving stylus events
     * @param callbackExecutor the executor to run the callback on
     * @param callback the callback to receive the result
     */
    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
    public void startConnectionlessStylusHandwriting(@NonNull View view,
            @Nullable CursorAnchorInfo cursorAnchorInfo,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull ConnectionlessHandwritingCallback callback) {
        startConnectionlessStylusHandwritingInternal(
                view, cursorAnchorInfo, null, null, callbackExecutor, callback);
    }

    /**
     * Starts a connectionless stylus handwriting session (see {@link
     * #startConnectionlessStylusHandwriting}) and additionally enables the recognised handwritten
     * text to be later committed to a text editor using {@link
     * #acceptStylusHandwritingDelegation(View)}.
     *
     * <p>After a connectionless session started using this method completes successfully, a text
     * editor view, called the delegate view, may call {@link
     * #acceptStylusHandwritingDelegation(View)} which will request the IME to commit the recognised
     * handwritten text from the connectionless session to the delegate view.
     *
     * <p>The delegate view must belong to the same package as the delegator view for the delegation
     * to succeed. If the delegate view belongs to a different package, use {@link
     * #startConnectionlessStylusHandwritingForDelegation(View, CursorAnchorInfo, String, Executor,
     * ConnectionlessHandwritingCallback)} instead.
     *
     * @param delegatorView the view receiving stylus events
     * @param cursorAnchorInfo positional information about the view receiving stylus events
     * @param callbackExecutor the executor to run the callback on
     * @param callback the callback to receive the result
     */
    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
    public void startConnectionlessStylusHandwritingForDelegation(@NonNull View delegatorView,
            @Nullable CursorAnchorInfo cursorAnchorInfo,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull ConnectionlessHandwritingCallback callback) {
        String delegatorPackageName = delegatorView.getContext().getOpPackageName();
        startConnectionlessStylusHandwritingInternal(delegatorView, cursorAnchorInfo,
                delegatorPackageName, delegatorPackageName, callbackExecutor, callback);
    }

    /**
     * Starts a connectionless stylus handwriting session (see {@link
     * #startConnectionlessStylusHandwriting}) and additionally enables the recognised handwritten
     * text to be later committed to a text editor using {@link
     * #acceptStylusHandwritingDelegation(View, String)}.
     *
     * <p>After a connectionless session started using this method completes successfully, a text
     * editor view, called the delegate view, may call {@link
     * #acceptStylusHandwritingDelegation(View, String)} which will request the IME to commit the
     * recognised handwritten text from the connectionless session to the delegate view.
     *
     * <p>The delegate view must belong to {@code delegatePackageName} for the delegation to
     * succeed.
     *
     * @param delegatorView the view receiving stylus events
     * @param cursorAnchorInfo positional information about the view receiving stylus events
     * @param delegatePackageName name of the package containing the delegate view which will accept
     *     the delegation
     * @param callbackExecutor the executor to run the callback on
     * @param callback the callback to receive the result
     */
    @FlaggedApi(Flags.FLAG_CONNECTIONLESS_HANDWRITING)
    public void startConnectionlessStylusHandwritingForDelegation(@NonNull View delegatorView,
            @Nullable CursorAnchorInfo cursorAnchorInfo,
            @NonNull String delegatePackageName,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull ConnectionlessHandwritingCallback callback) {
        Objects.requireNonNull(delegatePackageName);
        String delegatorPackageName = delegatorView.getContext().getOpPackageName();
        startConnectionlessStylusHandwritingInternal(delegatorView, cursorAnchorInfo,
                delegatorPackageName, delegatePackageName, callbackExecutor, callback);
    }

    private void startConnectionlessStylusHandwritingInternal(@NonNull View view,
            @Nullable CursorAnchorInfo cursorAnchorInfo,
            @Nullable String delegatorPackageName,
            @Nullable String delegatePackageName,
            @NonNull @CallbackExecutor Executor callbackExecutor,
            @NonNull ConnectionlessHandwritingCallback callback) {
        Objects.requireNonNull(view);
        Objects.requireNonNull(callbackExecutor);
        Objects.requireNonNull(callback);
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.startConnectionlessStylusHandwritingInternal(view, cursorAnchorInfo,
                    delegatorPackageName, delegatePackageName, callbackExecutor, callback);
        }

        checkFocus();
        synchronized (mH) {
            if (view.getViewRootImpl() != mCurRootView) {
                Log.w(TAG, "Ignoring startConnectionlessStylusHandwriting: "
                        + "View's window does not have focus.");
                return;
            }
            IInputMethodManagerGlobalInvoker.startConnectionlessStylusHandwriting(
                    mClient, UserHandle.myUserId(), cursorAnchorInfo,
                    delegatePackageName, delegatorPackageName,
                    new ConnectionlessHandwritingCallbackProxy(callbackExecutor, callback));
        }
    }

    /**
     * Prepares delegation of starting stylus handwriting session to a different editor in same
     * or different window than the view on which initial handwriting stroke was detected.
     *
     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
     * its {@link InputConnection} is started. Calling this method starts buffering of stylus
     * motion events until {@link #acceptStylusHandwritingDelegation(View)} is called, at which
     * point the handwriting session can be started and the buffered stylus motion events will be
     * delivered to the IME.
     * e.g. Delegation can be used when initial handwriting stroke is
     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
     * {@code Editor} is on a different window.
     *
     * <p> Note: If an actual {@code Editor} capable of {@link InputConnection} is being scribbled
     * upon using stylus, use {@link #startStylusHandwriting(View)} instead.</p>
     *
     * @param delegatorView the view that receives initial stylus stroke and delegates it to the
     *  actual editor. Its window must {@link View#hasWindowFocus have focus}.
     * @see #prepareStylusHandwritingDelegation(View, String)
     * @see #acceptStylusHandwritingDelegation(View)
     * @see #startStylusHandwriting(View)
     */
    public void prepareStylusHandwritingDelegation(@NonNull View delegatorView) {
        prepareStylusHandwritingDelegation(
                delegatorView, delegatorView.getContext().getOpPackageName());
    }

    /**
     * Prepares delegation of starting stylus handwriting session to a different editor in same or a
     * different window in a different package than the view on which initial handwriting stroke
     * was detected.
     *
     * Delegation can be used to start stylus handwriting session before the {@code Editor} view or
     * its {@link InputConnection} is started. Calling this method starts buffering of stylus
     * motion events until {@link #acceptStylusHandwritingDelegation(View, String)} is called, at
     * which point the handwriting session can be started and the buffered stylus motion events will
     * be delivered to the IME.
     * e.g. Delegation can be used when initial handwriting stroke is
     * on a pseudo {@code Editor} like widget (with no {@link InputConnection}) but actual
     * {@code Editor} is on a different window in the given package.
     *
     * <p>Note: If delegator and delegate are in same package use
     * {@link #prepareStylusHandwritingDelegation(View)} instead.</p>
     *
     * @param delegatorView  the view that receives initial stylus stroke and delegates it to the
     * actual editor. Its window must {@link View#hasWindowFocus have focus}.
     * @param delegatePackageName package name that contains actual {@code Editor} which should
     *  start stylus handwriting session by calling {@link #acceptStylusHandwritingDelegation}.
     * @see #prepareStylusHandwritingDelegation(View)
     * @see #acceptStylusHandwritingDelegation(View, String)
     */
    public void prepareStylusHandwritingDelegation(
            @NonNull View delegatorView, @NonNull String delegatePackageName) {
        Objects.requireNonNull(delegatorView);
        Objects.requireNonNull(delegatePackageName);

        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm =
                getFallbackInputMethodManagerIfNecessary(delegatorView);
        if (fallbackImm != null) {
            fallbackImm.prepareStylusHandwritingDelegation(delegatorView, delegatePackageName);
        }

        IInputMethodManagerGlobalInvoker.prepareStylusHandwritingDelegation(
                mClient,
                UserHandle.myUserId(),
                delegatePackageName,
                delegatorView.getContext().getOpPackageName());
    }

    /**
     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
     * initiation delegation was previously requested using
     * {@link #prepareStylusHandwritingDelegation(View)} from the delegator.
     *
     * <p>Note: If delegator and delegate are in different application packages, use
     * {@link #acceptStylusHandwritingDelegation(View, String)} instead.</p>
     *
     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
     * @return {@code true} if view belongs to same application package as used in
     *  {@link #prepareStylusHandwritingDelegation(View)} and delegation is accepted
     * @see #prepareStylusHandwritingDelegation(View)
     * @see #acceptStylusHandwritingDelegation(View, String)
     */
    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
    // <p>Otherwise, if the delegator view previously started delegation using {@link
    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo)},
    // requests the IME to commit the recognised handwritten text from the connectionless session to
    // the delegate view.
    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
    //     CursorAnchorInfo)
    public boolean acceptStylusHandwritingDelegation(@NonNull View delegateView) {
        return startStylusHandwritingInternal(
                delegateView, delegateView.getContext().getOpPackageName(),
                delegateView.getHandwritingDelegateFlags());
    }

    /**
     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
     * initiation delegation was previously requested using
     * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
     * belongs to a specified delegate package.
     *
     * <p>Note: If delegator and delegate are in the same application package, use
     * {@link #acceptStylusHandwritingDelegation(View)} instead.</p>
     *
     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
     * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
     * @return {@code true} if view belongs to allowed delegate package declared in {@link
     *     #prepareStylusHandwritingDelegation(View, String)} and delegation is accepted
     * @see #prepareStylusHandwritingDelegation(View, String)
     * @see #acceptStylusHandwritingDelegation(View)
     * TODO (b/293640003): deprecate this method once flag is enabled.
     */
    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
    // <p>Otherwise, if the delegator view previously started delegation using {@link
    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
    // String)}, requests the IME to commit the recognised handwritten text from the connectionless
    // session to the delegate view.
    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
    //     CursorAnchorInfo, String)
    public boolean acceptStylusHandwritingDelegation(
            @NonNull View delegateView, @NonNull String delegatorPackageName) {
        Objects.requireNonNull(delegatorPackageName);
        return startStylusHandwritingInternal(
                delegateView, delegatorPackageName, delegateView.getHandwritingDelegateFlags());
    }

    /**
     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
     * initiation delegation was previously requested using
     * {@link #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view
     * belongs to a specified delegate package.
     *
     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
     *  on which {@link #startStylusHandwriting(View)} will be called.
     * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
     * @param executor The executor to run the callback on.
     * @param callback Consumer callback that provides {@code true} if view belongs to allowed
     *                delegate package declared in
     *                {@link #prepareStylusHandwritingDelegation(View, String)} and handwriting
     *                session can start.
     * @see #prepareStylusHandwritingDelegation(View, String)
     * @see #acceptStylusHandwritingDelegation(View)
     */
    @FlaggedApi(Flags.FLAG_USE_ZERO_JANK_PROXY)
    public void acceptStylusHandwritingDelegation(
            @NonNull View delegateView, @NonNull String delegatorPackageName,
            @NonNull @CallbackExecutor Executor executor, @NonNull Consumer<Boolean> callback) {
        Objects.requireNonNull(delegatorPackageName);
        int flags = 0;
        if (Flags.homeScreenHandwritingDelegator()) {
            flags = delegateView.getHandwritingDelegateFlags();
        }
        acceptStylusHandwritingDelegation(
                delegateView, delegatorPackageName, flags, executor, callback);
    }

    /**
     * Accepts and starts a stylus handwriting session on the delegate view, if handwriting
     * initiation delegation was previously requested using {@link
     * #prepareStylusHandwritingDelegation(View, String)} from the delegator and the view belongs to
     * a specified delegate package.
     *
     * <p>Note: If delegator and delegate are in the same application package, use {@link
     * #acceptStylusHandwritingDelegation(View)} instead.
     *
     * @param delegateView delegate view capable of receiving input via {@link InputConnection}
     * @param delegatorPackageName package name of the delegator that handled initial stylus stroke.
     * @param flags {@link #HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} or {@code 0}
     * @param executor The executor to run the callback on.
     * @param callback {@code true>} would be received if delegation was accepted.
     * @see #prepareStylusHandwritingDelegation(View, String)
     * @see #acceptStylusHandwritingDelegation(View)
     */
    // TODO(b/300979854): Once connectionless APIs are finalised, update documentation to add:
    // <p>Otherwise, if the delegator view previously started delegation using {@link
    // #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver, CursorAnchorInfo,
    // String)}, requests the IME to commit the recognised handwritten text from the connectionless
    // session to the delegate view.
    // @see #startConnectionlessStylusHandwritingForDelegation(View, ResultReceiver,
    //     CursorAnchorInfo, String)
    //
    @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR)
    public void acceptStylusHandwritingDelegation(
            @NonNull View delegateView, @NonNull String delegatorPackageName,
            @HandwritingDelegateFlags int flags, @NonNull @CallbackExecutor Executor executor,
            @NonNull Consumer<Boolean> callback) {
        Objects.requireNonNull(delegatorPackageName);
        Objects.requireNonNull(delegateView);
        Objects.requireNonNull(executor);
        Objects.requireNonNull(callback);

        startStylusHandwritingInternal(
                delegateView, delegatorPackageName, flags, executor, callback);
    }

    /**
     * This method toggles the input method window display.
     * If the input window is already displayed, it gets hidden.
     * If not the input window will be displayed.
     * @param windowToken The token of the window that is making the request,
     * as returned by {@link View#getWindowToken() View.getWindowToken()}.
     *
     * @deprecated Use {@link #showSoftInput(View, int)} or
     * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
     * In particular during focus changes, the current visibility of the IME is not
     * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only
     * has an effect if the calling app is the current IME focus.
     */
    @Deprecated
    public void toggleSoftInputFromWindow(IBinder windowToken, @ShowFlags int showFlags,
            @HideFlags int hideFlags) {
        ImeTracing.getInstance().triggerClientDump(
                "InputMethodManager#toggleSoftInputFromWindow", InputMethodManager.this,
                null /* icProto */);
        synchronized (mH) {
            final View servedView = getServedViewLocked();
            if (servedView == null || servedView.getWindowToken() != windowToken) {
                return;
            }
            toggleSoftInput(showFlags, hideFlags);
        }
    }

    /**
     * This method toggles the input method window display.
     *
     * If the input window is already displayed, it gets hidden.
     * If not the input window will be displayed.
     *
     * @deprecated Use {@link #showSoftInput(View, int)} or
     * {@link #hideSoftInputFromWindow(IBinder, int)} explicitly instead.
     * In particular during focus changes, the current visibility of the IME is not
     * well defined. Starting in {@link Build.VERSION_CODES#S Android S}, this only
     * has an effect if the calling app is the current IME focus.
     */
    @Deprecated
    public void toggleSoftInput(@ShowFlags int showFlags, @HideFlags int hideFlags) {
        ImeTracing.getInstance().triggerClientDump(
                "InputMethodManager#toggleSoftInput", InputMethodManager.this,
                null /* icProto */);
        synchronized (mH) {
            final View view = getServedViewLocked();
            if (view != null) {
                final WindowInsets rootInsets = view.getRootWindowInsets();
                if (rootInsets != null && rootInsets.isVisible(WindowInsets.Type.ime())) {
                    hideSoftInputFromWindow(view.getWindowToken(), hideFlags,
                            null /* resultReceiver */,
                            SoftInputShowHideReason.HIDE_TOGGLE_SOFT_INPUT);
                } else {
                    showSoftInput(view, showFlags, null /* resultReceiver */,
                            SoftInputShowHideReason.SHOW_TOGGLE_SOFT_INPUT);
                }
            }
        }
    }

    /**
     * If the input method is currently connected to the given view,
     * restart it with its new contents.  You should call this when the text
     * within your view changes outside of the normal input method or key
     * input flow, such as when an application calls TextView.setText().
     *
     * @param view The view whose text has changed.
     */
    public void restartInput(View view) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.restartInput(view);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view)) {
                return;
            }

            mServedConnecting = true;
        }

        startInputInner(StartInputReason.APP_CALLED_RESTART_INPUT_API, null, 0, 0, 0);
    }

    /**
     * Sends an async signal to the IME to reset the currently served {@link InputConnection}.
     *
     * @param inputConnection the connection to be invalidated.
     * @param textSnapshot {@link TextSnapshot} to be used to update {@link EditorInfo}.
     * @param sessionId the session ID to be sent.
     * @return {@code true} if the operation is done. {@code false} if the caller needs to fall back
     *         to {@link InputMethodManager#restartInput(View)}.
     * @hide
     */
    public boolean doInvalidateInput(@NonNull RemoteInputConnectionImpl inputConnection,
            @NonNull TextSnapshot textSnapshot, int sessionId) {
        synchronized (mH) {
            if (mServedInputConnection != inputConnection || mCurrentEditorInfo == null) {
                // OK to ignore because the calling InputConnection is already abandoned.
                return true;
            }
            if (!isImeSessionAvailableLocked()) {
                // IME is not yet bound to the client.  Need to fall back to the restartInput().
                return false;
            }
            final EditorInfo editorInfo = mCurrentEditorInfo.createCopyInternal();
            editorInfo.initialSelStart = mCursorSelStart = textSnapshot.getSelectionStart();
            editorInfo.initialSelEnd = mCursorSelEnd = textSnapshot.getSelectionEnd();
            mCursorCandStart = textSnapshot.getCompositionStart();
            mCursorCandEnd = textSnapshot.getCompositionEnd();
            editorInfo.initialCapsMode = textSnapshot.getCursorCapsMode();
            editorInfo.setInitialSurroundingTextInternal(textSnapshot.getSurroundingText());
            mCurBindState.mImeSession.invalidateInput(editorInfo, mServedInputConnection,
                    sessionId);
            final IRemoteAccessibilityInputConnection accessibilityInputConnection =
                    mServedInputConnection.asIRemoteAccessibilityInputConnection();
            forAccessibilitySessionsLocked(wrapper -> wrapper.invalidateInput(editorInfo,
                    accessibilityInputConnection, sessionId));
            return true;
        }
    }

    /**
     * Gives a hint to the system that the text associated with {@code view} is updated by something
     * that is not an input method editor (IME), so that the system can cancel any pending text
     * editing requests from the IME until it receives the new editing context such as surrounding
     * text provided by {@link InputConnection#takeSnapshot()}.
     *
     * <p>When {@code view} does not support {@link InputConnection#takeSnapshot()} protocol,
     * calling this method may trigger {@link View#onCreateInputConnection(EditorInfo)}.</p>
     *
     * <p>Unlike {@link #restartInput(View)}, this API does not immediately interact with
     * {@link InputConnection}.  Instead, the application may later receive
     * {@link InputConnection#takeSnapshot()} as needed so that the system can capture new editing
     * context for the IME.  For instance, successive invocations of this API can be coerced into a
     * single (or zero) callback of {@link InputConnection#takeSnapshot()}.</p>
     *
     * @param view The view whose text has changed.
     * @see #restartInput(View)
     */
    public void invalidateInput(@NonNull View view) {
        Objects.requireNonNull(view);

        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.invalidateInput(view);
            return;
        }

        synchronized (mH) {
            if (mServedInputConnection == null || getServedViewLocked() != view) {
                return;
            }
            mServedInputConnection.scheduleInvalidateInput();
        }
    }

    /**
     * Starts an input connection from the served view that gains the window focus.
     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
     * background thread may blocked by other methods which already inside {@code mH} lock.
     *
     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
     * {@code userId} is different from the user id of the current process.</p>
     */
    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
    private boolean startInputInner(@StartInputReason int startInputReason,
            @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
            @SoftInputModeFlags int softInputMode, int windowFlags) {
        final View view;
        synchronized (mH) {
            view = getServedViewLocked();

            // Make sure we have a window token for the served view.
            if (DEBUG) {
                Log.v(TAG, "Starting input: view=" + InputMethodDebug.dumpViewInfo(view) +
                        " reason=" + InputMethodDebug.startInputReasonToString(startInputReason));
            }
            if (view == null) {
                if (DEBUG) Log.v(TAG, "ABORT input: no served view!");
                return false;
            }
        }

        // Now we need to get an input connection from the served view.
        // This is complicated in a couple ways: we can't be holding our lock
        // when calling out to the view, and we need to make sure we call into
        // the view on the same thread that is driving its view hierarchy.
        Handler vh = view.getHandler();
        if (vh == null) {
            // If the view doesn't have a handler, something has changed out
            // from under us, so just close the current input.
            // If we don't close the current input, the current input method can remain on the
            // screen without a connection.
            if (DEBUG) Log.v(TAG, "ABORT input: no handler for view! Close current input.");
            closeCurrentInput();
            return false;
        }
        if (vh.getLooper() != Looper.myLooper()) {
            // The view is running on a different thread than our own, so
            // we need to reschedule our work for over there.
            if (DEBUG) Log.v(TAG, "Starting input: reschedule to view thread");
            vh.post(() -> startInputOnWindowFocusGainInternal(startInputReason, null, 0, 0, 0));
            return false;
        }

        if (windowGainingFocus == null) {
            windowGainingFocus = view.getWindowToken();
            if (windowGainingFocus == null) {
                Log.e(TAG, "ABORT input: ServedView must be attached to a Window");
                return false;
            }
            startInputFlags = getStartInputFlags(view, startInputFlags);
            softInputMode = view.getViewRootImpl().mWindowAttributes.softInputMode;
            windowFlags = view.getViewRootImpl().mWindowAttributes.flags;
        }

        // Okay we are now ready to call into the served view and have it
        // do its stuff.
        // Life is good: let's hook everything up!
        final Pair<InputConnection, EditorInfo> connectionPair = createInputConnection(view);
        final InputConnection ic = connectionPair.first;
        final EditorInfo editorInfo = connectionPair.second;
        final Handler icHandler;
        InputBindResult res = null;
        final boolean hasServedView;
        synchronized (mH) {
            // Now that we are locked again, validate that our state hasn't
            // changed.
            final View servedView = getServedViewLocked();
            if (servedView != view || !mServedConnecting) {
                // Something else happened, so abort.
                if (DEBUG) Log.v(TAG, "Starting input: finished by someone else."
                        + " view=" + InputMethodDebug.dumpViewInfo(view)
                        + " servedView=" + InputMethodDebug.dumpViewInfo(servedView)
                        + " mServedConnecting=" + mServedConnecting);
                if (mServedInputConnection != null && startInputReason == BOUND_TO_IMMS) {
                    // This is not an error. Once IME binds (MSG_BIND), InputConnection is fully
                    // established. So we report this to interested recipients.
                    reportInputConnectionOpened(
                            mServedInputConnection.getInputConnection(), mCurrentEditorInfo,
                            mServedInputConnectionHandler, view);
                }
                return false;
            }

            // If we already have a text box, then this view is already
            // connected so we want to restart it.
            if (mCurrentEditorInfo == null) {
                startInputFlags |= StartInputFlags.INITIAL_CONNECTION;
            }

            editorInfo.setInitialToolType(mCurRootView.getLastClickToolType());

            // Hook 'em up and let 'er rip.
            mCurrentEditorInfo = editorInfo.createCopyInternal();
            // Store the previously served connection so that we can determine whether it is safe
            // to skip the call to startInputOrWindowGainedFocus in the IMMS
            final RemoteInputConnectionImpl previouslyServedConnection = mServedInputConnection;

            mServedConnecting = false;
            if (mServedInputConnection != null) {
                mServedInputConnection.deactivate();
                mServedInputConnection = null;
                mServedInputConnectionHandler = null;
            }
            final RemoteInputConnectionImpl servedInputConnection;
            if (ic != null) {
                mCursorSelStart = editorInfo.initialSelStart;
                mCursorSelEnd = editorInfo.initialSelEnd;
                mInitialSelStart = mCursorSelStart;
                mInitialSelEnd = mCursorSelEnd;
                mCursorCandStart = -1;
                mCursorCandEnd = -1;
                mCursorRect.setEmpty();
                mCursorAnchorInfo = null;
                Handler handler = null;
                try {
                    handler = ic.getHandler();
                } catch (AbstractMethodError ignored) {
                    // TODO(b/199934664): See if we can remove this by providing a default impl.
                }
                icHandler = handler;
                mServedInputConnectionHandler = icHandler;
                servedInputConnection = new RemoteInputConnectionImpl(
                        icHandler != null ? icHandler.getLooper() : vh.getLooper(), ic, this, view);
            } else {
                servedInputConnection = null;
                icHandler = null;
                mServedInputConnectionHandler = null;
            }
            mServedInputConnection = servedInputConnection;

            if (DEBUG) {
                Log.v(TAG, "START INPUT: view=" + InputMethodDebug.dumpViewInfo(view)
                        + " ic=" + ic + " editorInfo=" + editorInfo + " startInputFlags="
                        + InputMethodDebug.startInputFlagsToString(startInputFlags));
            }

            // When we switch between non-editable views, do not call into the IMMS.
            final boolean canSkip = OPTIMIZE_NONEDITABLE_VIEWS
                    && previouslyServedConnection == null
                    && ic == null
                    && isSwitchingBetweenEquivalentNonEditableViews(
                            mPreviousViewFocusParameters, startInputFlags,
                            startInputReason, softInputMode, windowFlags);
            mPreviousViewFocusParameters = new ViewFocusParameterInfo(mCurrentEditorInfo,
                    startInputFlags, startInputReason, softInputMode, windowFlags);
            if (canSkip) {
                if (DEBUG) {
                    Log.d(TAG, "Not calling IMMS due to switching between non-editable views.");
                }
                return false;
            }
            final int targetUserId = editorInfo.targetInputMethodUser != null
                    ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMM.startInputOrWindowGainedFocus");

            int startInputSeq = -1;
            if (Flags.useZeroJankProxy()) {
                // async result delivered via MSG_START_INPUT_RESULT.
                startInputSeq = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocusAsync(
                        startInputReason, mClient, windowGainingFocus, startInputFlags,
                        softInputMode, windowFlags, editorInfo, servedInputConnection,
                        servedInputConnection == null ? null
                                : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                        view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                        mImeDispatcher);
            } else {
                res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                        startInputReason, mClient, windowGainingFocus, startInputFlags,
                        softInputMode, windowFlags, editorInfo, servedInputConnection,
                        servedInputConnection == null ? null
                                : servedInputConnection.asIRemoteAccessibilityInputConnection(),
                        view.getContext().getApplicationInfo().targetSdkVersion, targetUserId,
                        mImeDispatcher);
            }
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            if (Flags.useZeroJankProxy()) {
                // Create a runnable for delayed notification to the app that the InputConnection is
                // initialized and ready for use.
                if (ic != null) {
                    final int seqId = startInputSeq;
                    mReportInputConnectionOpenedRunner =
                            new ReportInputConnectionOpenedRunner(startInputSeq) {
                                @Override
                                public void run() {
                                    if (DEBUG) {
                                        Log.v(TAG, "Calling View.onInputConnectionOpened: view= "
                                                + view
                                                + ", ic=" + ic + ", editorInfo=" + editorInfo
                                                + ", handler="
                                                + icHandler + ", startInputSeq=" + seqId);
                                    }
                                    reportInputConnectionOpened(ic, editorInfo, icHandler, view);
                                }
                            };
                } else {
                    mReportInputConnectionOpenedRunner = null;
                }
                return true;
            }

            if (DEBUG) Log.v(TAG, "Starting input: Bind result=" + res);
            if (res == null) {
                Log.wtf(TAG, "startInputOrWindowGainedFocus must not return"
                        + " null. startInputReason="
                        + InputMethodDebug.startInputReasonToString(startInputReason)
                        + " editorInfo=" + editorInfo
                        + " startInputFlags="
                        + InputMethodDebug.startInputFlagsToString(startInputFlags));
                return false;
            }
            if (res.id != null) {
                updateInputChannelLocked(res.channel);
                mCurMethod = res.method; // for @UnsupportedAppUsage
                mCurBindState = new BindState(res);
                mAccessibilityInputMethodSession.clear();
                if (res.accessibilitySessions != null) {
                    for (int i = 0; i < res.accessibilitySessions.size(); i++) {
                        IAccessibilityInputMethodSessionInvoker wrapper =
                                IAccessibilityInputMethodSessionInvoker.createOrNull(
                                        res.accessibilitySessions.valueAt(i));
                        if (wrapper != null) {
                            mAccessibilityInputMethodSession.append(
                                    res.accessibilitySessions.keyAt(i), wrapper);
                        }
                    }
                }
                mCurId = res.id; // for @UnsupportedAppUsage
            } else if (res.channel != null && res.channel != mCurChannel) {
                res.channel.dispose();
            }

            switch (res.result) {
                case InputBindResult.ResultCode.ERROR_NOT_IME_TARGET_WINDOW:
                    mRestartOnNextWindowFocus = true;
                    if (initiationWithoutInputConnection()) {
                        mServedView.getViewRootImpl().getHandwritingInitiator().clearFocusedView(
                                mServedView);
                    }
                    mServedView = null;
                    break;
            }
            if (mCompletions != null) {
                if (isImeSessionAvailableLocked()) {
                    mCurBindState.mImeSession.displayCompletions(mCompletions);
                }
            }
            hasServedView = mServedView != null;
        }

        // Notify the app that the InputConnection is initialized and ready for use.
        if (ic != null && res != null && res.method != null && hasServedView) {
            if (DEBUG) {
                Log.v(TAG, "Calling View.onInputConnectionOpened: view= " + view
                        + ", ic=" + ic + ", editorInfo=" + editorInfo + ", handler=" + icHandler);
            }
            reportInputConnectionOpened(ic, editorInfo, icHandler, view);
        }

        return true;
    }

    /**
     * @return {@code true} when we are switching focus between two non-editable views
     * so that we can avoid calling {@link IInputMethodManager#startInputOrWindowGainedFocus}.
     */
    @GuardedBy("mH")
    private boolean isSwitchingBetweenEquivalentNonEditableViews(
            @Nullable ViewFocusParameterInfo previousViewFocusParameters,
            @StartInputFlags int startInputFlags,
            @StartInputReason int startInputReason,
            @SoftInputModeFlags int softInputMode,
            int windowFlags) {
        return (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) == 0
                && (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) == 0
                && previousViewFocusParameters != null
                && previousViewFocusParameters.sameAs(mCurrentEditorInfo,
                    startInputFlags, startInputReason, softInputMode, windowFlags);
    }

    private void reportInputConnectionOpened(
            InputConnection ic, EditorInfo editorInfo, Handler icHandler, View view) {
        view.onInputConnectionOpenedInternal(ic, editorInfo, icHandler);
        final ViewRootImpl viewRoot = view.getViewRootImpl();
        if (viewRoot != null) {
            viewRoot.getHandwritingInitiator().onInputConnectionCreated(view);
        }
    }

    /**
     *
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public void addVirtualStylusIdForTestSession() {
        synchronized (mH) {
            IInputMethodManagerGlobalInvoker.addVirtualStylusIdForTestSession(mClient);
        }
    }

    /**
     * Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed.
     * <p> This API is for tests only.</p>
     * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
        synchronized (mH) {
            IInputMethodManagerGlobalInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
        }
    }

    /**
     * An empty method only to avoid crashes of apps that call this method via reflection and do not
     * handle {@link NoSuchMethodException} in a graceful manner.
     *
     * @deprecated This is an empty method.  No framework method must call this method.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage(trackingBug = 37122102, maxTargetSdk = Build.VERSION_CODES.Q,
            publicAlternatives = "{@code androidx.activity.ComponentActivity}")
    public void windowDismissed(IBinder appWindowToken) {
        // Intentionally empty.
        //
        // It seems that some applications call this method via reflection to null clear the
        // following fields that used to exist in InputMethodManager:
        //  * InputMethodManager#mCurRootView
        //  * InputMethodManager#mServedView
        //  * InputMethodManager#mNextServedView
        // so that these objects can be garbage-collected when an Activity gets dismissed.
        //
        // It is indeed true that older versions of InputMethodManager had issues that prevented
        // these fields from being null-cleared when it should have been, but the understanding of
        // the engineering team is that all known issues have already been fixed as of Android 10.
        //
        // For older devices, developers can work around the object leaks by using
        // androidx.activity.ComponentActivity.
        // See https://issuetracker.google.com/u/1/issues/37122102 for details.
        //
        // If you believe InputMethodManager is leaking objects in API 24 or any later version,
        // please file a bug at https://issuetracker.google.com/issues/new?component=192705.
    }

    private int getStartInputFlags(View focusedView, int startInputFlags) {
        startInputFlags |= StartInputFlags.VIEW_HAS_FOCUS;
        if (focusedView.onCheckIsTextEditor()) {
            startInputFlags |= StartInputFlags.IS_TEXT_EDITOR;
        }
        return startInputFlags;
    }

    /**
     * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
     * background thread may blocked by other methods which already inside {@code mH} lock.
     * @hide
     */
    @UnsupportedAppUsage
    public void checkFocus() {
        synchronized (mH) {
            if (mCurRootView == null) {
                return;
            }
            if (!checkFocusInternalLocked(false /* forceNewFocus */, mCurRootView)) {
                return;
            }
        }
        startInputOnWindowFocusGainInternal(StartInputReason.CHECK_FOCUS,
                null /* focusedView */,
                0 /* startInputFlags */, 0 /* softInputMode */, 0 /* windowFlags */);
    }

    /**
     * Check the next served view if needs to start input.
     */
    @GuardedBy("mH")
    private boolean checkFocusInternalLocked(boolean forceNewFocus, ViewRootImpl viewRootImpl) {
        if (mCurRootView != viewRootImpl) {
            return false;
        }
        if (mServedView == mNextServedView && !forceNewFocus) {
            return false;
        }
        if (DEBUG) {
            Log.v(TAG, "checkFocus: view=" + mServedView
                    + " next=" + mNextServedView
                    + " force=" + forceNewFocus
                    + " package="
                    + (mServedView != null ? mServedView.getContext().getPackageName()
                    : "<none>"));
        }
        // Close the connection when no next served view coming.
        if (mNextServedView == null) {
            finishInputLocked();
            closeCurrentInput();
            return false;
        }
        mServedView = mNextServedView;
        if (mServedInputConnection != null) {
            mServedInputConnection.finishComposingTextFromImm();
        }
        return true;
    }

    @UiThread
    private void onViewFocusChangedInternal(@Nullable View view, boolean hasFocus) {
        if (view == null || view.isTemporarilyDetached()) {
            return;
        }
        final ViewRootImpl viewRootImpl = view.getViewRootImpl();
        synchronized (mH) {
            if (mCurRootView != viewRootImpl) {
                return;
            }
            if (!view.hasImeFocus() || !view.hasWindowFocus()) {
                return;
            }
            if (DEBUG) {
                Log.d(TAG, "onViewFocusChangedInternal, view="
                        + InputMethodDebug.dumpViewInfo(view));
            }

            // We don't need to track the next served view when the view lost focus here
            // because:
            // 1) The current view focus may be cleared temporary when in touch mode, closing
            //    input at this moment isn't the right way.
            // 2) We only care about the served view change when it focused, since changing
            //    input connection when the focus target changed is reasonable.
            // 3) Setting the next served view as null when no more served view should be
            //    handled in other special events (e.g. view detached from window or the window
            //    dismissed).
            if (hasFocus) {
                mNextServedView = view;
            }
        }
        viewRootImpl.dispatchCheckFocus();
    }

    @UnsupportedAppUsage
    void closeCurrentInput() {
        final int reason = SoftInputShowHideReason.HIDE_CLOSE_CURRENT_SESSION;
        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
        ImeTracker.forLatency().onRequestHide(statsToken,
                ImeTracker.ORIGIN_CLIENT, reason,
                ActivityThread::currentApplication);

        synchronized (mH) {
            final View rootView = mCurRootView != null ? mCurRootView.getView() : null;
            if (rootView == null) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                ImeTracker.forLatency().onHideFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                return;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            IInputMethodManagerGlobalInvoker.hideSoftInput(
                    mClient,
                    rootView.getWindowToken(),
                    statsToken,
                    HIDE_NOT_ALWAYS,
                    null,
                    reason);
        }
    }

    /**
     * Register for IME state callbacks and applying visibility in
     * {@link android.view.ImeInsetsSourceConsumer}.
     * @hide
     */
    public void registerImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
        if (imeInsetsConsumer == null) {
            throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
        }

        synchronized (mH) {
            mImeInsetsConsumer = imeInsetsConsumer;
        }
    }

    /**
     * Unregister for IME state callbacks and applying visibility in
     * {@link android.view.ImeInsetsSourceConsumer}.
     * @hide
     */
    public void unregisterImeConsumer(@NonNull ImeInsetsSourceConsumer imeInsetsConsumer) {
        if (imeInsetsConsumer == null) {
            throw new IllegalStateException("ImeInsetsSourceConsumer cannot be null.");
        }

        synchronized (mH) {
            if (mImeInsetsConsumer == imeInsetsConsumer) {
                mImeInsetsConsumer = null;
            }
        }
    }

    /**
     * Call showSoftInput with currently focused view.
     *
     * @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 request.
     *
     * @return {@code true} if IME can (eventually) be shown, {@code false} otherwise.
     * @hide
     */
    public boolean requestImeShow(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
        checkFocus();
        synchronized (mH) {
            final View servedView = getServedViewLocked();
            if (servedView == null || servedView.getWindowToken() != windowToken) {
                ImeTracker.forLogging().onFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);
                return false;
            }

            ImeTracker.forLogging().onProgress(statsToken,
                    ImeTracker.PHASE_CLIENT_REQUEST_IME_SHOW);

            showSoftInput(servedView, statsToken, 0 /* flags */, null /* resultReceiver */,
                    SoftInputShowHideReason.SHOW_SOFT_INPUT_BY_INSETS_API);
            return true;
        }
    }

    /**
     * Notify IMMS that IME insets are no longer visible.
     *
     * @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 request.
     * @hide
     */
    public void notifyImeHidden(IBinder windowToken, @NonNull ImeTracker.Token statsToken) {
        ImeTracker.forLatency().onRequestHide(statsToken, ImeTracker.ORIGIN_CLIENT,
                SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API,
                ActivityThread::currentApplication);
        ImeTracing.getInstance().triggerClientDump("InputMethodManager#notifyImeHidden", this,
                null /* icProto */);
        synchronized (mH) {
            if (!isImeSessionAvailableLocked() || mCurRootView == null
                    || mCurRootView.getWindowToken() != windowToken) {
                ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);
                ImeTracker.forLatency().onHideFailed(statsToken,
                        ImeTracker.PHASE_CLIENT_VIEW_SERVED, ActivityThread::currentApplication);
                return;
            }

            ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_CLIENT_VIEW_SERVED);

            IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, statsToken,
                    0 /* flags */, null /* resultReceiver */,
                    SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
        }
    }

    /**
     * Notify IME directly to remove surface as it is no longer visible.
     * @param windowToken The client window token that requests the IME to remove its surface.
     * @hide
     */
    public void removeImeSurface(@NonNull IBinder windowToken) {
        synchronized (mH) {
            IInputMethodManagerGlobalInvoker.removeImeSurfaceFromWindowAsync(windowToken);
        }
    }

    /**
     * Report the current selection range.
     *
     * <p><strong>Editor authors</strong>, you need to call this method whenever
     * the cursor moves in your editor. Remember that in addition to doing this, your
     * editor needs to always supply current cursor values in
     * {@link EditorInfo#initialSelStart} and {@link EditorInfo#initialSelEnd} every
     * time {@link android.view.View#onCreateInputConnection(EditorInfo)} is
     * called, which happens whenever the keyboard shows up or the focus changes
     * to a text field, among other cases.</p>
     */
    public void updateSelection(View view, int selStart, int selEnd,
            int candidatesStart, int candidatesEnd) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.updateSelection(view, selStart, selEnd, candidatesStart, candidatesEnd);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                    || !isImeSessionAvailableLocked()) {
                return;
            }

            if (mServedInputConnection != null && mServedInputConnection.hasPendingInvalidation()) {
                return;
            }

            if (mCursorSelStart != selStart || mCursorSelEnd != selEnd
                    || mCursorCandStart != candidatesStart
                    || mCursorCandEnd != candidatesEnd) {
                if (DEBUG) Log.d(TAG, "updateSelection");

                if (DEBUG) {
                    Log.v(TAG, "SELECTION CHANGE: " + mCurBindState.mImeSession);
                }
                mCurBindState.mImeSession.updateSelection(mCursorSelStart, mCursorSelEnd, selStart,
                        selEnd, candidatesStart, candidatesEnd);
                forAccessibilitySessionsLocked(wrapper -> wrapper.updateSelection(mCursorSelStart,
                        mCursorSelEnd, selStart, selEnd, candidatesStart, candidatesEnd));
                mCursorSelStart = selStart;
                mCursorSelEnd = selEnd;
                mCursorCandStart = candidatesStart;
                mCursorCandEnd = candidatesEnd;
            }
        }
    }

    /**
     * Notify the event when the user tapped or clicked the text view.
     *
     * @param view {@link View} which is being clicked.
     * @see InputMethodService#onViewClicked(boolean)
     * @deprecated The semantics of this method can never be defined well for composite {@link View}
     *             that works as a giant "Canvas", which can host its own UI hierarchy and sub focus
     *             state. {@link android.webkit.WebView} is a good example. Application / IME
     *             developers should not rely on this method.
     */
    @Deprecated
    public void viewClicked(View view) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.viewClicked(view);
            return;
        }

        final View servedView;
        final View nextServedView;
        synchronized (mH) {
            servedView = getServedViewLocked();
            nextServedView = getNextServedViewLocked();
        }
        final boolean focusChanged = servedView != nextServedView;
        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                    || !isImeSessionAvailableLocked()) {
                return;
            }
            if (DEBUG) Log.v(TAG, "onViewClicked: " + focusChanged);
            mCurBindState.mImeSession.viewClicked(focusChanged);
        }
    }

    /**
     * Return true if the current input method wants to watch the location
     * of the input editor's cursor in its window.
     *
     * @deprecated Use {@link InputConnection#requestCursorUpdates(int)} instead.
     */
    @Deprecated
    public boolean isWatchingCursor(View view) {
        return false;
    }

    /**
     * Return true if the current input method wants to be notified when cursor/anchor location
     * is changed.
     *
     * @deprecated This method is kept for {@link UnsupportedAppUsage}.  Must not be used.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public boolean isCursorAnchorInfoEnabled() {
        synchronized (mH) {
            final boolean isImmediate = (mRequestUpdateCursorAnchorInfoMonitorMode &
                    CURSOR_UPDATE_IMMEDIATE) != 0;
            final boolean isMonitoring = (mRequestUpdateCursorAnchorInfoMonitorMode &
                    CURSOR_UPDATE_MONITOR) != 0;
            return isImmediate || isMonitoring;
        }
    }

    /**
     * Set the requested mode for {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)}.
     *
     * @deprecated This method is kept for {@link UnsupportedAppUsage}.  Must not be used.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    public void setUpdateCursorAnchorInfoMode(int flags) {
        synchronized (mH) {
            mRequestUpdateCursorAnchorInfoMonitorMode = flags;
        }
    }

    /**
     * Report the current cursor location in its window.
     *
     * @deprecated Use {@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} instead.
     */
    @Deprecated
    public void updateCursor(View view, int left, int top, int right, int bottom) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.updateCursor(view, left, top, right, bottom);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                    || !isImeSessionAvailableLocked()) {
                return;
            }

            mTmpCursorRect.set(left, top, right, bottom);
            if (!mCursorRect.equals(mTmpCursorRect)) {
                if (DEBUG) Log.d(TAG, "updateCursor: " + mCurBindState.mImeSession);

                mCurBindState.mImeSession.updateCursor(mTmpCursorRect);
                mCursorRect.set(mTmpCursorRect);
            }
        }
    }

    /**
     * Report positional change of the text insertion point and/or characters in the composition
     * string.
     */
    public void updateCursorAnchorInfo(View view, final CursorAnchorInfo cursorAnchorInfo) {
        if (view == null || cursorAnchorInfo == null) {
            return;
        }
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.updateCursorAnchorInfo(view, cursorAnchorInfo);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                    || !isImeSessionAvailableLocked()) {
                return;
            }
            // If immediate bit is set, we will call updateCursorAnchorInfo() even when the data has
            // not been changed from the previous call.
            final boolean isImmediate = mServedInputConnection != null
                    && mServedInputConnection.resetHasPendingImmediateCursorAnchorInfoUpdate();
            if (!isImmediate && Objects.equals(mCursorAnchorInfo, cursorAnchorInfo)) {
                // TODO: Consider always emitting this message once we have addressed redundant
                // calls of this method from android.widget.Editor.
                if (DEBUG) {
                    Log.w(TAG, "Ignoring redundant updateCursorAnchorInfo: info="
                            + cursorAnchorInfo);
                }
                return;
            }
            if (DEBUG) Log.v(TAG, "updateCursorAnchorInfo: " + cursorAnchorInfo);
            mCurBindState.mImeSession.updateCursorAnchorInfo(cursorAnchorInfo);
            mCursorAnchorInfo = cursorAnchorInfo;
        }
    }

    /**
     * Call {@link InputMethodSession#appPrivateCommand(String, Bundle)
     * InputMethodSession.appPrivateCommand()} on the current Input Method.
     * @param view Optional View that is sending the command, or null if
     * you want to send the command regardless of the view that is attached
     * to the input method.
     * @param action Name of the command to be performed.  This <em>must</em>
     * be a scoped name, i.e. prefixed with a package name you own, so that
     * different developers will not create conflicting commands.
     * @param data Any data to include with the command.
     */
    public void sendAppPrivateCommand(View view, String action, Bundle data) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(view);
        if (fallbackImm != null) {
            fallbackImm.sendAppPrivateCommand(view, action, data);
            return;
        }

        checkFocus();
        synchronized (mH) {
            if (!hasServedByInputMethodLocked(view) || mCurrentEditorInfo == null
                    || !isImeSessionAvailableLocked()) {
                return;
            }
            if (DEBUG) Log.v(TAG, "APP PRIVATE COMMAND " + action + ": " + data);
            mCurBindState.mImeSession.appPrivateCommand(action, data);
        }
    }

    /**
     * Force switch to a new input method component. This can only be called
     * from an application or a service which has a token of the currently active input method.
     *
     * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that
     * token can be {@code null} when the caller has
     * {@link Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
     * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
     * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
     *
     * @param token Supplies the identifying token given to an input method
     * when it was started, which allows it to perform this operation on
     * itself.
     * @param id The unique identifier for the new input method to be switched to.
     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
     * @deprecated Use {@link InputMethodService#switchInputMethod(String)}
     * instead. This method was intended for IME developers who should be accessing APIs through
     * the service. APIs in this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void setInputMethod(IBinder token, String id) {
        if (token == null) {
            // There are still some system components that rely on this undocumented behavior
            // regarding null IME token with WRITE_SECURE_SETTINGS.  Provide a fallback logic as a
            // temporary remedy.
            if (id == null) {
                return;
            }
            if (Process.myUid() == Process.SYSTEM_UID) {
                Log.w(TAG, "System process should not be calling setInputMethod() because almost "
                        + "always it is a bug under multi-user / multi-profile environment. "
                        + "Consider interacting with InputMethodManagerService directly via "
                        + "LocalServices.");
                return;
            }
            final Context fallbackContext = ActivityThread.currentApplication();
            if (fallbackContext == null) {
                return;
            }
            if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                    != PackageManager.PERMISSION_GRANTED) {
                return;
            }
            final List<InputMethodInfo> imis = getEnabledInputMethodList();
            final int numImis = imis.size();
            boolean found = false;
            for (int i = 0; i < numImis; ++i) {
                final InputMethodInfo imi = imis.get(i);
                if (id.equals(imi.getId())) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                Log.e(TAG, "Ignoring setInputMethod(null, " + id + ") because the specified "
                        + "id not found in enabled IMEs.");
                return;
            }
            Log.w(TAG, "The undocumented behavior that setInputMethod() accepts null token "
                    + "when the caller has WRITE_SECURE_SETTINGS is deprecated. This behavior may "
                    + "be completely removed in a future version.  Update secure settings directly "
                    + "instead.");
            final ContentResolver resolver = fallbackContext.getContentResolver();
            Settings.Secure.putInt(resolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
                    NOT_A_SUBTYPE_ID);
            Settings.Secure.putString(resolver, Settings.Secure.DEFAULT_INPUT_METHOD, id);
            return;
        }
        InputMethodPrivilegedOperationsRegistry.get(token).setInputMethod(id);
    }

    /**
     * Force switch to a new input method and subtype. This can only be called
     * from an application or a service which has a token of the currently active input method.
     *
     * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be
     * {@code null} even with {@link Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
     * update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
     * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
     *
     * @param token Supplies the identifying token given to an input method
     * when it was started, which allows it to perform this operation on
     * itself.
     * @param id The unique identifier for the new input method to be switched to.
     * @param subtype The new subtype of the new input method to be switched to.
     * @throws IllegalArgumentException if the input method is unknown or filtered by the rules of
     * <a href="/training/basics/intents/package-visibility">package visibility</a>.
     * @deprecated Use
     * {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}
     * instead. This method was intended for IME developers who should be accessing APIs through
     * the service. APIs in this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void setInputMethodAndSubtype(@NonNull IBinder token, String id,
            InputMethodSubtype subtype) {
        if (token == null) {
            Log.e(TAG, "setInputMethodAndSubtype() does not accept null token on Android Q "
                    + "and later.");
            return;
        }
        InputMethodPrivilegedOperationsRegistry.get(token).setInputMethodAndSubtype(id, subtype);
    }

    /**
     * Close/hide the input method's soft input area, so the user no longer
     * sees it or can interact with it.  This can only be called
     * from the currently active input method, as validated by the given token.
     *
     * @param token Supplies the identifying token given to an input method
     * when it was started, which allows it to perform this operation on
     * itself.
     * @deprecated Use {@link InputMethodService#requestHideSelf(int)} instead. This method was
     * intended for IME developers who should be accessing APIs through the service. APIs in this
     * class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void hideSoftInputFromInputMethod(IBinder token, @HideFlags int flags) {
        final int reason = SoftInputShowHideReason.HIDE_SOFT_INPUT_IMM_DEPRECATION;
        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_HIDE,
                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
        InputMethodPrivilegedOperationsRegistry.get(token).hideMySoftInput(statsToken, flags,
                reason);
    }

    /**
     * Show the input method's soft input area, so the user
     * sees the input method window and can interact with it.
     * This can only be called from the currently active input method,
     * as validated by the given token.
     *
     * @param token Supplies the identifying token given to an input method
     * when it was started, which allows it to perform this operation on
     * itself.
     * @deprecated Use {@link InputMethodService#requestShowSelf(int)} instead. This method was
     * intended for IME developers who should be accessing APIs through the service. APIs in this
     * class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public void showSoftInputFromInputMethod(IBinder token, @ShowFlags int flags) {
        final int reason = SoftInputShowHideReason.SHOW_SOFT_INPUT_IMM_DEPRECATION;
        final var statsToken = ImeTracker.forLogging().onStart(ImeTracker.TYPE_SHOW,
                ImeTracker.ORIGIN_CLIENT, reason, false /* fromUser */);
        InputMethodPrivilegedOperationsRegistry.get(token).showMySoftInput(statsToken, flags,
                reason);
    }

    /**
     * Dispatches an input event to the IME.
     *
     * Returns {@link #DISPATCH_HANDLED} if the event was handled.
     * Returns {@link #DISPATCH_NOT_HANDLED} if the event was not handled.
     * Returns {@link #DISPATCH_IN_PROGRESS} if the event is in progress and the
     * callback will be invoked later.
     *
     * @hide
     */
    public int dispatchInputEvent(InputEvent event, Object token,
            FinishedInputEventCallback callback, Handler handler) {
        synchronized (mH) {
            if (isImeSessionAvailableLocked()) {
                if (event instanceof KeyEvent) {
                    KeyEvent keyEvent = (KeyEvent)event;
                    if (keyEvent.getAction() == KeyEvent.ACTION_DOWN
                            && keyEvent.getKeyCode() == KeyEvent.KEYCODE_SYM
                            && keyEvent.getRepeatCount() == 0) {
                        showInputMethodPickerLocked();
                        return DISPATCH_HANDLED;
                    }
                }

                if (DEBUG) {
                    Log.v(TAG, "DISPATCH INPUT EVENT: " + mCurBindState.mImeSession);
                }

                PendingEvent p = obtainPendingEventLocked(
                        event, token, mCurBindState.mImeId, callback, handler);
                if (mMainLooper.isCurrentThread()) {
                    // Already running on the IMM thread so we can send the event immediately.
                    return sendInputEventOnMainLooperLocked(p);
                }

                // Post the event to the IMM thread.
                Message msg = mH.obtainMessage(MSG_SEND_INPUT_EVENT, p);
                msg.setAsynchronous(true);
                mH.sendMessage(msg);
                return DISPATCH_IN_PROGRESS;
            }
        }
        return DISPATCH_NOT_HANDLED;
    }

    /**
     * Provides the default implementation of {@link InputConnection#sendKeyEvent(KeyEvent)}, which
     * is expected to dispatch an keyboard event sent from the IME to an appropriate event target
     * depending on the given {@link View} and the current focus state.
     *
     * <p>CAUTION: This method is provided only for the situation where
     * {@link InputConnection#sendKeyEvent(KeyEvent)} needs to be implemented without relying on
     * {@link BaseInputConnection}. Do not use this API for anything else.</p>
     *
     * @param targetView the default target view. If {@code null} is specified, then this method
     * tries to find a good event target based on the current focus state.
     * @param event the key event to be dispatched.
     */
    public void dispatchKeyEventFromInputMethod(@Nullable View targetView,
            @NonNull KeyEvent event) {
        // Re-dispatch if there is a context mismatch.
        final InputMethodManager fallbackImm = getFallbackInputMethodManagerIfNecessary(targetView);
        if (fallbackImm != null) {
            fallbackImm.dispatchKeyEventFromInputMethod(targetView, event);
            return;
        }

        synchronized (mH) {
            ViewRootImpl viewRootImpl = targetView != null ? targetView.getViewRootImpl() : null;
            if (viewRootImpl == null) {
                final View servedView = getServedViewLocked();
                if (servedView != null) {
                    viewRootImpl = servedView.getViewRootImpl();
                }
            }
            if (viewRootImpl != null) {
                viewRootImpl.dispatchKeyFromIme(event);
            }
        }
    }

    // Must be called on the main looper
    private void sendInputEventAndReportResultOnMainLooper(PendingEvent p) {
        final boolean handled;
        synchronized (mH) {
            int result = sendInputEventOnMainLooperLocked(p);
            if (result == DISPATCH_IN_PROGRESS) {
                return;
            }

            handled = (result == DISPATCH_HANDLED);
        }

        invokeFinishedInputEventCallback(p, handled);
    }

    // Must be called on the main looper
    @GuardedBy("mH")
    private int sendInputEventOnMainLooperLocked(PendingEvent p) {
        if (mCurChannel != null) {
            if (mCurSender == null) {
                mCurSender = new ImeInputEventSender(mCurChannel, mH.getLooper());
            }

            final InputEvent event = p.mEvent;
            final int seq = event.getSequenceNumber();
            if (mCurSender.sendInputEvent(seq, event)) {
                mPendingEvents.put(seq, p);
                Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER,
                        mPendingEvents.size());

                Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
                msg.setAsynchronous(true);
                mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);
                return DISPATCH_IN_PROGRESS;
            }

            if (sPreventImeStartupUnlessTextEditor) {
                Log.d(TAG, "Dropping event because IME is evicted: " + event);
            } else {
                Log.w(TAG, "Unable to send input event to IME: " + getImeIdLocked()
                        + " dropping: " + event);
            }
        }
        return DISPATCH_NOT_HANDLED;
    }

    private void finishedInputEvent(int seq, boolean handled, boolean timeout) {
        final PendingEvent p;
        synchronized (mH) {
            int index = mPendingEvents.indexOfKey(seq);
            if (index < 0) {
                return; // spurious, event already finished or timed out
            }

            p = mPendingEvents.valueAt(index);
            mPendingEvents.removeAt(index);
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, PENDING_EVENT_COUNTER, mPendingEvents.size());

            if (timeout) {
                Log.w(TAG, "Timeout waiting for IME to handle input event after "
                        + INPUT_METHOD_NOT_RESPONDING_TIMEOUT + " ms: " + p.mInputMethodId);
            } else {
                mH.removeMessages(MSG_TIMEOUT_INPUT_EVENT, p);
            }
        }

        invokeFinishedInputEventCallback(p, handled);
    }

    // Assumes the event has already been removed from the queue.
    private void invokeFinishedInputEventCallback(PendingEvent p, boolean handled) {
        p.mHandled = handled;
        if (p.mHandler.getLooper().isCurrentThread()) {
            // Already running on the callback handler thread so we can send the
            // callback immediately.
            p.run();
        } else {
            // Post the event to the callback handler thread.
            // In this case, the callback will be responsible for recycling the event.
            Message msg = Message.obtain(p.mHandler, p);
            msg.setAsynchronous(true);
            msg.sendToTarget();
        }
    }

    @GuardedBy("mH")
    private void flushPendingEventsLocked() {
        mH.removeMessages(MSG_FLUSH_INPUT_EVENT);

        final int count = mPendingEvents.size();
        for (int i = 0; i < count; i++) {
            int seq = mPendingEvents.keyAt(i);
            Message msg = mH.obtainMessage(MSG_FLUSH_INPUT_EVENT, seq, 0);
            msg.setAsynchronous(true);
            msg.sendToTarget();
        }
    }

    @GuardedBy("mH")
    private PendingEvent obtainPendingEventLocked(InputEvent event, Object token,
            String inputMethodId, FinishedInputEventCallback callback, Handler handler) {
        PendingEvent p = mPendingEventPool.acquire();
        if (p == null) {
            p = new PendingEvent();
        }
        p.mEvent = event;
        p.mToken = token;
        p.mInputMethodId = inputMethodId;
        p.mCallback = callback;
        p.mHandler = handler;
        return p;
    }

    @GuardedBy("mH")
    private void recyclePendingEventLocked(PendingEvent p) {
        p.recycle();
        mPendingEventPool.release(p);
    }

    /**
     * Show IME picker popup window.
     *
     * <p>Requires the {@link PackageManager#FEATURE_INPUT_METHODS} feature which can be detected
     * using {@link PackageManager#hasSystemFeature(String)}.
     */
    public void showInputMethodPicker() {
        synchronized (mH) {
            showInputMethodPickerLocked();
        }
    }

    /**
     * Shows the input method chooser dialog from system.
     *
     * @param showAuxiliarySubtypes Set true to show auxiliary input methods.
     * @param displayId The ID of the display where the chooser dialog should be shown.
     * @hide
     */
    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
    public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) {
        final int mode = showAuxiliarySubtypes
                ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
                : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
    }

    @GuardedBy("mH")
    private void showInputMethodPickerLocked() {
        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromClient(mClient,
                SHOW_IM_PICKER_MODE_AUTO);
    }

    /**
     * A test API for CTS to make sure that {@link #showInputMethodPicker()} works as expected.
     *
     * <p>When customizing the implementation of {@link #showInputMethodPicker()} API, make sure
     * that this test API returns when and only while and only while
     * {@link #showInputMethodPicker()} is showing UI. Otherwise your OS implementation may not
     * pass CTS.</p>
     *
     * @return {@code true} while and only while {@link #showInputMethodPicker()} is showing UI.
     * @hide
     */
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public boolean isInputMethodPickerShown() {
        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();
    }

    /**
     * A test API for CTS to finish the tracking of any pending IME visibility requests. This
     * won't stop the actual requests, but allows resetting the state when starting up test runs.
     *
     * @hide
     */
    @SuppressLint("UnflaggedApi") // @TestApi without associated feature.
    @TestApi
    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
    public void finishTrackingPendingImeVisibilityRequests() {
        IInputMethodManagerGlobalInvoker.finishTrackingPendingImeVisibilityRequests();
    }

    /**
     * Show the settings for enabling subtypes of the specified input method.
     *
     * @param imiId An input method, whose subtypes settings will be shown. If imiId is null,
     * subtypes of all input methods will be shown.
     */
    public void showInputMethodAndSubtypeEnabler(@Nullable String imiId) {
        Context context = null;
        synchronized (mH) {
            if (mCurRootView != null) {
                context = mCurRootView.mContext;
            }
        }
        if (context == null) {
            final Context appContext = ActivityThread.currentApplication();
            final DisplayManager displayManager = appContext.getSystemService(DisplayManager.class);
            context = appContext.createDisplayContext(displayManager.getDisplay(mDisplayId));
        }

        final Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
        if (!TextUtils.isEmpty(imiId)) {
            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, imiId);
        }
        context.startActivity(intent);
    }

    /**
     * Returns the current input method subtype. This subtype is one of the subtypes in
     * the current input method. This method returns null when the current input method doesn't
     * have any input method subtype.
     */
    @Nullable
    public InputMethodSubtype getCurrentInputMethodSubtype() {
        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodSubtype(UserHandle.myUserId());
    }

    /**
     * Switch to a new input method subtype of the current input method.
     * @param subtype A new input method subtype to switch.
     * @return true if the current subtype was successfully switched. When the specified subtype is
     * null, this method returns false.
     * @deprecated If the calling process is an IME, use
     *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
     *             does not require any permission as long as the caller is the current IME.
     *             If the calling process is some privileged app that already has
     *             {@link Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
     *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
     */
    @Deprecated
    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
        if (Process.myUid() == Process.SYSTEM_UID) {
            Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
                    + "almost always it is a bug under multi-user / multi-profile environment. "
                    + "Consider directly interacting with InputMethodManagerService "
                    + "via LocalServices.");
            return false;
        }
        if (subtype == null) {
            // See the JavaDoc. This is how this method has worked.
            return false;
        }
        final Context fallbackContext = ActivityThread.currentApplication();
        if (fallbackContext == null) {
            return false;
        }
        if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                != PackageManager.PERMISSION_GRANTED) {
            return false;
        }
        final ContentResolver contentResolver = fallbackContext.getContentResolver();
        final String imeId = Settings.Secure.getString(contentResolver,
                Settings.Secure.DEFAULT_INPUT_METHOD);
        if (ComponentName.unflattenFromString(imeId) == null) {
            // Null or invalid IME ID format.
            return false;
        }
        final List<InputMethodSubtype> enabledSubtypes =
                IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(imeId, true,
                        UserHandle.myUserId());
        final int numSubtypes = enabledSubtypes.size();
        for (int i = 0; i < numSubtypes; ++i) {
            final InputMethodSubtype enabledSubtype = enabledSubtypes.get(i);
            if (enabledSubtype.equals(subtype)) {
                Settings.Secure.putInt(contentResolver,
                        Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, enabledSubtype.hashCode());
                return true;
            }
        }
        return false;
    }

    /**
     * Notify that a user took some action with this input method.
     *
     * @deprecated Just kept to avoid possible app compat issue.
     * @hide
     */
    @Deprecated
    @UnsupportedAppUsage(trackingBug = 114740982, maxTargetSdk = Build.VERSION_CODES.P)
    public void notifyUserAction() {
        Log.w(TAG, "notifyUserAction() is a hidden method, which is now just a stub method"
                + " that does nothing.  Leave comments in b.android.com/114740982 if your "
                + " application still depends on the previous behavior of this method.");
    }

    /**
     * Returns a map of all shortcut input method info and their subtypes.
     */
    public Map<InputMethodInfo, List<InputMethodSubtype>> getShortcutInputMethodsAndSubtypes() {
        final List<InputMethodInfo> enabledImes = getEnabledInputMethodList();

        // Ensure we check system IMEs first.
        enabledImes.sort(Comparator.comparingInt(imi -> imi.isSystem() ? 0 : 1));

        final int numEnabledImes = enabledImes.size();
        for (int imiIndex = 0; imiIndex < numEnabledImes; ++imiIndex) {
            final InputMethodInfo imi = enabledImes.get(imiIndex);
            final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeList(
                    imi, true);
            final int subtypeCount = subtypes.size();
            for (int subtypeIndex = 0; subtypeIndex < subtypeCount; ++subtypeIndex) {
                final InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex);
                if (SUBTYPE_MODE_VOICE.equals(subtype.getMode())) {
                    return Collections.singletonMap(imi, Collections.singletonList(subtype));
                }
            }
        }
        return Collections.emptyMap();
    }

    /**
     * This is kept due to {@link android.compat.annotation.UnsupportedAppUsage}.
     *
     * <p>TODO(Bug 113914148): Check if we can remove this.  We have accidentally exposed
     * WindowManagerInternal#getInputMethodWindowVisibleHeight to app developers and some of them
     * started relying on it.</p>
     *
     * @return Something that is not well-defined.
     * @hide
     */
    @UnsupportedAppUsage(trackingBug = 204906124, maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
            publicAlternatives = "Use {@link android.view.WindowInsets} instead")
    public int getInputMethodWindowVisibleHeight() {
        return IInputMethodManagerGlobalInvoker.getInputMethodWindowVisibleHeight(mClient);
    }

    /**
     * {@code true} means that
     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
     * {@code false} when the IME client and the IME run in different displays.
     */
    final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true);

    /**
     * Controls the display ID mismatch validation in
     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}.
     *
     * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work
     * correctly when the IME client and the IME run in different displays.  This is why
     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
     * {@code false} by default when the display ID does not match. This method allows special apps
     * to override this behavior when they are sure that it should work.</p>
     *
     * <p>By default the validation is enabled.</p>
     *
     * @param enabled {@code false} to disable the display ID validation.
     * @hide
     */
    public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) {
        mRequestCursorUpdateDisplayIdCheck.set(enabled);
    }

    /**
     * Force switch to the last used input method and subtype. If the last input method didn't have
     * any subtypes, the framework will simply switch to the last input method with no subtype
     * specified.
     * @param imeToken Supplies the identifying token given to an input method when it was started,
     * which allows it to perform this operation on itself.
     * @return true if the current input method and subtype was successfully switched to the last
     * used input method and subtype.
     * @deprecated Use {@link InputMethodService#switchToPreviousInputMethod()} instead. This method
     * was intended for IME developers who should be accessing APIs through the service. APIs in
     * this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public boolean switchToLastInputMethod(IBinder imeToken) {
        return InputMethodPrivilegedOperationsRegistry.get(imeToken).switchToPreviousInputMethod();
    }

    /**
     * Force switch to the next input method and subtype. If there is no IME enabled except
     * current IME and subtype, do nothing.
     * @param imeToken Supplies the identifying token given to an input method when it was started,
     * which allows it to perform this operation on itself.
     * @param onlyCurrentIme if true, the framework will find the next subtype which
     * belongs to the current IME
     * @return true if the current input method and subtype was successfully switched to the next
     * input method and subtype.
     * @deprecated Use {@link InputMethodService#switchToNextInputMethod(boolean)} instead. This
     * method was intended for IME developers who should be accessing APIs through the service.
     * APIs in this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public boolean switchToNextInputMethod(IBinder imeToken, boolean onlyCurrentIme) {
        return InputMethodPrivilegedOperationsRegistry.get(imeToken)
                .switchToNextInputMethod(onlyCurrentIme);
    }

    /**
     * Returns true if the current IME needs to offer the users ways to switch to a next input
     * method (e.g. a globe key.).
     * When an IME sets supportsSwitchingToNextInputMethod and this method returns true,
     * the IME has to offer ways to to invoke {@link #switchToNextInputMethod} accordingly.
     * <p> Note that the system determines the most appropriate next input method
     * and subtype in order to provide the consistent user experience in switching
     * between IMEs and subtypes.
     * @param imeToken Supplies the identifying token given to an input method when it was started,
     * which allows it to perform this operation on itself.
     * @deprecated Use {@link InputMethodService#shouldOfferSwitchingToNextInputMethod()}
     * instead. This method was intended for IME developers who should be accessing APIs through
     * the service. APIs in this class are intended for app developers interacting with the IME.
     */
    @Deprecated
    public boolean shouldOfferSwitchingToNextInputMethod(IBinder imeToken) {
        return InputMethodPrivilegedOperationsRegistry.get(imeToken)
                .shouldOfferSwitchingToNextInputMethod();
    }

    /**
     * Set additional input method subtypes. Only a process which shares the same uid with the IME
     * can add additional input method subtypes to the IME.
     * Please note that a subtype's status is stored in the system.
     * For example, enabled subtypes are remembered by the framework even after they are removed
     * by using this method. If you re-add the same subtypes again,
     * they will just get enabled. If you want to avoid such conflicts, for instance, you may
     * want to create a "different" new subtype even with the same locale and mode,
     * by changing its extra value. The different subtype won't get affected by the stored past
     * status. (You may want to take a look at {@link InputMethodSubtype#hashCode()} to refer
     * to the current implementation.)
     *
     * <p>NOTE: If the same subtype exists in both the manifest XML file and additional subtypes
     * specified by {@code subtypes}, those multiple instances are automatically merged into one
     * instance.</p>
     *
     * <p>CAVEAT: In API Level 23 and prior, the system may do nothing if an empty
     * {@link InputMethodSubtype} is specified in {@code subtypes}, which prevents you from removing
     * the last one entry of additional subtypes. If your IME statically defines one or more
     * subtypes in the manifest XML file, you may be able to work around this limitation by
     * specifying one of those statically defined subtypes in {@code subtypes}.</p>
     *
     * @param imiId Id of InputMethodInfo which additional input method subtypes will be added to.
     * If the imiId is {@code null}, system would do nothing for this operation.
     * @param subtypes subtypes will be added as additional subtypes of the current input method.
     * If the subtypes is {@code null}, system would do nothing for this operation.
     * @deprecated For IMEs that have already implemented features like customizable/downloadable
     *             keyboard layouts/languages, please start migration to other approaches. One idea
     *             would be exposing only one unified {@link InputMethodSubtype} then implement
     *             IME's own language switching mechanism within that unified subtype. The support
     *             of "Additional Subtype" may be completely dropped in a future version of Android.
     */
    @Deprecated
    public void setAdditionalInputMethodSubtypes(@NonNull String imiId,
            @NonNull InputMethodSubtype[] subtypes) {
        IInputMethodManagerGlobalInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes,
                UserHandle.myUserId());
    }

    /**
     * Updates the list of explicitly enabled {@link InputMethodSubtype} for a given IME owned by
     * the calling process.
     *
     * <p>By default each IME has no explicitly enabled {@link InputMethodSubtype}.  In this state
     * the system will decide what {@link InputMethodSubtype} should be enabled by using information
     * available at runtime as per-user language settings.  Users can, however, manually pick up one
     * or more {@link InputMethodSubtype} to be enabled on an Activity shown by
     * {@link #showInputMethodAndSubtypeEnabler(String)}. Such a manual change is stored in
     * {@link Settings.Secure#ENABLED_INPUT_METHODS} so that the change can persist across reboots.
     * {@link Settings.Secure#ENABLED_INPUT_METHODS} stores {@link InputMethodSubtype#hashCode()} as
     * the identifier of {@link InputMethodSubtype} for historical reasons.</p>
     *
     * <p>This API provides a safe and managed way for IME developers to modify what
     * {@link InputMethodSubtype} are referenced in {@link Settings.Secure#ENABLED_INPUT_METHODS}
     * for their own IME.  One use case is when IME developers want to use their own Activity for
     * users to pick up {@link InputMethodSubtype}. Another use case is for IME developers to fix up
     * any stale and/or invalid value stored in {@link Settings.Secure#ENABLED_INPUT_METHODS}
     * without bothering users. Passing an empty {@code subtypeHashCodes} is guaranteed to reset
     * the state to default.</p>
     *
     * <h3>To control the return value of {@link InputMethodSubtype#hashCode()}</h3>
     * <p>{@link android.R.attr#subtypeId} and {@link
     * android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder#setSubtypeId(int)} are
     * available for IME developers to control the return value of
     * {@link InputMethodSubtype#hashCode()}. Beware that {@code -1} is not a valid value of
     * {@link InputMethodSubtype#hashCode()} for historical reasons.</p>
     *
     * <h3>Note for Direct Boot support</h3>
     * <p>While IME developers can call this API even before
     * {@link android.os.UserManager#isUserUnlocked()} becomes {@code true}, such a change is
     * volatile thus remains effective only until {@link android.os.UserManager#isUserUnlocked()}
     * becomes {@code true} or the device is rebooted. To make the change persistent IME developers
     * need to call this API again after receiving {@link Intent#ACTION_USER_UNLOCKED}.</p>
     *
     * @param imiId IME ID. The specified IME and the calling process need to belong to the same
     *              package.  Otherwise {@link SecurityException} will be thrown.
     * @param subtypeHashCodes An arrays of {@link InputMethodSubtype#hashCode()} to be explicitly
     *                         enabled. Entries that are found in the specified IME will be silently
     *                         ignored. Pass an empty array to reset the state to default.
     * @throws NullPointerException if {@code subtypeHashCodes} is {@code null}.
     * @throws SecurityException if the specified IME and the calling process do not belong to the
     *                           same package.
     */
    public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imiId,
            @NonNull int[] subtypeHashCodes) {
        IInputMethodManagerGlobalInvoker.setExplicitlyEnabledInputMethodSubtypes(imiId,
                subtypeHashCodes, UserHandle.myUserId());
    }

    /**
     * Returns the last used {@link InputMethodSubtype} in system history.
     *
     * @return the last {@link InputMethodSubtype}, {@code null} if last IME have no subtype.
     */
    @Nullable
    public InputMethodSubtype getLastInputMethodSubtype() {
        return IInputMethodManagerGlobalInvoker.getLastInputMethodSubtype(UserHandle.myUserId());
    }

    /**
     * <p>This is used for CTS test only. Do not use this method outside of CTS package.<p/>
     * @return the ID of this display which this {@link InputMethodManager} resides
     * @hide
     */
    @TestApi
    public int getDisplayId() {
        return mDisplayId;
    }

    private void doDump(FileDescriptor fd, PrintWriter fout, String[] args) {
        if (processDump(fd, args)) {
            return;
        }

        final Printer p = new PrintWriterPrinter(fout);
        p.println("Input method client state for " + this + ":");
        p.println("  mFallbackInputConnection=" + mFallbackInputConnection);
        p.println("  mActive=" + mActive
                + " mRestartOnNextWindowFocus=" + mRestartOnNextWindowFocus
                + " mBindSequence=" + getBindSequenceLocked()
                + " mCurImeId=" + getImeIdLocked());
        p.println("  mFullscreenMode=" + mFullscreenMode);
        if (isImeSessionAvailableLocked()) {
            p.println("  mCurMethod=" + mCurBindState.mImeSession);
        } else {
            p.println("  mCurMethod= null");
        }
        for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) {
            p.println("  mAccessibilityInputMethodSession("
                    + mAccessibilityInputMethodSession.keyAt(i) + ")="
                    + mAccessibilityInputMethodSession.valueAt(i));
        }
        p.println("  mCurRootView=" + mCurRootView);
        p.println("  mServedView=" + getServedViewLocked());
        p.println("  mNextServedView=" + getNextServedViewLocked());
        p.println("  mServedConnecting=" + mServedConnecting);
        if (mCurrentEditorInfo != null) {
            p.println("  mCurrentEditorInfo:");
            mCurrentEditorInfo.dump(p, "    ", false /* dumpExtras */);
        } else {
            p.println("  mCurrentEditorInfo: null");
        }
        p.println("  mServedInputConnection=" + mServedInputConnection);
        p.println("  mServedInputConnectionHandler=" + mServedInputConnectionHandler);
        p.println("  mCompletions=" + Arrays.toString(mCompletions));
        p.println("  mCursorRect=" + mCursorRect);
        p.println("  mCursorSelStart=" + mCursorSelStart
                + " mCursorSelEnd=" + mCursorSelEnd
                + " mCursorCandStart=" + mCursorCandStart
                + " mCursorCandEnd=" + mCursorCandEnd);
    }

    /**
     * Callback that is invoked when an input event that was dispatched to
     * the IME has been finished.
     * @hide
     */
    public interface FinishedInputEventCallback {
        public void onFinishedInputEvent(Object token, boolean handled);
    }

    private static class ConnectionlessHandwritingCallbackProxy
            extends IConnectionlessHandwritingCallback.Stub {
        private final Object mLock = new Object();

        @Nullable
        @GuardedBy("mLock")
        private Executor mExecutor;

        @Nullable
        @GuardedBy("mLock")
        private ConnectionlessHandwritingCallback mCallback;

        ConnectionlessHandwritingCallbackProxy(
                @NonNull Executor executor, @NonNull ConnectionlessHandwritingCallback callback) {
            mExecutor = executor;
            mCallback = callback;
        }

        @Override
        public void onResult(CharSequence text) {
            Executor executor;
            ConnectionlessHandwritingCallback callback;
            synchronized (mLock) {
                if (mExecutor == null || mCallback == null) {
                    return;
                }
                executor = mExecutor;
                callback = mCallback;
                mExecutor = null;
                mCallback = null;
            }
            final long identity = Binder.clearCallingIdentity();
            try {
                if (TextUtils.isEmpty(text)) {
                    executor.execute(() -> callback.onError(
                            ConnectionlessHandwritingCallback
                                    .CONNECTIONLESS_HANDWRITING_ERROR_NO_TEXT_RECOGNIZED));
                } else {
                    executor.execute(() -> callback.onResult(text));
                }
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }

        @Override
        public void onError(int errorCode) {
            Executor executor;
            ConnectionlessHandwritingCallback callback;
            synchronized (mLock) {
                if (mExecutor == null || mCallback == null) {
                    return;
                }
                executor = mExecutor;
                callback = mCallback;
                mExecutor = null;
                mCallback = null;
            }
            final long identity = Binder.clearCallingIdentity();
            try {
                executor.execute(() -> callback.onError(errorCode));
            } finally {
                Binder.restoreCallingIdentity(identity);
            }
        }
    }

    private final class ImeInputEventSender extends InputEventSender {
        public ImeInputEventSender(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEventFinished(int seq, boolean handled) {
            finishedInputEvent(seq, handled, false);
        }
    }

    private final class PendingEvent implements Runnable {
        public InputEvent mEvent;
        public Object mToken;
        public String mInputMethodId;
        public FinishedInputEventCallback mCallback;
        public Handler mHandler;
        public boolean mHandled;

        public void recycle() {
            mEvent = null;
            mToken = null;
            mInputMethodId = null;
            mCallback = null;
            mHandler = null;
            mHandled = false;
        }

        @Override
        public void run() {
            mCallback.onFinishedInputEvent(mToken, mHandled);

            synchronized (mH) {
                recyclePendingEventLocked(this);
            }
        }
    }

    private static final class BindState {
        /**
         * Encapsulates IPCs to the currently connected InputMethodService.
         */
        @Nullable
        final IInputMethodSessionInvoker mImeSession;

        /**
         * As reported by {@link InputBindResult}. This value is determined by
         * {@link com.android.internal.R.styleable#InputMethod_suppressesSpellChecker}.
         */
        final boolean mIsInputMethodSuppressingSpellChecker;

        /**
         * As reported by {@link InputBindResult}. This value indicates the bound input method ID.
         */
        @Nullable
        final String mImeId;

        /**
         * Sequence number of this binding, as returned by the server.
         */
        final int mBindSequence;

        BindState(@NonNull InputBindResult inputBindResult) {
            mImeSession = IInputMethodSessionInvoker.createOrNull(inputBindResult.method);
            mIsInputMethodSuppressingSpellChecker =
                    inputBindResult.isInputMethodSuppressingSpellChecker;
            mImeId = inputBindResult.id;
            mBindSequence = inputBindResult.sequence;
        }
    }

    @GuardedBy("mH")
    private boolean isImeSessionAvailableLocked() {
        return mCurBindState != null && mCurBindState.mImeSession != null;
    }

    @GuardedBy("mH")
    private String getImeIdLocked() {
        return mCurBindState != null ? mCurBindState.mImeId : null;
    }

    @GuardedBy("mH")
    private int getBindSequenceLocked() {
        return mCurBindState != null ? mCurBindState.mBindSequence : -1;
    }

    /**
     * Checks the args to see if a proto-based ime dump was requested and writes the client side
     * ime dump to the given {@link FileDescriptor}.
     *
     * @return {@code true} if a proto-based ime dump was requested.
     */
    private boolean processDump(final FileDescriptor fd, final String[] args) {
        if (args == null) {
            return false;
        }

        for (String arg : args) {
            if (arg.equals(ImeTracing.PROTO_ARG)) {
                final ProtoOutputStream proto = new ProtoOutputStream(fd);
                dumpDebug(proto, null /* icProto */);
                proto.flush();
                return true;
            }
        }
        return false;
    }

    /**
     * Write the proto dump of various client side components to the provided
     * {@link ProtoOutputStream}.
     *
     * @param proto The proto stream to which the dumps are written.
     * @param icProto {@link InputConnection} call data in proto format.
     * @hide
     */
    public void dumpDebug(ProtoOutputStream proto, @Nullable byte[] icProto) {
        synchronized (mH) {
            if (!isImeSessionAvailableLocked()) {
                return;
            }

            proto.write(DISPLAY_ID, mDisplayId);
            final long token = proto.start(INPUT_METHOD_MANAGER);
            proto.write(CUR_ID, mCurBindState.mImeId);
            proto.write(FULLSCREEN_MODE, mFullscreenMode);
            proto.write(ACTIVE, mActive);
            proto.write(SERVED_CONNECTING, mServedConnecting);
            proto.write(SERVED_VIEW, Objects.toString(mServedView));
            proto.write(NEXT_SERVED_VIEW, Objects.toString(mNextServedView));
            proto.end(token);
            if (mCurRootView != null) {
                mCurRootView.dumpDebug(proto, VIEW_ROOT_IMPL);
            }
            if (mCurrentEditorInfo != null) {
                mCurrentEditorInfo.dumpDebug(proto, EDITOR_INFO);
            }
            if (mImeInsetsConsumer != null) {
                mImeInsetsConsumer.dumpDebug(proto, IME_INSETS_SOURCE_CONSUMER);
            }
            if (mServedInputConnection != null) {
                mServedInputConnection.dumpDebug(proto, INPUT_CONNECTION);
            }
            if (icProto != null) {
                proto.write(INPUT_CONNECTION_CALL, icProto);
            }
        }
    }

    @GuardedBy("mH")
    private void forAccessibilitySessionsLocked(
            Consumer<IAccessibilityInputMethodSessionInvoker> consumer) {
        for (int i = 0; i < mAccessibilityInputMethodSession.size(); i++) {
            consumer.accept(mAccessibilityInputMethodSession.valueAt(i));
        }
    }

    @UiThread
    private static Pair<InputConnection, EditorInfo> createInputConnection(
            @NonNull View servedView) {
        final EditorInfo editorInfo = new EditorInfo();
        // Note: Use Context#getOpPackageName() rather than Context#getPackageName() so that the
        // system can verify the consistency between the uid of this process and package name passed
        // from here. See comment of Context#getOpPackageName() for details.
        editorInfo.packageName = servedView.getContext().getOpPackageName();
        editorInfo.autofillId = servedView.getAutofillId();
        editorInfo.fieldId = servedView.getId();
        final InputConnection ic = servedView.onCreateInputConnection(editorInfo);
        if (DEBUG) Log.v(TAG, "Starting input: editorInfo=" + editorInfo + " ic=" + ic);

        // Clear autofill and field ids if a connection could not be established.
        // This ensures that even disconnected EditorInfos have well-defined attributes,
        // making them consistently and straightforwardly comparable.
        if (ic == null) {
            editorInfo.autofillId = AutofillId.NO_AUTOFILL_ID;
            editorInfo.fieldId = 0;
        }
        return new Pair<>(ic, editorInfo);
    }
}
