From c54c1171640519ae0ad8da1f32477295d96db1b8 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Thu, 6 Sep 2018 11:39:50 -0700 Subject: 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 --- .../inputmethodservice/InputMethodService.java | 131 ++++++++++++++++++--- 1 file changed, 113 insertions(+), 18 deletions(-) (limited to 'core/java/android/inputmethodservice/InputMethodService.java') diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 64c934b332f2..ea7d42ee79a9 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -42,6 +42,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; +import android.os.RemoteException; import android.os.ResultReceiver; import android.os.SystemClock; import android.provider.Settings; @@ -79,6 +80,9 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.inputmethod.IInputContentUriToken; +import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; + import java.io.FileDescriptor; import java.io.PrintWriter; import java.lang.annotation.Retention; @@ -342,7 +346,8 @@ public class InputMethodService extends AbstractInputMethodService { private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; InputMethodManager mImm; - + private IInputMethodPrivilegedOperations mPrivOps; + @UnsupportedAppUsage int mTheme = 0; @@ -444,6 +449,22 @@ public class InputMethodService extends AbstractInputMethodService { * all of the standard behavior for an input method. */ 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} */ @@ -470,9 +491,7 @@ public class InputMethodService extends AbstractInputMethodService { mInputConnection = binding.getConnection(); if (DEBUG) Log.v(TAG, "bindInput(): binding=" + binding + " ic=" + mInputConnection); - if (mImm != null && mToken != null) { - mImm.reportFullscreenMode(mToken, mIsFullscreen); - } + reportFullscreenMode(); initialize(); onBindInput(); } @@ -521,7 +540,11 @@ public class InputMethodService extends AbstractInputMethodService { public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @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(). // Otherwise IME developers who have overridden those interface methods will lose @@ -563,8 +586,8 @@ public class InputMethodService extends AbstractInputMethodService { } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. - mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()), - mBackDisposition); + setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? 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 * {@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. boolean showing = onEvaluateInputViewShown(); - mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), - mBackDisposition); + setImeWindowStatus(IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition); } } @@ -1090,7 +1123,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } mBackDisposition = disposition; - mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); } /** @@ -1188,7 +1221,18 @@ public class InputMethodService extends AbstractInputMethodService { public EditorInfo getCurrentInputEditorInfo() { 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 * 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) { changed = true; mIsFullscreen = isFullscreen; - if (mImm != null && mToken != null) { - mImm.reportFullscreenMode(mToken, mIsFullscreen); - } + reportFullscreenMode(); mFullscreenApplied = true; initialize(); LinearLayout.LayoutParams lp = (LinearLayout.LayoutParams) @@ -1823,7 +1865,7 @@ public class InputMethodService extends AbstractInputMethodService { final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); if (previousImeWindowStatus != nextImeWindowStatus) { - mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition); + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); } if ((previousImeWindowStatus & IME_ACTIVE) == 0) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); @@ -1848,7 +1890,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void doHideWindow() { - mImm.setImeWindowStatus(mToken, 0, mBackDisposition); + setImeWindowStatus(0, mBackDisposition); hideWindow(); } @@ -1889,10 +1931,30 @@ public class InputMethodService extends AbstractInputMethodService { + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); if (!mShouldClearInsetOfPreviousIme) return; - mImm.clearLastInputMethodWindowForTransition(mToken); + clearLastInputMethodWindowForTransition(); 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. + * + *

Caveat: {@link android.inputmethodservice.InputMethodService#clearInsetOfPreviousIme()} + * is the only expected caller of this method. Do not depend on this anywhere else.

+ * + *

TODO: We probably need to reconsider how IME should be handled.

+ */ + 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 * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} @@ -2806,7 +2868,40 @@ public class InputMethodService extends AbstractInputMethodService { if (getCurrentInputConnection() != inputConnection) { 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. + * + *

See {@link android.inputmethodservice.InputMethodService#exposeContent(InputContentInfo, + * InputConnection)} for details.

+ * + * @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) { -- cgit v1.2.3