Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6db3bfe3 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Track event flow to IMMS#setImeWindowStatus

This is part of work to introduce historical debugging infrastructure
for Android IME.

In this CL, we will focus on the following two event flows.

 A1. IMMS#attachNewInputLocked() queues MSG_(RE)START_INPUT to deliver
     new InputConnection/EditorInfo to the current IME
 A2. The IME triggers IMS#onStartInput()/IMS#onRestartInput() and
     updates the following fields:
      - InputMethodService#mStartedInputConnection
      - InputMethodService#mInputEditorInfo

 B1. IME is expected to call back IMM#setImeWindowStatus() to notify
     its window visibility change to IMMS.
 B2. IMMS updates the following field if the caller is still the
     current IME.
      - InputMethodManagerService#mImeWindowVis

What this CL aims to do is to enable IMMS to access A1 state when it
was in B2 state, by considering that for given a B1 the last A2
happened before B1 is the cause of B1 and B2.

To do this, IMMS issues a binder token in A1 and each IME keeps it
so that it can be passed in B1. By using this Binder token as a key,
IMMS can keep tracking state snapshot taken from each A1. Note that
those state snapshots keep alive until the Binder token's proxy in the
IME process loses strong reference from its GC root.

Test: Make sure `adb shell dumpsys input_method | grep mImeWindowVis`
      matches to the IME window visibility.
Test: Make sure the current IME is not receiving any
      InvalidParameterException from IMMS.
Bug: 35079353
Change-Id: I9921b381e02106dbffff5e0b3d13f0a1245ce807
parent f7526b58
Loading
Loading
Loading
Loading
+8 −5
Original line number Diff line number Diff line
@@ -166,8 +166,9 @@ class IInputMethodWrapper extends IInputMethod.Stub
                final SomeArgs args = (SomeArgs) msg.obj;
                final int missingMethods = msg.arg1;
                final boolean restarting = msg.arg2 != 0;
                final IInputContext inputContext = (IInputContext) args.arg1;
                final EditorInfo info = (EditorInfo) args.arg2;
                final IBinder startInputToken = (IBinder) args.arg1;
                final IInputContext inputContext = (IInputContext) args.arg2;
                final EditorInfo info = (EditorInfo) args.arg3;
                final InputConnection ic = inputContext != null
                        ? new InputConnectionWrapper(mTarget, inputContext, missingMethods) : null;
                info.makeCompatible(mTargetSdkVersion);
@@ -176,6 +177,8 @@ class IInputMethodWrapper extends IInputMethod.Stub
                } else {
                    inputMethod.startInput(ic, info);
                }
                inputMethod.dispatchStartInputWithToken(ic, info, true /* initial */,
                        startInputToken);
                args.recycle();
                return;
            }
@@ -255,11 +258,11 @@ class IInputMethodWrapper extends IInputMethod.Stub
    }

    @Override
    public void startInput(IInputContext inputContext,
    public void startInput(IBinder startInputToken, IInputContext inputContext,
            @InputConnectionInspector.MissingMethodFlags final int missingMethods,
            EditorInfo attribute, boolean restarting) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOO(DO_START_INPUT,
                missingMethods, restarting ? 1 : 0, inputContext, attribute));
        mCaller.executeOrSendMessage(mCaller.obtainMessageIIOOO(DO_START_INPUT,
                missingMethods, restarting ? 1 : 0, startInputToken, inputContext, attribute));
    }

    @Override
+41 −8
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import android.annotation.DrawableRes;
import android.annotation.IntDef;
import android.annotation.MainThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.ActivityManager;
import android.app.Dialog;
import android.content.Context;
@@ -292,6 +293,19 @@ public class InputMethodService extends AbstractInputMethodService {
    InputConnection mStartedInputConnection;
    EditorInfo mInputEditorInfo;

    /**
     * A token to keep tracking the last IPC that triggered
     * {@link #doStartInput(InputConnection, EditorInfo, boolean)}. If
     * {@link #doStartInput(InputConnection, EditorInfo, boolean)} was not caused by IPCs from
     * {@link com.android.server.InputMethodManagerService}, this needs to remain unchanged.
     *
     * <p>Some IPCs to {@link com.android.server.InputMethodManagerService} require this token to
     * disentangle event flows for various purposes such as better window animation and providing
     * fine-grained debugging information.</p>
     */
    @Nullable
    private IBinder mStartInputToken;

    int mShowInputFlags;
    boolean mShowInputRequested;
    boolean mLastShowInputRequested;
@@ -415,6 +429,23 @@ public class InputMethodService extends AbstractInputMethodService {
            doStartInput(ic, attribute, true);
        }

        /**
         * {@inheritDoc}
         * @hide
         */
        @Override
        public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                @NonNull EditorInfo editorInfo, boolean restarting,
                @NonNull IBinder startInputToken) {
            mStartInputToken = startInputToken;

            // This needs to be dispatched to interface methods rather than doStartInput().
            // Otherwise IME developers who have overridden those interface methods will lose
            // notifications.
            super.dispatchStartInputWithToken(inputConnection, editorInfo, restarting,
                    startInputToken);
        }

        /**
         * Handle a request by the system to hide the soft input area.
         */
