From 99e1f6e04b63a4d85f42fda292001e8b77060093 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Fri, 22 Jun 2018 17:33:19 -0700 Subject: Notify new IME target before IME starts interacting Previously, there was a time window between when an IME starts new input and when the IME issues an IPC IMM#setImeWindowStatus() so that WindowManagerService (WMS) can be notified about the new IME target window. With this CL, it is now guaranteed that WindowManagerService (WMS) is always notified about the new IME target window before IME starts interacting with that window. Note that WMS is not using notified IME target window yet hence there should be no user-visible behavior change. Bug: 110531072 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: I032b91ce722a16b17518a5d88015c574d7d3e51b --- .../inputmethodservice/InputMethodService.java | 32 ++++++---------------- 1 file changed, 8 insertions(+), 24 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 b4c8a5e504bc..431c6511b1f3 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -370,19 +370,6 @@ 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. - * - *

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.

- */ - @Nullable - private IBinder mStartInputToken; - int mShowInputFlags; boolean mShowInputRequested; boolean mLastShowInputRequested; @@ -528,7 +515,7 @@ public class InputMethodService extends AbstractInputMethodService { public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken) { - mStartInputToken = startInputToken; + mImm.reportStartInput(mToken, startInputToken); // This needs to be dispatched to interface methods rather than doStartInput(). // Otherwise IME developers who have overridden those interface methods will lose @@ -579,8 +566,8 @@ public class InputMethodService extends AbstractInputMethodService { } clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. - mImm.setImeWindowStatus(mToken, mStartInputToken, - mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()), + mBackDisposition); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_SHOWN @@ -1059,8 +1046,8 @@ public class InputMethodService extends AbstractInputMethodService { } // If user uses hard keyboard, IME button should always be shown. boolean showing = onEvaluateInputViewShown(); - mImm.setImeWindowStatus(mToken, mStartInputToken, - IME_ACTIVE | (showing ? IME_VISIBLE : 0), mBackDisposition); + mImm.setImeWindowStatus(mToken, IME_ACTIVE | (showing ? IME_VISIBLE : 0), + mBackDisposition); } } @@ -1110,8 +1097,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } mBackDisposition = disposition; - mImm.setImeWindowStatus(mToken, mStartInputToken, mapToImeWindowStatus(isInputViewShown()), - mBackDisposition); + mImm.setImeWindowStatus(mToken, mapToImeWindowStatus(isInputViewShown()), mBackDisposition); } /** @@ -1861,8 +1847,7 @@ public class InputMethodService extends AbstractInputMethodService { final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); if (previousImeWindowStatus != nextImeWindowStatus) { - mImm.setImeWindowStatus(mToken, mStartInputToken, nextImeWindowStatus, - mBackDisposition); + mImm.setImeWindowStatus(mToken, nextImeWindowStatus, mBackDisposition); } if ((previousImeWindowStatus & IME_ACTIVE) == 0) { if (DEBUG) Log.v(TAG, "showWindow: showing!"); @@ -1887,7 +1872,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void doHideWindow() { - mImm.setImeWindowStatus(mToken, mStartInputToken, 0, mBackDisposition); + mImm.setImeWindowStatus(mToken, 0, mBackDisposition); hideWindow(); } @@ -2869,7 +2854,6 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mInputStarted=" + mInputStarted + " mInputViewStarted=" + mInputViewStarted + " mCandidatesViewStarted=" + mCandidatesViewStarted); - p.println(" mStartInputToken=" + mStartInputToken); if (mInputEditorInfo != null) { p.println(" mInputEditorInfo:"); -- cgit v1.2.3 From 1dd7d113b6f716b85e7a6030062b1fc10ea642eb Mon Sep 17 00:00:00 2001 From: Mathew Inwood Date: Tue, 31 Jul 2018 14:53:29 +0100 Subject: Add @UnsupportedAppUsage annotations For packages: android.inputmethodservice This is an automatically generated CL. See go/UnsupportedAppUsage for more details. Exempted-From-Owner-Approval: Mechanical changes to the codebase which have been approved by Android API council and announced on android-eng@ Bug: 110868826 Test: m Change-Id: Ia0c5584247aa586cb30a4e4dd2618ec7f92f90ed --- core/java/android/inputmethodservice/InputMethodService.java | 11 +++++++++++ 1 file changed, 11 insertions(+) (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 431c6511b1f3..46671b237203 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -28,6 +28,7 @@ import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.UnsupportedAppUsage; import android.app.ActivityManager; import android.app.Dialog; import android.content.Context; @@ -343,10 +344,12 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; + @UnsupportedAppUsage int mTheme = 0; LayoutInflater mInflater; TypedArray mThemeAttrs; + @UnsupportedAppUsage View mRootView; SoftInputWindow mWindow; boolean mInitialized; @@ -378,8 +381,10 @@ public class InputMethodService extends AbstractInputMethodService { boolean mFullscreenApplied; boolean mIsFullscreen; + @UnsupportedAppUsage View mExtractView; boolean mExtractViewHidden; + @UnsupportedAppUsage ExtractEditText mExtractEditText; ViewGroup mExtractAccessories; View mExtractAction; @@ -402,6 +407,7 @@ public class InputMethodService extends AbstractInputMethodService { */ boolean mShouldClearInsetOfPreviousIme; + @UnsupportedAppUsage final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; @@ -811,6 +817,7 @@ public class InputMethodService extends AbstractInputMethodService { mService.getContentResolver().unregisterContentObserver(this); } + @UnsupportedAppUsage private boolean shouldShowImeWithHardKeyboard() { // Lazily initialize as needed. if (mShowImeWithHardKeyboard == ShowImeWithHardKeyboardType.UNKNOWN) { @@ -850,6 +857,7 @@ public class InputMethodService extends AbstractInputMethodService { return "SettingsObserver{mShowImeWithHardKeyboard=" + mShowImeWithHardKeyboard + "}"; } } + @UnsupportedAppUsage private SettingsObserver mSettingsObserver; /** @@ -2492,6 +2500,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * @hide */ + @UnsupportedAppUsage public void onExtractedDeleteText(int start, int end) { InputConnection conn = getCurrentInputConnection(); if (conn != null) { @@ -2504,6 +2513,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * @hide */ + @UnsupportedAppUsage public void onExtractedReplaceText(int start, int end, CharSequence text) { InputConnection conn = getCurrentInputConnection(); if (conn != null) { @@ -2515,6 +2525,7 @@ public class InputMethodService extends AbstractInputMethodService { /** * @hide */ + @UnsupportedAppUsage public void onExtractedSetSpan(Object span, int start, int end, int flags) { InputConnection conn = getCurrentInputConnection(); if (conn != null) { -- cgit v1.2.3 From 674cc4bedd506bf5700abfdc56842f8e051091c4 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Thu, 6 Sep 2018 10:47:15 -0700 Subject: Clarify that InputMethod.attachToken() can be called at most once I don't know whether this comment was valid in the initial implementation or not, in the current system InputMethod.attachToken() is not (indirectly) exposed to the IME client process vir Binder interface. The only Binder interface that is exposed from the IME process to the IME client process is IInputMethodSession, not IInputMethod that implements attachToken(). One may think that a malicious app could try to call Context.bindService() with InputMethod.SERVICE_INTERFACE to directly obtain IInputMethod, but it should fail because IMEs must be protecting their InputMethodService with BIND_INPUT_METHOD so that only the system can bind to them. This CL clarifies this point in both JavaDoc and its implementation. If an IME happened to receive multiple attachToken(), it is an OS bug and worth letting people know by crashing the IME process. Fix: 114164394 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: Ia1e1d5ce020155b906e42a222f27b76905217395 --- core/java/android/inputmethodservice/InputMethodService.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 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 9295bb7c021c..64c934b332f2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -450,10 +450,12 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void attachToken(IBinder token) { - if (mToken == null) { - mToken = token; - mWindow.setToken(token); + if (mToken != null) { + throw new IllegalStateException( + "attachToken() must be called at most once. token=" + token); } + mToken = token; + mWindow.setToken(token); } /** -- cgit v1.2.3 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 From 2bc3d6f0a6da8bbd503d8615538e72b69ba16c9c Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Sun, 9 Sep 2018 20:48:34 -0700 Subject: Add a wrapper for IInputMethodPrivilegedOperations This is a mechanical refactoring to split out boilerplate code around IPCs from InputMethodManager to another file. Bug: 114418674 Bug: 113177698 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: I9ca251482867daea84c2777f74fd9b8a2b0f29cd --- .../inputmethodservice/InputMethodService.java | 60 ++++------------------ 1 file changed, 11 insertions(+), 49 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 ea7d42ee79a9..d3abe7f38808 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -42,7 +42,6 @@ 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; @@ -82,6 +81,7 @@ import android.widget.TextView; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; +import com.android.internal.inputmethod.InputMethodPrivilegedOperations; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -346,7 +346,7 @@ public class InputMethodService extends AbstractInputMethodService { private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; InputMethodManager mImm; - private IInputMethodPrivilegedOperations mPrivOps; + private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); @UnsupportedAppUsage int mTheme = 0; @@ -457,11 +457,7 @@ public class InputMethodService extends AbstractInputMethodService { @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; + mPrivOps.set(privilegedOperations); attachToken(token); } @@ -540,12 +536,7 @@ public class InputMethodService extends AbstractInputMethodService { public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, @NonNull IBinder startInputToken) { - try { - mPrivOps.reportStartInput(startInputToken); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } - + mPrivOps.reportStartInput(startInputToken); // This needs to be dispatched to interface methods rather than doStartInput(). // Otherwise IME developers who have overridden those interface methods will lose // notifications. @@ -607,14 +598,7 @@ 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(); - } + mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition); } /** @@ -1223,14 +1207,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void reportFullscreenMode() { - if (mPrivOps == null) { - return; - } - try { - mPrivOps.reportFullscreenMode(mIsFullscreen); - } catch (RemoteException e) { - throw e.rethrowFromSystemServer(); - } + mPrivOps.reportFullscreenMode(mIsFullscreen); } /** @@ -1945,14 +1922,7 @@ public class InputMethodService extends AbstractInputMethodService { *

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(); - } + mPrivOps.clearLastInputMethodWindowForTransition(); } /** @@ -2885,23 +2855,15 @@ public class InputMethodService extends AbstractInputMethodService { */ 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) { + final IInputContentUriToken uriToken = + mPrivOps.createInputContentUriToken(contentUri, editorInfo.packageName); + if (uriToken == null) { Log.e(TAG, "createInputContentAccessToken failed. contentUri=" + contentUri.toString() - + " packageName=" + editorInfo.packageName, e); + + " packageName=" + editorInfo.packageName); return; } inputContentInfo.setUriToken(uriToken); - return; } private static int mapToImeWindowStatus(boolean isInputViewShown) { -- cgit v1.2.3 From c7ca36845edb9937f7f9d09d50505ade3c8abf9f Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Sun, 9 Sep 2018 20:48:38 -0700 Subject: Stop relying on IMM in IMS for token-guarded IME APIs This is a follow up CL to previous CLs [1][2][3] that made sure that APIs that are exposed only to IMEs should live in InputMethodService instead of InputMethodManager. Now that we have a dedicated Binder inferface [4] that allows InputMethodService (IMS) to directly send IPCs to InputMethodManagerService (IMMS) without relying on InputMethodManager (IMM), it is natural for the above public APIs in IMS to stop relying on IMM. This CL also addresses a small concern that it is no longer obvious when those APIs become available. Previously, it was a bit more obvious that passing null IME token doesn't work so IME developers could imagine that those APIs were unavailable until attachToken() is called. With this CL, InputMethodPrivilegedOperations starts showing warning messages when called too early, which we hope help IME developers understand why those APIs do nothing when called too early. [1]: I3163f3cbe557c85103ca287bee0874a3b4194032 d8d03a8e1b5b7f4b05d7f99a19d356ffd95edb58 [2]: If6a786c5774805d041ea9672ef2721e4a38df7fc fbc2f7acd581eca7180a3d916936fb4e758279e1 [3]: I6efd5ca473e33e6faeadb7eea7772b9d2b8ca12b 164cfba536644095b962b45379020a792d3c51c8 [4]: I2f3ec3c5de546fb3603275a4b64000ed3f863b65 c54c1171640519ae0ad8da1f32477295d96db1b8 Bug: 114418674 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: I995c4b922f91b94438c1292392b2c3030598594f --- .../android/inputmethodservice/InputMethodService.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 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 d3abe7f38808..7f259e2f9f4c 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -1170,7 +1170,7 @@ public class InputMethodService extends AbstractInputMethodService { * used input method and subtype. */ public final boolean switchToPreviousInputMethod() { - return mImm.switchToPreviousInputMethodInternal(mToken); + return mPrivOps.switchToPreviousInputMethod(); } /** @@ -1182,7 +1182,7 @@ public class InputMethodService extends AbstractInputMethodService { * input method and subtype. */ public final boolean switchToNextInputMethod(boolean onlyCurrentIme) { - return mImm.switchToNextInputMethodInternal(mToken, onlyCurrentIme); + return mPrivOps.switchToNextInputMethod(onlyCurrentIme); } /** @@ -1195,7 +1195,7 @@ public class InputMethodService extends AbstractInputMethodService { * between IMEs and subtypes. */ public final boolean shouldOfferSwitchingToNextInputMethod() { - return mImm.shouldOfferSwitchingToNextInputMethodInternal(mToken); + return mPrivOps.shouldOfferSwitchingToNextInputMethod(); } public boolean getCurrentInputStarted() { @@ -1513,12 +1513,12 @@ public class InputMethodService extends AbstractInputMethodService { public void showStatusIcon(@DrawableRes int iconResId) { mStatusIcon = iconResId; - mImm.showStatusIconInternal(mToken, getPackageName(), iconResId); + mPrivOps.updateStatusIcon(getPackageName(), iconResId); } public void hideStatusIcon() { mStatusIcon = 0; - mImm.hideStatusIconInternal(mToken); + mPrivOps.updateStatusIcon(null, 0); } /** @@ -1529,7 +1529,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param id Unique identifier of the new input method to start. */ public void switchInputMethod(String id) { - mImm.setInputMethodInternal(mToken, id); + mPrivOps.setInputMethod(id); } /** @@ -1541,7 +1541,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param subtype The new subtype of the new input method to be switched to. */ public final void switchInputMethod(String id, InputMethodSubtype subtype) { - mImm.setInputMethodAndSubtypeInternal(mToken, id, subtype); + mPrivOps.setInputMethodAndSubtype(id, subtype); } public void setExtractView(View view) { @@ -2132,7 +2132,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param flags Provides additional operating flags. */ public void requestHideSelf(int flags) { - mImm.hideSoftInputFromInputMethodInternal(mToken, flags); + mPrivOps.hideMySoftInput(flags); } /** @@ -2144,7 +2144,7 @@ public class InputMethodService extends AbstractInputMethodService { * @param flags Provides additional operating flags. */ public final void requestShowSelf(int flags) { - mImm.showSoftInputFromInputMethodInternal(mToken, flags); + mPrivOps.showMySoftInput(flags); } private boolean handleBack(boolean doIt) { -- cgit v1.2.3 From eec552e9e9c453a7921463e5a0ade959e030f9a0 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Sun, 9 Sep 2018 20:48:41 -0700 Subject: Allow IMM to forward API calls to IMS This is one further step towards deprecating 8 IME APIs that were accidentally defined InputMethodManager (IMM) instead of InputMethodService (IMS). With this CL, API calls to those 8 deprecated ones in IMM will be forwarded to IMS so that we can completely remove corresponding IPC methods from IInputMethodManager.aidl. This guarantees that processes that have no InputMethodService running there become unable to access IPC methods behind such IME APIs that are intended to be used only from IMEs. One tricky thing is that the following 4 public APIs have been allowed to processes that have WRITE_SECURE_SETTINGS permission, even if such a process does not have active InputMethodService. * InputMethodManager.setInputMethod * InputMethodManager.setInputMethodAndSubtype * InputMethodManager.switchToLastInputMethod * InputMethodManager.switchToNextInputMethod In general, user mode apps should not have WRITE_SECURE_SETTINGS permission. Thus it might be not that difficult for us to simply deprecate such a special rule. Bug 114488811 is tracking that effort. For now, this CL preserves the existing behavior when a null IME token is specified to those 4 APIs. Bug: 114418674 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: If762714b2003fa6477e1318110f63e13968c1d7e --- core/java/android/inputmethodservice/InputMethodService.java | 6 ++++++ 1 file changed, 6 insertions(+) (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 7f259e2f9f4c..aa5cf09f396b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -458,6 +458,7 @@ public class InputMethodService extends AbstractInputMethodService { public final void initializeInternal(IBinder token, IInputMethodPrivilegedOperations privilegedOperations) { mPrivOps.set(privilegedOperations); + mImm.registerInputMethodPrivOps(token, mPrivOps); attachToken(token); } @@ -1000,6 +1001,11 @@ public class InputMethodService extends AbstractInputMethodService { mSettingsObserver.unregister(); mSettingsObserver = null; } + if (mToken != null) { + // This is completely optional, but allows us to show more explicit error messages + // when IME developers are doing something unsupported. + mImm.unregisterInputMethodPrivOps(mToken); + } } /** -- cgit v1.2.3 From c07fd4c2848302e30183d96475cc5c50c09a1c49 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 11 Sep 2018 11:37:13 -0700 Subject: Move notifyUserAction() to IInputMethodPrivilegedOperations This CL re-implements the way to propagate user action on an IME to InputMethodManagerService (IMMS) so that we can dynamically update IME Subtype rotation list discussed as requested in Bug 7043015. It turns out that my previous CLs [1][2][3][4] are unnecessarily complex because I tried to monitor user behavior in the IME client process rather than in the IME process. In the end, I ended up introducing a sequence number protocol for the sake of performance with a ton of complexity. This could have been implemented in a much safer and simpler way by sending user action signals from the IME process to IMMS, because A. IME already knows when it switches to a new subtype. IME needs to send a signal only once per subtype change. There is no need to use sequence counter. B. Malicious IME client is unable to disturb IME rotation list by sending a fake signal because the IPC endpoint is no longer exposed to IME client processes. In case there remain some applitations that still call this hidden API via reflection without gracefully handling exceptions, this CL keeps InputMethodManager.notifyUserAction() as a stub method so as not to break such applications. [1]: I11ed9a767588f8080753cd9bce011dac7db579ad d7443c83ceae0bdd20d68bf84648cf3b40115d85 [2]: I7f3e13a7226ef0dceee82b67e8a0d8536f7e9807 2a6a8d2fbb063c84e388c185402c4ca788618c72 [3]: I19ad8542659bc092b92ee13eb9f1d68ddd4b815a b56c6c721fc01fba8e36632d8e28f5123831abc5 [4]: I03fa436df0a5e348b3f93170aab3a8ac5a7e1677 c21ccc151631663d71230a3c1c756d94b575ab9e Bug: 113177698 Fix: 114159783 Test: Manually verified as follows 1. Build and flush aosp_taimen-userdebug 2. make -j SoftKeyboard 3. adb install -r $OUT/system/app/SoftKeyboard/SoftKeyboard.apk 4. adb shell ime enable com.example.android.softkeyboard/.SoftKeyboard 5. Open AOSP Keyboard settings 6. Enable "English (US)", "French", and "German" 7. Open SoftKeyboard settings 8. Enable "English (United States)", "English (GB)" 9. Open the Dialer app and tap the top edit field. 10. Make sure that the IME layout rotation order when tapping the globe key will be updated only when you tap the keyboard to enter some character. 11. Also confirm it with "adb shell dumpsys input_method" by checking "mSwitchingController:" section there. Change-Id: Icc1f9c7f530f0144ecfd460e86114e109ae0044e --- .../inputmethodservice/InputMethodService.java | 31 +++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) (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 aa5cf09f396b..2d12b867949a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -22,6 +22,7 @@ import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACK import static java.lang.annotation.RetentionPolicy.SOURCE; +import android.annotation.AnyThread; import android.annotation.CallSuper; import android.annotation.DrawableRes; import android.annotation.IntDef; @@ -79,6 +80,7 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; @@ -402,6 +404,10 @@ public class InputMethodService extends AbstractInputMethodService { @BackDispositionMode int mBackDisposition; + private Object mLock = new Object(); + @GuardedBy("mLock") + private boolean mNotifyUserActionSent; + /** * {@code true} when the previous IME had non-empty inset at the bottom of the screen and we * have not shown our own window yet. In this situation, the previous inset continues to be @@ -594,7 +600,7 @@ public class InputMethodService extends AbstractInputMethodService { @MainThread @Override public void changeInputMethodSubtype(InputMethodSubtype subtype) { - onCurrentInputMethodSubtypeChanged(subtype); + dispatchOnCurrentInputMethodSubtypeChanged(subtype); } } @@ -2792,6 +2798,13 @@ public class InputMethodService extends AbstractInputMethodService { } } + private void dispatchOnCurrentInputMethodSubtypeChanged(InputMethodSubtype newSubtype) { + synchronized (mLock) { + mNotifyUserActionSent = false; + } + onCurrentInputMethodSubtypeChanged(newSubtype); + } + // TODO: Handle the subtype change event /** * Called when the subtype was changed. @@ -2847,6 +2860,22 @@ public class InputMethodService extends AbstractInputMethodService { exposeContentInternal(inputContentInfo, getCurrentInputEditorInfo()); } + /** + * {@inheritDoc} + * @hide + */ + @AnyThread + @Override + public final void notifyUserActionIfNecessary() { + synchronized (mLock) { + if (mNotifyUserActionSent) { + return; + } + mPrivOps.notifyUserActionAsync(); + mNotifyUserActionSent = true; + } + } + /** * Allow the receiver of {@link InputContentInfo} to obtain a temporary read-only access * permission to the content. -- cgit v1.2.3 From 90120a8b5b14d4d0830b3b5f478bb627a7ac06ea Mon Sep 17 00:00:00 2001 From: lumark Date: Wed, 15 Aug 2018 00:33:03 +0800 Subject: Support IME Window to show in secondary display. 1) Moving WMS.setInputMethodWindowLocked to DisplayContent, each display can have its own IME window. 2) Add getDisplayIdFromWindow in WindowManagerInternal, used for InputMethodManagerService to know which display for given IME window token. 3) Support add / remove IME window according displayId. 4) Modify WMS.inputMethodClientHasFocus to traverse all active display if inputMethodClient focused. 5) Add displayId parameter for IInputMethod.initializeInternal to update context display then client can addView to right display. Note: 1) There should be zero behavior difference as long as the target app is running on the default display. 2) The current implementation is not final and there are still chances that the current IME may not work well or even crash depending on how the IME is implemented. Bug: 111364446 Test: manual, use ActivityView & launch Messages in VirtualDisplay, tap search icon to see if soft input keyboard shown & app window size is adjusted by soft input. Change-Id: I8da315936caebdc8b2c16cff4e24192c06743251 --- .../inputmethodservice/InputMethodService.java | 23 +++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) (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 2d12b867949a..34fa5b6b9842 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -16,6 +16,7 @@ package android.inputmethodservice; +import static android.view.Display.DEFAULT_DISPLAY; import static android.view.ViewGroup.LayoutParams.MATCH_PARENT; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS; @@ -461,10 +462,11 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(IBinder token, + public final void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privilegedOperations) { mPrivOps.set(privilegedOperations); mImm.registerInputMethodPrivOps(token, mPrivOps); + updateInputMethodDisplay(displayId); attachToken(token); } @@ -482,6 +484,22 @@ public class InputMethodService extends AbstractInputMethodService { mWindow.setToken(token); } + /** + * {@inheritDoc} + * @hide + */ + @MainThread + @Override + public void updateInputMethodDisplay(int displayId) { + // Update display for adding IME window to the right display. + if (displayId != DEFAULT_DISPLAY) { + // TODO(b/111364446) Need to address context lifecycle issue if need to re-create + // for update resources & configuration correctly when show soft input + // in non-default display. + updateDisplay(displayId); + } + } + /** * {@inheritDoc} * @@ -930,6 +948,9 @@ public class InputMethodService extends AbstractInputMethodService { // If the previous IME has occupied non-empty inset in the screen, we need to decide whether // we continue to use the same size of the inset or update it mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0); + // TODO(b/111364446) Need to address context lifecycle issue if need to re-create + // for update resources & configuration correctly when show soft input + // in non-default display. mInflater = (LayoutInflater)getSystemService( Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this, "InputMethod", mTheme, null, null, mDispatcherState, -- cgit v1.2.3 From d746a7e89386e87a234c4bfda7afb2fe79e8b832 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 18 Sep 2018 18:55:02 -0700 Subject: Let InputMethodPrivilegedOperationsRegistry deal with its singleton-ness This is a preparation to allow InputMethodManager to have per-display instances rather than the current per-process singleton instance. When I introduced InputMethodPrivilegedOperationsRegistry [1], there was an assumption that InputMethodManager was a per-process global singleton object. Now that we are going to break up that global per-process instance into multiple per-display instances, having multiple InputMethodPrivilegedOperationsRegistry instances probably does not make much sense, because it would likely to increases the risk of compability issues in existing IMEs. Although IME developers soon really need to use the right Context to obtain the right instance of InputMethodManager anyway, unnecessarily introducing compatibility pitfalls that can be avoided in the Framework side is not my intention. With this CL, following 9 methods can continue to work no matter whether InputMethodManager is a per-process singleton or not. This is fine because those APIs had been mistakenly exposed in InputMethodManager and already deprecated in favor of newly added ones in InputMethodService. * InputMethodManager.hideSoftInputFromInputMethod * InputMethodManager.hideStatusIcon * InputMethodManager.setInputMethod * InputMethodManager.setInputMethodAndSubtype * InputMethodManager.shouldOfferSwitchingToNextInputMethod * InputMethodManager.showSoftInputFromInputMethod * InputMethodManager.showStatusIcon * InputMethodManager.switchToLastInputMethod * InputMethodManager.switchToNextInputMethod [1]: If762714b2003fa6477e1318110f63e13968c1d7e eec552e9e9c453a7921463e5a0ade959e030f9a0 Bug: 115893206 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: I4a61470f06ffac5f7a512536f8431489db0108f4 --- core/java/android/inputmethodservice/InputMethodService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 34fa5b6b9842..b94840279cea 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -85,6 +85,7 @@ import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.IInputContentUriToken; import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; import com.android.internal.inputmethod.InputMethodPrivilegedOperations; +import com.android.internal.inputmethod.InputMethodPrivilegedOperationsRegistry; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -465,7 +466,7 @@ public class InputMethodService extends AbstractInputMethodService { public final void initializeInternal(IBinder token, int displayId, IInputMethodPrivilegedOperations privilegedOperations) { mPrivOps.set(privilegedOperations); - mImm.registerInputMethodPrivOps(token, mPrivOps); + InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); attachToken(token); } @@ -1031,7 +1032,7 @@ public class InputMethodService extends AbstractInputMethodService { if (mToken != null) { // This is completely optional, but allows us to show more explicit error messages // when IME developers are doing something unsupported. - mImm.unregisterInputMethodPrivOps(mToken); + InputMethodPrivilegedOperationsRegistry.remove(mToken); } } -- cgit v1.2.3 From d4d895e2f8a9493bc0c5831f453866eb861d5404 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 24 Sep 2018 16:01:20 -0700 Subject: Remove a special optimization for a quite rare scenario This CL effectively reverts the following 3 CLs. * Reduce window resizing during IME transition I5723f627ce323b0d12bd7b93f5b35fc4d342b50c 792faa2c16d319e874a1d633f964a78266d5f3f2 * Clear the inset of previous IME when necessary Ib04967f39b2529251e4835c42e9f99dba2cf43f2 2977eb7b6ce82309a1bb1ba4ab698f503cb0388a * Make IMS#clearInsetOfPreviousIme() reliable Ib567daa009c1139858dccadcfc6a04465ebecf36 833bdcedceee60a873694a77587393abfc830eb5 The main idea behind the first CL is that the target application can skil avoid layout resizing if the following two conditions are met. A. WindowManagerService (WMS) can remember the last IME's inset until the next IME's window is fully shown. B. Both the last IME and the new IME have the same inset. Basically the first CL implements the above A part with an assumption that some IMEs would do the B part. However, in reality it is quite unlikely that two random IMEs have the same inset size. At the same time, maintaining this kind of special optimization is getting harder and harder as more and more use cases and form factors need to be supported. Let's remove this optimization given that no one is benefited by it. Fix: 116492038 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Test: do not see any noticeable visual difference Change-Id: I8ffbf9bf7c3a8be54df0ca8eac1a1f041ef7d3c9 --- .../inputmethodservice/InputMethodService.java | 43 ---------------------- 1 file changed, 43 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 b94840279cea..ae12f93285a8 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -410,14 +410,6 @@ public class InputMethodService extends AbstractInputMethodService { @GuardedBy("mLock") private boolean mNotifyUserActionSent; - /** - * {@code true} when the previous IME had non-empty inset at the bottom of the screen and we - * have not shown our own window yet. In this situation, the previous inset continues to be - * shown as an empty region until it is explicitly updated. Basically we can trigger the update - * by calling 1) {@code mWindow.show()} or 2) {@link #clearInsetOfPreviousIme()}. - */ - boolean mShouldClearInsetOfPreviousIme; - @UnsupportedAppUsage final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; @@ -581,7 +573,6 @@ public class InputMethodService extends AbstractInputMethodService { mShowInputFlags = 0; mShowInputRequested = false; doHideWindow(); - clearInsetOfPreviousIme(); if (resultReceiver != null) { resultReceiver.send(wasVis != isInputViewShown() ? InputMethodManager.RESULT_HIDDEN @@ -601,7 +592,6 @@ public class InputMethodService extends AbstractInputMethodService { if (dispatchOnShowInputRequested(flags, false)) { showWindow(true); } - clearInsetOfPreviousIme(); // If user uses hard keyboard, IME button should always be shown. setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); @@ -946,9 +936,6 @@ public class InputMethodService extends AbstractInputMethodService { super.onCreate(); mImm = (InputMethodManager)getSystemService(INPUT_METHOD_SERVICE); mSettingsObserver = SettingsObserver.createAndRegister(this); - // If the previous IME has occupied non-empty inset in the screen, we need to decide whether - // we continue to use the same size of the inset or update it - mShouldClearInsetOfPreviousIme = (mImm.getInputMethodWindowVisibleHeight() > 0); // TODO(b/111364446) Need to address context lifecycle issue if need to re-create // for update resources & configuration correctly when show soft input // in non-default display. @@ -1882,9 +1869,6 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: showing!"); onWindowShown(); mWindow.show(); - // Put here rather than in onWindowShown() in case people forget to call - // super.onWindowShown(). - mShouldClearInsetOfPreviousIme = false; } } @@ -1933,32 +1917,6 @@ public class InputMethodService extends AbstractInputMethodService { // Intentionally empty } - /** - * Reset the inset occupied the previous IME when and only when - * {@link #mShouldClearInsetOfPreviousIme} is {@code true}. - */ - private void clearInsetOfPreviousIme() { - if (DEBUG) Log.v(TAG, "clearInsetOfPreviousIme() " - + " mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); - if (!mShouldClearInsetOfPreviousIme) return; - - 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() { - mPrivOps.clearLastInputMethodWindowForTransition(); - } - /** * Called when a new client has bound to the input method. This * may be followed by a series of {@link #onStartInput(EditorInfo, boolean)} @@ -2980,7 +2938,6 @@ public class InputMethodService extends AbstractInputMethodService { + " visibleTopInsets=" + mTmpInsets.visibleTopInsets + " touchableInsets=" + mTmpInsets.touchableInsets + " touchableRegion=" + mTmpInsets.touchableRegion); - p.println(" mShouldClearInsetOfPreviousIme=" + mShouldClearInsetOfPreviousIme); p.println(" mSettingsObserver=" + mSettingsObserver); } } -- cgit v1.2.3 From 9d73f2efa058b1a3b517dac276cf5d2128f4a867 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Wed, 26 Sep 2018 18:19:21 -0700 Subject: Deprecate InputMethodService#getInputMethodWindowRecommendedHeight() InputMethodService#getInputMethodWindowRecommendedHeight() was added with an assumption that some IMEs may want to call this API in InputMethodService#onCreate() to adjust its IME window height to be the same as the previous IME's window height [1], but in reality for IME developers this API is quite difficult to use because relying on this API means user-visible behavior is no longer deterministic. Basically "Recommended" is too vague to rely on. Let's deprecate this API before we end up having to define what is the "recommended" height for more complicated scenarios such as multi-displays and multi-profiles. With this CL, IMS##getInputMethodWindowRecommendedHeight() always returns 0. Basically doing this would not likely to cause compatibility issues because the possibility of returning 0 has been clearly mentioned in the API document. In practice this must have returned 0 when the previous IME did not show the software keyboard (e.g. AOSP keyboard with a hardware keyboard). Therefore IMEs that have correctly used this API should be able to fall back to a safe default behavior even if this API returns 0. [1]: I0e920ee79c526c3aea6872b063cf294e2ab081c8 658c7b896a751b971db1292d86655dbb97f00067 Fix: 116502957 Test: atest CtsInputMethodTestCases CtsInputMethodServiceHostTestCases Change-Id: Ia2cde031a0e67d45a3631e54226f9b5a0698dd61 --- .../inputmethodservice/InputMethodService.java | 24 +++++++++++++--------- 1 file changed, 14 insertions(+), 10 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 ae12f93285a8..f7f627ebedc2 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2803,18 +2803,22 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * @return The recommended height of the input method window. - * An IME author can get the last input method's height as the recommended height - * by calling this in - * {@link android.inputmethodservice.InputMethodService#onStartInputView(EditorInfo, boolean)}. - * If you don't need to use a predefined fixed height, you can avoid the window-resizing of IME - * switching by using this value as a visible inset height. It's efficient for the smooth - * transition between different IMEs. However, note that this may return 0 (or possibly - * unexpectedly low height). You should thus avoid relying on the return value of this method - * all the time. Please make sure to use a reasonable height for the IME. + * Aimed to return the previous input method's {@link Insets#contentTopInsets}, but its actual + * semantics has never been well defined. + * + *

Note that the previous document clearly mentioned that this method could return {@code 0} + * at any time for whatever reason. Now this method is just always returning {@code 0}.

+ * + * @return on Android {@link android.os.Build.VERSION_CODES#Q} and later devices this method + * always returns {@code 0} + * @deprecated the actual behavior of this method has never been well defined. You cannot use + * this method in a reliable and predictable way */ + @Deprecated public int getInputMethodWindowRecommendedHeight() { - return mImm.getInputMethodWindowVisibleHeight(); + Log.w(TAG, "getInputMethodWindowRecommendedHeight() is deprecated and now always returns 0." + + " Do not use this method."); + return 0; } /** -- cgit v1.2.3 From 5c05dedda1cc0261183e6aa2f8c7c1fb04f77203 Mon Sep 17 00:00:00 2001 From: Andrew Solovay Date: Tue, 2 Oct 2018 14:14:42 -0700 Subject: cherry-pick from pi-dev docs: Replacing {#link with {@link Several java files had the typo {#link (for cross-references to other Javadocs) instead of the proper {@link format. This was confusing the new doc publish tool (Mivi) since that's the format used for {# Django comments #}. Fixed a couple of links that had other errors (which prevented building once the {# -> {@ was done) and other typos. Replaced throughout the frameworks/base project; I'll need a separate CL for the AndroidX fixes. (Other files were not in the public Javadocs.) Bug: 111925950 Test: make ds-docs Change-Id: Ia06e1fffd814671289a1caebd5962aedc18a28d7 Original Change-Id: Ia06e1fffd814671289a1caebd5962aedc18a28d7 Exempt-From-Owner-Approval: Docs-only change --- core/java/android/inputmethodservice/InputMethodService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 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 f7f627ebedc2..097a3e3a270a 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2095,7 +2095,7 @@ public class InputMethodService extends AbstractInputMethodService { * Called when the application has reported a new location of its text * cursor. This is only called if explicitly requested by the input method. * The default implementation does nothing. - * @deprecated Use {#link onUpdateCursorAnchorInfo(CursorAnchorInfo)} instead. + * @deprecated Use {@link #onUpdateCursorAnchorInfo(CursorAnchorInfo)} instead. */ @Deprecated public void onUpdateCursor(Rect newCursor) { @@ -2162,7 +2162,7 @@ public class InputMethodService extends AbstractInputMethodService { } /** - * @return {#link ExtractEditText} if it is considered to be visible and active. Otherwise + * @return {@link ExtractEditText} if it is considered to be visible and active. Otherwise * {@code null} is returned. */ private ExtractEditText getExtractEditTextIfVisible() { -- cgit v1.2.3 From 31755f94e11225df5d59b8f7e535106200fdf32d Mon Sep 17 00:00:00 2001 From: Mathew Inwood Date: Thu, 20 Dec 2018 13:53:36 +0000 Subject: Limit access to suspected false positives. Members modified herein are suspected to be false positives: i.e. things that were added to the greylist in P, but subsequent data analysis suggests that they are not, in fact, used after all. Add a maxTargetSdk=P to these APIs. This is lower-risk that simply removing these things from the greylist, as none of out data sources are perfect nor complete. For APIs that are not supported yet by annotations, move them to hiddenapi-greylist-max-p.txt instead which has the same effect. Exempted-From-Owner-Approval: Automatic changes to the codebase affecting only @UnsupportedAppUsage annotations, themselves added without requiring owners approval earlier. Bug: 115609023 Test: m Change-Id: I020a9c09672ebcae64c5357abc4993e07e744687 --- core/java/android/inputmethodservice/InputMethodService.java | 5 +++-- 1 file changed, 3 insertions(+), 2 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 097a3e3a270a..7d40227f9c7d 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -41,6 +41,7 @@ import android.database.ContentObserver; import android.graphics.Rect; import android.graphics.Region; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -352,7 +353,7 @@ public class InputMethodService extends AbstractInputMethodService { InputMethodManager mImm; private InputMethodPrivilegedOperations mPrivOps = new InputMethodPrivilegedOperations(); - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mTheme = 0; LayoutInflater mInflater; @@ -410,7 +411,7 @@ public class InputMethodService extends AbstractInputMethodService { @GuardedBy("mLock") private boolean mNotifyUserActionSent; - @UnsupportedAppUsage + @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) final Insets mTmpInsets = new Insets(); final int[] mTmpLocation = new int[2]; -- cgit v1.2.3 From 9b60ba07e4f17d0cb0f466eb2e3b3ad7e8326aad Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Mon, 21 Jan 2019 00:06:27 -0800 Subject: Unify IInputMethodPrivilegedOperations as sync IPC Since its bettining [1] notifyUserAction() has been implemented as an async IPC method. This wasn't changed when I recently moved it from IInputMethodManager to IInputMethodPrivilegedOperations [2]. However, mixing sync and async methods into the same Binder object is known to be an anti-pattern. Although sending some signals in an asynchronous manner would make sense, it should be done by converting all the methods to async (with some result callbacks for operations that need to receive responce) like we do so in IInputContext.aidl. As for IInputMethodPrivilegedOperations, I think converting notifyUserAction() from async to sync is acceptable because there are already similar sync methods such as: * setImeWindowStatus * reportStartInput * reportFullscreenMode * updateStatusIcon [1]: I11ed9a767588f8080753cd9bce011dac7db579ad d7443c83ceae0bdd20d68bf84648cf3b40115d85 [2]: Icc1f9c7f530f0144ecfd460e86114e109ae0044e c07fd4c2848302e30183d96475cc5c50c09a1c49 Bug: 114159783 Test: Manually verified as follows 1. Build and flush aosp_taimen-userdebug into Taimen 2. make -j SoftKeyboard 3. adb install -r $OUT/system/app/SoftKeyboard/SoftKeyboard.apk 4. adb shell ime enable com.example.android.softkeyboard/.SoftKeyboard 5. Open AOSP Keyboard settings 6. Enable "English (US)", "French", and "German" 7. Open SoftKeyboard settings 8. Enable "English (United States)", "English (GB)" 9. Open the Dialer app and tap the top edit field. 10. Make sure that the IME layout rotation order when tapping the globe key will be updated only when you tap the keyboard to enter some character. 11. Also confirm it with "adb shell dumpsys input_method" by checking "mSwitchingController:" section there. Change-Id: I9d41f19e30f205acd4d257a105e285bd32288130 --- core/java/android/inputmethodservice/InputMethodService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (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 7d40227f9c7d..d3509d5eb289 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2856,7 +2856,7 @@ public class InputMethodService extends AbstractInputMethodService { if (mNotifyUserActionSent) { return; } - mPrivOps.notifyUserActionAsync(); + mPrivOps.notifyUserAction(); mNotifyUserActionSent = true; } } -- cgit v1.2.3 From 0eb8d16d904cb484097f20cf3aa24b0eda223dc1 Mon Sep 17 00:00:00 2001 From: Yohei Yukawa Date: Tue, 22 Jan 2019 21:47:57 -0800 Subject: Deprecate InputMethodService#onViewClicked() This CL marks InputMethodService#onViewClicked() deprecated. There is no actual implementation change at this moment, but developers should avoid relying on this callback in general not only on futuer versions of Android but also on existing devices. Since this API was implemented in Android 4.0 ICS [1] it never worked well for WebView and any other UI libraries that expose themselves as a giant "Canvas", which can host their own UI hierarchy and sub focus state. If IME developers want consistent and reliable user interaction across devices and applicactions, implementing IMEs' own UI on top of CursorAnchorInfo API would be a good choice. If IME developers just want to be notified an existing input connection is interrupted, they can just monitor InputMethodService#onFinishInput(). [1]: Ia71ad325e50a20e9e1001def662ef992bb50a9d0 863fcd62171e55bc9f2105d9fb5877df982454d8 Fix: 123245358 Test: make -j checkbuild Change-Id: I37a6c6d82d4a424f0df84633b09bca4881efbf02 --- core/java/android/inputmethodservice/InputMethodService.java | 7 +++++++ 1 file changed, 7 insertions(+) (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 d3509d5eb289..38ddc169f527 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -2087,7 +2087,14 @@ public class InputMethodService extends AbstractInputMethodService { * protocol, so applications with custom text editing written before this method appeared will * not call to inform the IME of this interaction. * @param focusChanged true if the user changed the focused view by this click. + * @see InputMethodManager#viewClicked(View) + * @deprecated The method may not be called 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. If your goal is just being notified when an + * on-going input is interrupted, simply monitor {@link #onFinishInput()}. */ + @Deprecated public void onViewClicked(boolean focusChanged) { // Intentionally empty } -- cgit v1.2.3 From eadb1392f85f1ac8801018a02c31427801959b1c Mon Sep 17 00:00:00 2001 From: Tarandeep Singh Date: Fri, 9 Nov 2018 18:15:57 +0100 Subject: Pre-render input method (IME transitions 1/n) Pre-render input method views and window when EditText receives focus. This is a pre-requisite for implementing better IME transitions. Strategy: Once EditText receives focus, startInput is called. If optimization is available, IME views and window (SoftInputWindow) are created and rendered. Until user taps on EditText or showSoftInput() is called, IME window remains invisible. This pre-rendered window is kept around until EditorInfo changes or new connection is started (onStartInput). IME window's visibility will be set using new Insets controller API rather than conventional client-side dialog.show(). Behavior: - This is just IME side preparation CL. No performance improvements yet. - There should be no user perceptible behavior change. - As long as IME developers were following official lifecycle, they shouldn't perceive any behavior change. Availability: This optimization, once fully implemented, will be available when: - Device is not "Low memory" - AND Master flag DebugFlags.FLAG_PRE_RENDER_IME_VIEWS is set. - ViewRootImpl.USE_NEW_INSETS_API is enabled Bug: 118599175 Bug: 111084606 Test: atest CtsInputMethodTestCases Test: atest CtsInputMethodServiceHostTestCases Test: atest ActivityManagerMultiDisplayTests Test: Tested with 4 IMEs and didn't preceive any behavior change. Scenarios tested: 1. With and without hardware keyboard 2. Screen rotation w/ fullscreen mode. 3. split-screen Change-Id: I1a6300fe167eb205ee2b4214a6e270a52ebae062 --- .../inputmethodservice/InputMethodService.java | 245 ++++++++++++++------- 1 file changed, 170 insertions(+), 75 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 d3509d5eb289..301e48c4a990 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -346,6 +346,13 @@ public class InputMethodService extends AbstractInputMethodService { */ public static final int IME_VISIBLE = 0x2; + /** + * @hide + * The IME is active and ready with views but set invisible. + * This flag cannot be combined with {@link #IME_VISIBLE}. + */ + public static final int IME_INVISIBLE = 0x4; + // Min and max values for back disposition. private static final int BACK_DISPOSITION_MIN = BACK_DISPOSITION_DEFAULT; private static final int BACK_DISPOSITION_MAX = BACK_DISPOSITION_ADJUST_NOTHING; @@ -362,10 +369,19 @@ public class InputMethodService extends AbstractInputMethodService { View mRootView; SoftInputWindow mWindow; boolean mInitialized; - boolean mWindowCreated; - boolean mWindowVisible; - boolean mWindowWasVisible; + boolean mViewsCreated; + // IME views visibility. + boolean mDecorViewVisible; + boolean mDecorViewWasVisible; boolean mInShowWindow; + // True if pre-rendering of IME views/window is supported. + boolean mCanPreRender; + // If IME is pre-rendered. + boolean mIsPreRendered; + // IME window visibility. + // Use (mDecorViewVisible && mWindowVisible) to check if IME is visible to the user. + boolean mWindowVisible; + ViewGroup mFullscreenArea; FrameLayout mExtractFrame; FrameLayout mCandidatesFrame; @@ -552,15 +568,18 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, + public final void dispatchStartInputWithToken(@Nullable InputConnection inputConnection, @NonNull EditorInfo editorInfo, boolean restarting, - @NonNull IBinder startInputToken) { + @NonNull IBinder startInputToken, boolean shouldPreRenderIme) { mPrivOps.reportStartInput(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); + mCanPreRender = shouldPreRenderIme; + if (DEBUG) Log.v(TAG, "Will Pre-render IME: " + mCanPreRender); + + if (restarting) { + restartInput(inputConnection, editorInfo); + } else { + startInput(inputConnection, editorInfo); + } } /** @@ -570,14 +589,27 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void hideSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "hideSoftInput()"); - boolean wasVis = isInputViewShown(); - mShowInputFlags = 0; - mShowInputRequested = false; - doHideWindow(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window invisible"); + } + setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + } else { + mShowInputFlags = 0; + mShowInputRequested = false; + doHideWindow(); + } + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_HIDDEN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -589,17 +621,28 @@ public class InputMethodService extends AbstractInputMethodService { @Override public void showSoftInput(int flags, ResultReceiver resultReceiver) { if (DEBUG) Log.v(TAG, "showSoftInput()"); - boolean wasVis = isInputViewShown(); + final boolean wasVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { - showWindow(true); + if (mIsPreRendered) { + // TODO: notify visibility to insets consumer. + if (DEBUG) { + Log.v(TAG, "Making IME window visible"); + } + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + showWindow(true); + } } // If user uses hard keyboard, IME button should always be shown. - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); - + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); + final boolean isVisible = mIsPreRendered + ? mDecorViewVisible && mWindowVisible : isInputViewShown(); + final boolean visibilityChanged = isVisible != wasVisible; if (resultReceiver != null) { - resultReceiver.send(wasVis != isInputViewShown() + resultReceiver.send(visibilityChanged ? InputMethodManager.RESULT_SHOWN - : (wasVis ? InputMethodManager.RESULT_UNCHANGED_SHOWN + : (wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN : InputMethodManager.RESULT_UNCHANGED_HIDDEN), null); } } @@ -972,7 +1015,7 @@ public class InputMethodService extends AbstractInputMethodService { void initViews() { mInitialized = false; - mWindowCreated = false; + mViewsCreated = false; mShowInputRequested = false; mShowInputFlags = 0; @@ -1046,7 +1089,7 @@ public class InputMethodService extends AbstractInputMethodService { } private void resetStateForNewConfiguration() { - boolean visible = mWindowVisible; + boolean visible = mDecorViewVisible; int showFlags = mShowInputFlags; boolean showingInput = mShowInputRequested; CompletionInfo[] completions = mCurCompletions; @@ -1129,7 +1172,7 @@ public class InputMethodService extends AbstractInputMethodService { return; } mBackDisposition = disposition; - setImeWindowStatus(mapToImeWindowStatus(isInputViewShown()), mBackDisposition); + setImeWindowStatus(mapToImeWindowStatus(), mBackDisposition); } /** @@ -1380,7 +1423,7 @@ public class InputMethodService extends AbstractInputMethodService { mExtractFrame.setVisibility(View.GONE); } updateCandidatesVisibility(mCandidatesVisibility == View.VISIBLE); - if (mWindowWasVisible && mFullscreenArea.getVisibility() != vis) { + if (mDecorViewWasVisible && mFullscreenArea.getVisibility() != vis) { int animRes = mThemeAttrs.getResourceId(vis == View.VISIBLE ? com.android.internal.R.styleable.InputMethodService_imeExtractEnterAnimation : com.android.internal.R.styleable.InputMethodService_imeExtractExitAnimation, @@ -1439,7 +1482,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void updateInputViewShown() { boolean isShown = mShowInputRequested && onEvaluateInputViewShown(); - if (mIsInputViewShown != isShown && mWindowVisible) { + if (mIsInputViewShown != isShown && mDecorViewVisible) { mIsInputViewShown = isShown; mInputFrame.setVisibility(isShown ? View.VISIBLE : View.GONE); if (mInputView == null) { @@ -1458,14 +1501,14 @@ public class InputMethodService extends AbstractInputMethodService { public boolean isShowInputRequested() { return mShowInputRequested; } - + /** * Return whether the soft input view is currently shown to the * user. This is the state that was last determined and * applied by {@link #updateInputViewShown()}. */ public boolean isInputViewShown() { - return mIsInputViewShown && mWindowVisible; + return mCanPreRender ? mWindowVisible : mIsInputViewShown && mDecorViewVisible; } /** @@ -1499,7 +1542,7 @@ public class InputMethodService extends AbstractInputMethodService { */ public void setCandidatesViewShown(boolean shown) { updateCandidatesVisibility(shown); - if (!mShowInputRequested && mWindowVisible != shown) { + if (!mShowInputRequested && mDecorViewVisible != shown) { // If we are being asked to show the candidates view while the app // has not asked for the input view to be shown, then we need // to update whether the window is shown. @@ -1804,7 +1847,8 @@ public class InputMethodService extends AbstractInputMethodService { public void showWindow(boolean showInput) { if (DEBUG) Log.v(TAG, "Showing window: showInput=" + showInput + " mShowInputRequested=" + mShowInputRequested - + " mWindowCreated=" + mWindowCreated + + " mViewsCreated=" + mViewsCreated + + " mDecorViewVisible=" + mDecorViewVisible + " mWindowVisible=" + mWindowVisible + " mInputStarted=" + mInputStarted + " mShowInputFlags=" + mShowInputFlags); @@ -1814,18 +1858,42 @@ public class InputMethodService extends AbstractInputMethodService { return; } - mWindowWasVisible = mWindowVisible; + mDecorViewWasVisible = mDecorViewVisible; mInShowWindow = true; - showWindowInner(showInput); - mWindowWasVisible = true; + boolean isPreRenderedAndInvisible = mIsPreRendered && !mWindowVisible; + final int previousImeWindowStatus = + (mDecorViewVisible ? IME_ACTIVE : 0) | (isInputViewShown() + ? (isPreRenderedAndInvisible ? IME_INVISIBLE : IME_VISIBLE) : 0); + startViews(prepareWindow(showInput)); + final int nextImeWindowStatus = mapToImeWindowStatus(); + if (previousImeWindowStatus != nextImeWindowStatus) { + setImeWindowStatus(nextImeWindowStatus, mBackDisposition); + } + + // compute visibility + onWindowShown(); + mIsPreRendered = mCanPreRender; + if (mIsPreRendered) { + onPreRenderedWindowVisibilityChanged(true /* setVisible */); + } else { + // Pre-rendering not supported. + if (DEBUG) Log.d(TAG, "No pre-rendering supported"); + mWindowVisible = true; + } + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if ((previousImeWindowStatus & IME_ACTIVE) == 0) { + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + } + mDecorViewWasVisible = true; mInShowWindow = false; } - void showWindowInner(boolean showInput) { + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; - final int previousImeWindowStatus = - (mWindowVisible ? IME_ACTIVE : 0) | (isInputViewShown() ? IME_VISIBLE : 0); - mWindowVisible = true; + mDecorViewVisible = true; if (!mShowInputRequested && mInputStarted && showInput) { doShowInput = true; mShowInputRequested = true; @@ -1836,8 +1904,8 @@ public class InputMethodService extends AbstractInputMethodService { updateFullscreenMode(); updateInputViewShown(); - if (!mWindowCreated) { - mWindowCreated = true; + if (!mViewsCreated) { + mViewsCreated = true; initialize(); if (DEBUG) Log.v(TAG, "CALL: onCreateCandidatesView"); View v = onCreateCandidatesView(); @@ -1846,6 +1914,10 @@ public class InputMethodService extends AbstractInputMethodService { setCandidatesView(v); } } + return doShowInput; + } + + private void startViews(boolean doShowInput) { if (mShowInputRequested) { if (!mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); @@ -1857,29 +1929,26 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, false); } - - if (doShowInput) { - startExtractingText(false); - } + if (doShowInput) startExtractingText(false); + } - final int nextImeWindowStatus = mapToImeWindowStatus(isInputViewShown()); - if (previousImeWindowStatus != nextImeWindowStatus) { - setImeWindowStatus(nextImeWindowStatus, mBackDisposition); - } - if ((previousImeWindowStatus & IME_ACTIVE) == 0) { - if (DEBUG) Log.v(TAG, "showWindow: showing!"); + private void onPreRenderedWindowVisibilityChanged(boolean setVisible) { + mWindowVisible = setVisible; + mShowInputFlags = setVisible ? mShowInputFlags : 0; + mShowInputRequested = setVisible; + mDecorViewVisible = setVisible; + if (setVisible) { onWindowShown(); - mWindow.show(); } } - private void finishViews() { + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(false); + onFinishInputView(finishingInput); } else if (mCandidatesViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(false); + onFinishCandidatesView(finishingInput); } mInputViewStarted = false; mCandidatesViewStarted = false; @@ -1891,12 +1960,15 @@ public class InputMethodService extends AbstractInputMethodService { } public void hideWindow() { - finishViews(); - if (mWindowVisible) { + if (DEBUG) Log.v(TAG, "CALL: hideWindow"); + mIsPreRendered = false; + mWindowVisible = false; + finishViews(false /* finishingInput */); + if (mDecorViewVisible) { mWindow.hide(); - mWindowVisible = false; + mDecorViewVisible = false; onWindowHidden(); - mWindowWasVisible = false; + mDecorViewWasVisible = false; } updateFullscreenMode(); } @@ -1956,15 +2028,8 @@ public class InputMethodService extends AbstractInputMethodService { } void doFinishInput() { - if (mInputViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); - onFinishInputView(true); - } else if (mCandidatesViewStarted) { - if (DEBUG) Log.v(TAG, "CALL: onFinishCandidatesView"); - onFinishCandidatesView(true); - } - mInputViewStarted = false; - mCandidatesViewStarted = false; + if (DEBUG) Log.v(TAG, "CALL: doFinishInput"); + finishViews(true /* finishingInput */); if (mInputStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInput"); onFinishInput(); @@ -1984,7 +2049,7 @@ public class InputMethodService extends AbstractInputMethodService { initialize(); if (DEBUG) Log.v(TAG, "CALL: onStartInput"); onStartInput(attribute, restarting); - if (mWindowVisible) { + if (mDecorViewVisible) { if (mShowInputRequested) { if (DEBUG) Log.v(TAG, "CALL: onStartInputView"); mInputViewStarted = true; @@ -1995,6 +2060,31 @@ public class InputMethodService extends AbstractInputMethodService { mCandidatesViewStarted = true; onStartCandidatesView(mInputEditorInfo, restarting); } + } else if (mCanPreRender && mInputEditorInfo != null && mStartedInputConnection != null) { + // Pre-render IME views and window when real EditorInfo is available. + // pre-render IME window and keep it invisible. + if (DEBUG) Log.v(TAG, "Pre-Render IME for " + mInputEditorInfo.fieldName); + if (mInShowWindow) { + Log.w(TAG, "Re-entrance in to showWindow"); + return; + } + + mDecorViewWasVisible = mDecorViewVisible; + mInShowWindow = true; + startViews(prepareWindow(true /* showInput */)); + + // compute visibility + mIsPreRendered = true; + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + + // request draw for the IME surface. + // When IME is not pre-rendered, this will actually show the IME. + if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); + mWindow.show(); + mDecorViewWasVisible = true; + mInShowWindow = false; + } else { + mIsPreRendered = false; } } @@ -2146,7 +2236,7 @@ public class InputMethodService extends AbstractInputMethodService { // consume the back key. if (doIt) requestHideSelf(0); return true; - } else if (mWindowVisible) { + } else if (mDecorViewVisible) { if (mCandidatesVisibility == View.VISIBLE) { // If we are showing candidates even if no input area, then // hide them. @@ -2173,7 +2263,6 @@ public class InputMethodService extends AbstractInputMethodService { return mExtractEditText; } - /** * Called back when a {@link KeyEvent} is forwarded from the target application. * @@ -2886,8 +2975,11 @@ public class InputMethodService extends AbstractInputMethodService { inputContentInfo.setUriToken(uriToken); } - private static int mapToImeWindowStatus(boolean isInputViewShown) { - return IME_ACTIVE | (isInputViewShown ? IME_VISIBLE : 0); + private int mapToImeWindowStatus() { + return IME_ACTIVE + | (isInputViewShown() + ? (mCanPreRender ? (mWindowVisible ? IME_VISIBLE : IME_INVISIBLE) + : IME_VISIBLE) : 0); } /** @@ -2897,9 +2989,10 @@ public class InputMethodService extends AbstractInputMethodService { @Override protected void dump(FileDescriptor fd, PrintWriter fout, String[] args) { final Printer p = new PrintWriterPrinter(fout); p.println("Input method service state for " + this + ":"); - p.println(" mWindowCreated=" + mWindowCreated); - p.println(" mWindowVisible=" + mWindowVisible - + " mWindowWasVisible=" + mWindowWasVisible + p.println(" mViewsCreated=" + mViewsCreated); + p.println(" mDecorViewVisible=" + mDecorViewVisible + + " mDecorViewWasVisible=" + mDecorViewWasVisible + + " mWindowVisible=" + mWindowVisible + " mInShowWindow=" + mInShowWindow); p.println(" Configuration=" + getResources().getConfiguration()); p.println(" mToken=" + mToken); @@ -2919,6 +3012,8 @@ public class InputMethodService extends AbstractInputMethodService { p.println(" mShowInputRequested=" + mShowInputRequested + " mLastShowInputRequested=" + mLastShowInputRequested + + " mCanPreRender=" + mCanPreRender + + " mIsPreRendered=" + mIsPreRendered + " mShowInputFlags=0x" + Integer.toHexString(mShowInputFlags)); p.println(" mCandidatesVisibility=" + mCandidatesVisibility + " mFullscreenApplied=" + mFullscreenApplied -- cgit v1.2.3 From 2cbcd7ffbf689591a234bee7c416ecf16bd1e684 Mon Sep 17 00:00:00 2001 From: Tarandeep Singh Date: Fri, 25 Jan 2019 11:47:57 -0800 Subject: Pipe IME state into insets (IME transitions 3/n) Add a IME state changes callback that pipes IME state into the Inset consumer. Bug: 118599175 Bug: 118118435 Test: atest InsetControllerTest Test: atest InsetSourceConsumerTest Test: atest ImeInsetsSourceConsumerTest Change-Id: Id878226418e19cdf0499a0094f1d5c47fea33125 --- .../inputmethodservice/InputMethodService.java | 29 +++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) (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 5b3ad77dbf43..333cfbd400dd 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -597,6 +597,7 @@ public class InputMethodService extends AbstractInputMethodService { Log.v(TAG, "Making IME window invisible"); } setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + applyVisibilityInInsetsConsumer(false /* setVisible */); onPreRenderedWindowVisibilityChanged(false /* setVisible */); } else { mShowInputFlags = 0; @@ -625,10 +626,10 @@ public class InputMethodService extends AbstractInputMethodService { ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (dispatchOnShowInputRequested(flags, false)) { if (mIsPreRendered) { - // TODO: notify visibility to insets consumer. if (DEBUG) { Log.v(TAG, "Making IME window visible"); } + applyVisibilityInInsetsConsumer(true /* setVisible */); onPreRenderedWindowVisibilityChanged(true /* setVisible */); } else { showWindow(true); @@ -1887,10 +1888,23 @@ public class InputMethodService extends AbstractInputMethodService { if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); } + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } + /** + * Notify {@link android.view.ImeInsetsSourceConsumer} if IME has been pre-rendered + * for current EditorInfo, when pre-rendering is enabled. + */ + private void maybeNotifyPreRendered() { + if (!mCanPreRender || !mIsPreRendered) { + return; + } + mPrivOps.reportPreRendered(getCurrentInputEditorInfo()); + } + + private boolean prepareWindow(boolean showInput) { boolean doShowInput = false; mDecorViewVisible = true; @@ -1942,6 +1956,18 @@ public class InputMethodService extends AbstractInputMethodService { } } + /** + * Apply the IME visibility in {@link android.view.ImeInsetsSourceConsumer} when + * pre-rendering is enabled. + * @param setVisible {@code true} to make it visible, false to hide it. + */ + private void applyVisibilityInInsetsConsumer(boolean setVisible) { + if (!mIsPreRendered) { + return; + } + mPrivOps.applyImeVisibility(setVisible); + } + private void finishViews(boolean finishingInput) { if (mInputViewStarted) { if (DEBUG) Log.v(TAG, "CALL: onFinishInputView"); @@ -2081,6 +2107,7 @@ public class InputMethodService extends AbstractInputMethodService { // When IME is not pre-rendered, this will actually show the IME. if (DEBUG) Log.v(TAG, "showWindow: draw decorView!"); mWindow.show(); + maybeNotifyPreRendered(); mDecorViewWasVisible = true; mInShowWindow = false; } else { -- cgit v1.2.3 From 46d59f0ecb19fc586aecbe3008fd4661f20e98ef Mon Sep 17 00:00:00 2001 From: Tarandeep Singh Date: Tue, 29 Jan 2019 18:09:15 -0800 Subject: Link InsetsController to IME (IME transitons 4/n) With this change, InsetsController.show/hide now links to IME. This also takes care of animating IME along with other types. Insets API are reactive i.e. they remain in sync with state of IME. Test: atest InsetsControllerTest Test: atest ImeInsetsConsumerTest Bug: 118118435 Change-Id: Ib3997487bd19351d1d23bc70173fc9bdfd23a704 --- .../android/inputmethodservice/InputMethodService.java | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) (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 333cfbd400dd..ab630fd7467b 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -592,7 +592,6 @@ public class InputMethodService extends AbstractInputMethodService { final boolean wasVisible = mIsPreRendered ? mDecorViewVisible && mWindowVisible : isInputViewShown(); if (mIsPreRendered) { - // TODO: notify visibility to insets consumer. if (DEBUG) { Log.v(TAG, "Making IME window invisible"); } @@ -658,6 +657,11 @@ public class InputMethodService extends AbstractInputMethodService { } } + private void notifyImeHidden() { + setImeWindowStatus(IME_ACTIVE | IME_INVISIBLE, mBackDisposition); + onPreRenderedWindowVisibilityChanged(false /* setVisible */); + } + private void setImeWindowStatus(int visibilityFlags, int backDisposition) { mPrivOps.setImeWindowStatus(visibilityFlags, backDisposition); } @@ -760,6 +764,14 @@ public class InputMethodService extends AbstractInputMethodService { } InputMethodService.this.onUpdateCursorAnchorInfo(info); } + + /** + * Notify IME that window is hidden. + * @hide + */ + public final void notifyImeHidden() { + InputMethodService.this.notifyImeHidden(); + } } /** -- cgit v1.2.3 From e0af39403ed67d26b918da32c9da05d0371835ea Mon Sep 17 00:00:00 2001 From: lumark Date: Wed, 29 May 2019 17:11:57 +0800 Subject: Skip IME initialization when received the token that already registered When IME window switches from current display to another display and back quickly, system will re-bind InputMethodService to re-attach window token for each moving, the IME service lifecycle will be onBind -> onUnBind -> onDestroy -> onBind. Ideally, system will deliver window token when onServiceConnected and system should only receive one onServiceConnected when the last service bound. But due to user switch display very quick, before unbind service callbacked from client, the next bind service request comes, caused the connection exists in system side and client will then callback 2 onServiceConnected to system. Since CL [1] introduced InputMethodPrivilegedOperationsRegistry to deal with token registraction singleton-ness, the exception will be thrown for above case due to the duplicate window token passed to client. Add InputMethodPrivilegedOperationsRegistry#isRegistered to check if the token already registered, if so, skip this duplicate initialization request. [1]: I4a61470f06ffac5f7a512536f8431489db0108f4 Fix: 133624278 Test: manual as below steps: 1) Settings > Developer options, enable "Simulated display" & "force desktop mode" 2) Reboot device. 3) Launch app from simulated display. 4) Launch app from primary display (i.e. contacts), focus EditText to show IME. 5) Tapping primary & simulated display repeatly & quickly. 6) See if any exception log from IME application. (i.e. Gboard) Change-Id: Ie6bfbae735724fe744590e715124d2737d2b665d --- core/java/android/inputmethodservice/InputMethodService.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (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 ab630fd7467b..82d4d1d10d7e 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -472,8 +472,12 @@ public class InputMethodService extends AbstractInputMethodService { */ @MainThread @Override - public final void initializeInternal(IBinder token, int displayId, + public final void initializeInternal(@NonNull IBinder token, int displayId, IInputMethodPrivilegedOperations privilegedOperations) { + if (InputMethodPrivilegedOperationsRegistry.isRegistered(token)) { + Log.w(TAG, "The token has already registered, ignore this initialization."); + return; + } mPrivOps.set(privilegedOperations); InputMethodPrivilegedOperationsRegistry.put(token, mPrivOps); updateInputMethodDisplay(displayId); -- cgit v1.2.3