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

Commit c54c1171 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Add a new Binder interface to allow IMS to directly talk to IMMS

Historically, InputMethodService (IMS) has relied on
InputMethodManager's hidden methods to communicate with
InputMethodManagerService (IMMS).  Because of this, InputMethodManager
(IMM) has ended up being a mixture of IPC endpoint for both IME
clients and IME itself.

There are multiple problems.

 * IMM is instantiated in almost all user mode processes.  This means
   that unnecessary IPC endpoints have been accessible to them via
   reflection.  Even though those endpoints refuses request without a
   valid IME window token, and even though we have tighten up use of
   private APIs in the runtime level, exposing unnecessary IPC
   endpoints is still questionable.
 * Mixing multiple responsibilities has been caused unnecessary
   complexity in IMM.  In Bug 70282603, we have moved some APIs from
   IMM to IMS to sort out this complexity that are surfaced in API
   boundary, but in the implementation level everything remained to be
   the same.

Now that Bug 70282603 is fixed, the natural next step is to start
implementing actual an IPC connection from IMS to IMMS without relying
on IMM.

Here is the new diagram that describes (most of) IPC interfaces around
IMEs.

   APP---(1)---IMMS
     \          |
      \         |
       \        |
        \       |
         \      |
         (2)   (3)
           \    |
            \   |
             \  |
              \ |
               \|
               IME

 (1): IInputMethodManager.aidl: send requests from APP to IMMS
      IInputMethodClient.aidl:  send requests from IMMS to APP
 (2): IInputMethodSession.aidl: send requests from APP to IME
      IInputContext.aidl:       send requests from IME to APP
       -> this is the actual interface behind InputConnection
 (3): IInputMethod.aidl:        send requests from IMMS to IME
      IInputMethodPrivilegedOperations.aidl:
                                send requests from IME to IMMS

IInputMethodPrivilegedOperations.aidl is what this CL is adding.

With that, this CL moves 5 IPC methods
  from IInputMethodManager.aidl (1)
  to IInputMethodPrivilegedOperations.aidl (3).

There remain some IPC methods that are intended to be used only from
IMEs in IInputMethodManager.aidl because those methods have been
unfortunately exposed via public APIs in InputMethodmanager.

Although all of those public APIs were deprecated in Android P as part
of Bug 70282603, we still need to keep maintaining those APIs until
(most of) IMEs migrate to APIs that are newly introduced in
InputMethodService.  It would take several years.

IInputMethodManager#getInputMethodWindowVisibleHeight() is another
method that we cannot migrate right now because some apps have already
relied on its corresponding hidden method in IMM, as discussed in Bug
113914148.

Fix: 113177698
Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases
Change-Id: I2f3ec3c5de546fb3603275a4b64000ed3f863b65
parent cf7039dc
Loading
Loading
Loading
Loading
+1 −0
Original line number Original line Diff line number Diff line
@@ -381,6 +381,7 @@ java_defaults {
        "core/java/com/android/internal/backup/IBackupTransport.aidl",
        "core/java/com/android/internal/backup/IBackupTransport.aidl",
        "core/java/com/android/internal/backup/IObbBackupService.aidl",
        "core/java/com/android/internal/backup/IObbBackupService.aidl",
        "core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl",
        "core/java/com/android/internal/inputmethod/IInputContentUriToken.aidl",
        "core/java/com/android/internal/inputmethod/IInputMethodPrivilegedOperations.aidl",
        "core/java/com/android/internal/net/INetworkWatchlistManager.aidl",
        "core/java/com/android/internal/net/INetworkWatchlistManager.aidl",
        "core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl",
        "core/java/com/android/internal/policy/IKeyguardDrawnCallback.aidl",
        "core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl",
        "core/java/com/android/internal/policy/IKeyguardDismissCallback.aidl",
+14 −6
Original line number Original line Diff line number Diff line
@@ -35,6 +35,7 @@ import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSession;
import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.InputMethodSubtype;


import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.os.SomeArgs;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputContext;
@@ -60,7 +61,7 @@ class IInputMethodWrapper extends IInputMethod.Stub
    private static final String TAG = "InputMethodWrapper";
    private static final String TAG = "InputMethodWrapper";


    private static final int DO_DUMP = 1;
    private static final int DO_DUMP = 1;
    private static final int DO_ATTACH_TOKEN = 10;
    private static final int DO_INITIALIZE_INTERNAL = 10;
    private static final int DO_SET_INPUT_CONTEXT = 20;
    private static final int DO_SET_INPUT_CONTEXT = 20;
    private static final int DO_UNSET_INPUT_CONTEXT = 30;
    private static final int DO_UNSET_INPUT_CONTEXT = 30;
    private static final int DO_START_INPUT = 32;
    private static final int DO_START_INPUT = 32;
@@ -160,8 +161,14 @@ class IInputMethodWrapper extends IInputMethod.Stub
                return;
                return;
            }
            }


            case DO_ATTACH_TOKEN: {
            case DO_INITIALIZE_INTERNAL: {
                inputMethod.attachToken((IBinder)msg.obj);
                SomeArgs args = (SomeArgs) msg.obj;
                try {
                    inputMethod.initializeInternal((IBinder) args.arg1,
                            (IInputMethodPrivilegedOperations) args.arg2);
                } finally {
                    args.recycle();
                }
                return;
                return;
            }
            }
            case DO_SET_INPUT_CONTEXT: {
            case DO_SET_INPUT_CONTEXT: {
@@ -246,8 +253,9 @@ class IInputMethodWrapper extends IInputMethod.Stub


    @BinderThread
    @BinderThread
    @Override
    @Override
    public void attachToken(IBinder token) {
    public void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privOps) {
        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_ATTACH_TOKEN, token));
        mCaller.executeOrSendMessage(
                mCaller.obtainMessageOO(DO_INITIALIZE_INTERNAL, token, privOps));
    }
    }


    @BinderThread
    @BinderThread