@@ -454,8 +485,8 @@ public class InputMethodService extends AbstractInputMethodService {
            clearInsetOfPreviousIme();
            // If user uses hard keyboard, IME button should always be shown.
            boolean showing = isInputViewShown();
            mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0),
                    mBackDisposition);
            mImm.setImeWindowStatus(mToken, mStartInputToken,
                    IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
            if (resultReceiver != null) {
                resultReceiver.send(wasVis != isInputViewShown()
                        ? InputMethodManager.RESULT_SHOWN
@@ -926,8 +957,8 @@ public class InputMethodService extends AbstractInputMethodService {
            }
            // If user uses hard keyboard, IME button should always be shown.
            boolean showing = onEvaluateInputViewShown();
            mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0),
                    mBackDisposition);
            mImm.setImeWindowStatus(mToken, mStartInputToken,
                    IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
        }
    }

@@ -1653,7 +1684,8 @@ public class InputMethodService extends AbstractInputMethodService {

        final int nextImeWindowStatus = IME_ACTIVE | (isInputViewShown() ? IME_VISIBLE : 0);
        if (previousImeWindowStatus != nextImeWindowStatus) {
            mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition);
            mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus,
                    mBackDisposition);
        }
        if ((previousImeWindowStatus & IME_ACTIVE) == 0) {
            if (DEBUG) Log.v(TAG, "showWindow: showing!");
@@ -1678,7 +1710,7 @@ public class InputMethodService extends AbstractInputMethodService {
    }

    private void doHideWindow() {
        mImm.setImeWindowStatus(mToken, 0, mBackDisposition);
        mImm.setImeWindowStatus(mToken, mStartInputToken, 0, mBackDisposition);
        hideWindow();
    }

@@ -2643,6 +2675,7 @@ public class InputMethodService extends AbstractInputMethodService {
        p.println("  mInputStarted=" + mInputStarted
                + " mInputViewStarted=" + mInputViewStarted
                + " mCandidatesViewStarted=" + mCandidatesViewStarted);
        p.println("  mStartInputToken=" + mStartInputToken);

        if (mInputEditorInfo != null) {
            p.println("  mInputEditorInfo:");
+40 −0
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package android.view.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.inputmethodservice.InputMethodService;
@@ -147,6 +149,44 @@ public interface InputMethod {
     */
    public void restartInput(InputConnection inputConnection, EditorInfo attribute);

    /**
     * This method is called when {@code {@link #startInput(InputConnection, EditorInfo)} or
     * {@code {@link #restartInput(InputConnection, EditorInfo)} needs to be dispatched.
     *
     * <p>Note: This method is hidden because the {@code startInputToken} that this method is
     * dealing with is one of internal details, which should not be exposed to the IME developers.
     * If you override this method, you are responsible for not breaking existing IMEs that expect
     * {@link #startInput(InputConnection, EditorInfo)} to be still called back.</p>
     *
     * @param inputConnection optional specific input connection for communicating with the text
     *                        box; if {@code null}, you should use the generic bound input
     *                        connection
     * @param editorInfo information about the text box (typically, an EditText) that requests input
     * @param restarting {@code false} if this corresponds to
     *                   {@link #startInput(InputConnection, EditorInfo)}. Otherwise this
     *                   corresponds to {@link #restartInput(InputConnection, EditorInfo)}.
     * @param startInputToken a token that identifies a logical session that starts with this method
     *                        call. Some internal IPCs such as {@link
     *                        InputMethodManager#setImeWindowStatus(IBinder, IBinder, int, int)}
     *                        require this token to work, and you have to keep the token alive until
     *                        the next {@link #startInput(InputConnection, EditorInfo, IBinder)} as
     *                        long as your implementation of {@link InputMethod} relies on such
     *                        IPCs
     * @see #startInput(InputConnection, EditorInfo)
     * @see #restartInput(InputConnection, EditorInfo)
     * @see EditorInfo
     * @hide
     */
    default void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
            @NonNull EditorInfo editorInfo, boolean restarting,
            @NonNull IBinder startInputToken) {
        if (restarting) {
            restartInput(inputConnection, editorInfo);
        } else {
            startInput(inputConnection, editorInfo);
        }
    }

    /**
     * Create a new {@link InputMethodSession} that can be handed to client
     * applications for interacting with the input method.  You can later
+3 −2
Original line number Diff line number Diff line
@@ -738,9 +738,10 @@ public final class InputMethodManager {
    }

    /** @hide */
    public void setImeWindowStatus(IBinder imeToken, int vis, int backDisposition) {
    public void setImeWindowStatus(IBinder imeToken, IBinder startInputToken, int vis,
            int backDisposition) {
        try {
            mService.setImeWindowStatus(imeToken, vis, backDisposition);
            mService.setImeWindowStatus(imeToken, startInputToken, vis, backDisposition);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
+10 −0
Original line number Diff line number Diff line
@@ -173,6 +173,16 @@ public class HandlerCaller {
        return mH.obtainMessage(what, arg1, arg2, args);
    }

    public Message obtainMessageIIOOOO(int what, int arg1, int arg2, Object arg3, Object arg4,
            Object arg5, Object arg6) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = arg3;
        args.arg2 = arg4;
        args.arg3 = arg5;
        args.arg4 = arg6;
        return mH.obtainMessage(what, arg1, arg2, args);
    }

    public Message obtainMessageOO(int what, Object arg1, Object arg2) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = arg1;
Loading