+113 −18
Original line number Original line Diff line number Diff line
@@ -42,6 +42,7 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Bundle;
import android.os.Handler;
import android.os.Handler;
import android.os.IBinder;
import android.os.IBinder;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ResultReceiver;
import android.os.SystemClock;
import android.os.SystemClock;
import android.provider.Settings;
import android.provider.Settings;
@@ -79,6 +80,9 @@ import android.widget.ImageButton;
import android.widget.LinearLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import android.widget.TextView;


import com.android.internal.inputmethod.IInputContentUriToken;
import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;

import java.io.FileDescriptor;
import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
@@ -342,6 +346,7 @@ public class InputMethodService extends AbstractInputMethodService {
    private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;
    private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING;


    InputMethodManager mImm;
    InputMethodManager mImm;
    private IInputMethodPrivilegedOperations mPrivOps;


    @UnsupportedAppUsage
    @UnsupportedAppUsage
    int mTheme = 0;
    int mTheme = 0;
@@ -444,6 +449,22 @@ public class InputMethodService extends AbstractInputMethodService {
     * all of the standard behavior for an input method.
     * all of the standard behavior for an input method.
     */
     */
    public class InputMethodImpl extends AbstractInputMethodImpl {
    public class InputMethodImpl extends AbstractInputMethodImpl {
        /**
         * {@inheritDoc}
         * @hide
         */
        @MainThread
        @Override
        public final void initializeInternal(IBinder token,
                IInputMethodPrivilegedOperations privilegedOperations) {
            if (mToken != null) {
                throw new IllegalStateException("initializeInternal() must be called at most once."
                        + " privOps=" + privilegedOperations);
            }
            mPrivOps = privilegedOperations;
            attachToken(token);
        }

        /**
        /**
         * {@inheritDoc}
         * {@inheritDoc}
         */
         */
@@ -470,9 +491,7 @@ public class InputMethodService extends AbstractInputMethodService {
            mInputConnection = binding.getConnection();
            mInputConnection = binding.getConnection();
            if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
            if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding
                    + " ic=" + mInputConnection);
                    + " ic=" + mInputConnection);
            if (mImm != null && mToken != null) {
            reportFullscreenMode();
                mImm.reportFullscreenMode(mToken, mIsFullscreen);
            }
            initialize();
            initialize();
            onBindInput();
            onBindInput();
        }
        }
@@ -521,7 +540,11 @@ public class InputMethodService extends AbstractInputMethodService {
        public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
        public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection,
                @NonNull EditorInfo editorInfo, boolean restarting,
                @NonNull EditorInfo editorInfo, boolean restarting,
                @NonNull IBinder startInputToken) {
                @NonNull IBinder startInputToken) {
            mImm.reportStartInput(mToken, startInputToken);
            try {
                mPrivOps.reportStartInput(startInputToken);
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }


            // This needs to be dispatched to interface methods rather than doStartInput().
            // This needs to be dispatched to interface methods rather than doStartInput().
            // Otherwise IME developers who have overridden those interface methods will lose
            // Otherwise IME developers who have overridden those interface methods will lose
@@ -563,8 +586,8 @@ public class InputMethodService extends AbstractInputMethodService {
            }
            }
            clearInsetOfPreviousIme();
            clearInsetOfPreviousIme();
            // If user uses hard keyboard, IME button should always be shown.
            // If user uses hard keyboard, IME button should always be shown.
            mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()),
            setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
                    mBackDisposition);

            if (resultReceiver != null) {
            if (resultReceiver != null) {
                resultReceiver.send(wasVis != isInputViewShown()
                resultReceiver.send(wasVis != isInputViewShown()
                        ? InputMethodManager.RESULT_SHOWN
                        ? InputMethodManager.RESULT_SHOWN
@@ -583,6 +606,17 @@ public class InputMethodService extends AbstractInputMethodService {
        }
        }
    }
    }


    private void setImeWindowStatus(int visibilityFlags, int backDisposition) {
        if (mPrivOps == null) {
            return;
        }
        try {
            mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
    /**
     * Concrete implementation of
     * Concrete implementation of
     * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
     * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
@@ -1039,8 +1073,7 @@ public class InputMethodService extends AbstractInputMethodService {
            }
            }
            // If user uses hard keyboard, IME button should always be shown.
            // If user uses hard keyboard, IME button should always be shown.
            boolean showing = onEvaluateInputViewShown();
            boolean showing = onEvaluateInputViewShown();
            mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0),
            setImeWindowStatus(IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition);
                    mBackDisposition);
        }
        }
    }
    }


@@ -1090,7 +1123,7 @@ public class InputMethodService extends AbstractInputMethodService {
            return;
            return;
        }
        }
        mBackDisposition = disposition;
        mBackDisposition = disposition;
        mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
        setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition);
    }
    }


    /**
    /**
@@ -1189,6 +1222,17 @@ public class InputMethodService extends AbstractInputMethodService {
        return mInputEditorInfo;
        return mInputEditorInfo;
    }
    }


    private void reportFullscreenMode() {
        if (mPrivOps == null) {
            return;
        }
        try {
            mPrivOps.reportFullscreenMode(mIsFullscreen);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
    /**
     * Re-evaluate whether the input method should be running in fullscreen
     * Re-evaluate whether the input method should be running in fullscreen
     * mode, and update its UI if this has changed since the last time it
     * mode, and update its UI if this has changed since the last time it
@@ -1203,9 +1247,7 @@ public class InputMethodService extends AbstractInputMethodService {
        if (mIsFullscreen != isFullscreen || !mFullscreenApplied) {
        if (mIsFullscreen != isFullscreen || !mFullscreenApplied) {
            changed = true;
            changed = true;
            mIsFullscreen = isFullscreen;
            mIsFullscreen = isFullscreen;
            if (mImm != null && mToken != null) {
            reportFullscreenMode();
                mImm.reportFullscreenMode(mToken, mIsFullscreen);
            }
            mFullscreenApplied = true;
            mFullscreenApplied = true;
            initialize();
            initialize();
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
            LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams)
@@ -1823,7 +1865,7 @@ public class InputMethodService extends AbstractInputMethodService {


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


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


@@ -1889,10 +1931,30 @@ public class InputMethodService extends AbstractInputMethodService {
                + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme);
                + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme);
        if (!mShouldClearInsetOfPreviousIme) return;
        if (!mShouldClearInsetOfPreviousIme) return;


        mImm.clearLastInputMethodWindowForTransition(mToken);
        clearLastInputMethodWindowForTransition();
        mShouldClearInsetOfPreviousIme = false;
        mShouldClearInsetOfPreviousIme = false;
    }
    }


    /**
     * Tells the system that the IME decided to not show a window and the system no longer needs to
     * use the previous IME's inset.
     *
     * <p>Caveat: {@link android.inputmethodservice.InputMethodService#clearInsetOfPreviousIme()}
     * is the only expected caller of this method.  Do not depend on this anywhere else.</p>
     *
     * <p>TODO: We probably need to reconsider how IME should be handled.</p>
     */
    private void clearLastInputMethodWindowForTransition() {
        if (mPrivOps == null) {
            return;
        }
        try {
            mPrivOps.clearLastInputMethodWindowForTransition();
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
        }
    }

    /**
    /**
     * Called when a new client has bound to the input method.  This
     * Called when a new client has bound to the input method.  This
     * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)}
     * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)}
@@ -2806,7 +2868,40 @@ public class InputMethodService extends AbstractInputMethodService {
        if (getCurrentInputConnection() != inputConnection) {
        if (getCurrentInputConnection() != inputConnection) {
            return;
            return;
        }
        }
        mImm.exposeContent(mToken, inputContentInfo, getCurrentInputEditorInfo());
        exposeContentInternal(inputContentInfo, getCurrentInputEditorInfo());
    }

    /**
     * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access
     * permission to the content.
     *
     * <p>See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo,
     * InputConnection)} for details.</p>
     *
     * @param inputContentInfo Content to be temporarily exposed from the input method to the
     * application.
     * This cannot be {@code null}.
     * @param editorInfo The editor that receives {@link InputContentInfo}.
     */
    private void exposeContentInternal(@NonNull InputContentInfo inputContentInfo,
            @NonNull EditorInfo editorInfo) {
        if (mPrivOps == null) {
            return;
        }
        final IInputContentUriToken uriToken;
        final Uri contentUri = inputContentInfo.getContentUri();
        try {
            uriToken = mPrivOps.createInputContentUriToken(contentUri, editorInfo.packageName);
            if (uriToken == null) {
                return;
            }
        } catch (RemoteException e) {
            Log.e(TAG, "createInputContentAccessToken failed. contentUri=" + contentUri.toString()
                    + " packageName=" + editorInfo.packageName, e);
            return;
        }
        inputContentInfo.setUriToken(uriToken);
        return;
    }
    }


    private static int mapToImeWindowStatus(boolean isInputViewShown) {
    private static int mapToImeWindowStatus(boolean isInputViewShown) {
+9 −1
Original line number Original line Diff line number Diff line
@@ -183,7 +183,15 @@ public final class InputContentInfo implements Parcelable {
    @Nullable
    @Nullable
    public Uri getLinkUri() { return mLinkUri; }
    public Uri getLinkUri() { return mLinkUri; }


    void setUriToken(IInputContentUriToken token) {
    /**
     * Update the internal state of this object to be associated with the given token.
     *
     * <p>TODO(yukawa): Come up with an idea to make {@link InputContentInfo} immutable.</p>
     *
     * @param token special URI token obtained from the system.
     * @hide
     */
    public void setUriToken(IInputContentUriToken token) {
        if (mUriToken != null) {
        if (mUriToken != null) {
            throw new IllegalStateException("URI token is already set");
            throw new IllegalStateException("URI token is already set");
        }
        }
+21 −1
Original line number Original line Diff line number Diff line
@@ -25,6 +25,8 @@ import android.inputmethodservice.InputMethodService;
import android.os.IBinder;
import android.os.IBinder;
import android.os.ResultReceiver;
import android.os.ResultReceiver;


import com.android.internal.inputmethod.IInputMethodPrivilegedOperations;

/**
/**
 * The InputMethod interface represents an input method which can generate key
 * The InputMethod interface represents an input method which can generate key
 * events and text, such as digital, email addresses, CJK characters, other
 * events and text, such as digital, email addresses, CJK characters, other
@@ -80,6 +82,24 @@ public interface InputMethod {
        public void sessionCreated(InputMethodSession session);
        public void sessionCreated(InputMethodSession session);
    }
    }


    /**
     * Called first thing after an input method is created, this supplies a
     * unique token for the session it has with the system service as well as
     * IPC endpoint to do some other privileged operations.
     *
     * @param token special token for the system to identify
     *              {@link InputMethodService}
     * @param privilegedOperations IPC endpoint to do some privileged
     *                             operations that are allowed only to the
     *                             current IME.
     * @hide
     */
    @MainThread
    default void initializeInternal(IBinder token,
            IInputMethodPrivilegedOperations privilegedOperations) {
        attachToken(token);
    }

    /**
    /**
     * Called first thing after an input method is created, this supplies a
     * Called first thing after an input method is created, this supplies a
     * unique token for the session it has with the system service.  It is
     * unique token for the session it has with the system service.  It is
Loading