From 4ba8484ffbef4fac301ea70210549692e9684e0d Mon Sep 17 00:00:00 2001 From: Ming-Shin Lu Date: Fri, 4 Dec 2020 03:21:12 +0000 Subject: [PATCH] Revert "Revert "Let IME#onFinishInput called without dup onStart..." Revert^2 "Verify lifecycle test when screen on/off" c2769e41ea17d7f0d261396d28ddde3563422701 Reason: Fix forward Bug 174512702 and Bug 174445559 with CL[1]. CL[1]: Icf1504d40b25fc5f53eae65d4bab48a3070db1d6 Change-Id: Id4e71a822bfde5fe6a263bbe094c0d238017efe1 --- core/api/test-current.txt | 8 ++++ .../IInputMethodSessionWrapper.java | 10 +++++ .../InputMethodService.java | 29 ++++++++++++- ...lientInputMethodClientCallbackAdaptor.java | 9 +++- .../java/android/view/ImeFocusController.java | 20 +++++++++ .../view/inputmethod/InputMethodManager.java | 37 +++++++++++++++- .../internal/view/IInputMethodClient.aidl | 2 +- .../internal/view/IInputMethodSession.aidl | 2 + .../InputMethodManagerService.java | 43 ++++++++++++++----- .../MultiClientInputMethodManagerService.java | 4 +- 10 files changed, 146 insertions(+), 18 deletions(-) diff --git a/core/api/test-current.txt b/core/api/test-current.txt index d38e5373b9cb2..51edd03515e11 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -839,6 +839,14 @@ package android.hardware.soundtrigger { } +package android.inputmethodservice { + + public class InputMethodService extends android.inputmethodservice.AbstractInputMethodService { + field public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // 0x94fa793L + } + +} + package android.location { public final class GnssClock implements android.os.Parcelable { diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index e9de27456f975..0766917642e80 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -54,6 +54,8 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub private static final int DO_VIEW_CLICKED = 115; private static final int DO_NOTIFY_IME_HIDDEN = 120; private static final int DO_REMOVE_IME_SURFACE = 130; + private static final int DO_FINISH_INPUT = 140; + @UnsupportedAppUsage HandlerCaller mCaller; @@ -141,6 +143,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub mInputMethodSession.removeImeSurface(); return; } + case DO_FINISH_INPUT: { + mInputMethodSession.finishInput(); + return; + } } Log.w(TAG, "Unhandled message code: " + msg.what); } @@ -222,6 +228,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_SESSION)); } + @Override + public void finishInput() { + mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_FINISH_INPUT)); + } private final class ImeInputEventReceiver extends InputEventReceiver implements InputMethodSession.EventCallback { private final SparseArray mPendingEvents = new SparseArray(); diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 14bc1ddad5f0c..6831eca32f726 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -62,9 +62,12 @@ import android.annotation.IntDef; import android.annotation.MainThread; import android.annotation.NonNull; import android.annotation.Nullable; +import android.annotation.TestApi; import android.annotation.UiContext; import android.app.ActivityManager; import android.app.Dialog; +import android.compat.annotation.ChangeId; +import android.compat.annotation.EnabledSince; import android.compat.annotation.UnsupportedAppUsage; import android.content.Context; import android.content.pm.PackageManager; @@ -412,7 +415,29 @@ public class InputMethodService extends AbstractInputMethodService { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) int mTheme = 0; - + + /** + * Finish the {@link InputConnection} when the device becomes + * {@link android.os.PowerManager#isInteractive non-interactive}. + * + *

+ * If enabled by the current {@link InputMethodService input method}, the current input + * connection will be {@link InputMethodService#onFinishInput finished} whenever the devices + * becomes non-interactive. + * + *

+ * If not enabled, the current input connection will instead be silently deactivated when the + * devices becomes non-interactive, and an {@link InputMethodService#onFinishInput + * onFinishInput()} {@link InputMethodService#onStartInput onStartInput()} pair is dispatched + * when the device becomes interactive again. + * + * @hide + */ + @TestApi + @ChangeId + @EnabledSince(targetSdkVersion = Build.VERSION_CODES.S) + public static final long FINISH_INPUT_NO_FALLBACK_CONNECTION = 156215187L; // This is a bug id. + LayoutInflater mInflater; TypedArray mThemeAttrs; @UnsupportedAppUsage @@ -2352,7 +2377,7 @@ public class InputMethodService extends AbstractInputMethodService { } void doStartInput(InputConnection ic, EditorInfo attribute, boolean restarting) { - if (!restarting) { + if (!restarting && mInputStarted) { doFinishInput(); } ImeTracing.getInstance().triggerServiceDump("InputMethodService#doStartInput", this, diff --git a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java index dbb669be14027..2db9ed1103fa4 100644 --- a/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java +++ b/core/java/android/inputmethodservice/MultiClientInputMethodClientCallbackAdaptor.java @@ -23,6 +23,7 @@ import android.os.Bundle; import android.os.Debug; import android.os.Handler; import android.os.Looper; +import android.os.RemoteException; import android.os.ResultReceiver; import android.util.Log; import android.view.InputChannel; @@ -38,8 +39,8 @@ import android.view.inputmethod.EditorInfo; import android.view.inputmethod.ExtractedText; import com.android.internal.annotations.GuardedBy; -import com.android.internal.inputmethod.IMultiClientInputMethodSession; import com.android.internal.inputmethod.CancellationGroup; +import com.android.internal.inputmethod.IMultiClientInputMethodSession; import com.android.internal.os.SomeArgs; import com.android.internal.util.function.pooled.PooledLambda; import com.android.internal.view.IInputContext; @@ -303,6 +304,12 @@ final class MultiClientInputMethodClientCallbackAdaptor { // no-op for multi-session reportNotSupported(); } + + @Override + public void finishInput() throws RemoteException { + // no-op for multi-session + reportNotSupported(); + } } private static final class MultiClientInputMethodSessionImpl diff --git a/core/java/android/view/ImeFocusController.java b/core/java/android/view/ImeFocusController.java index efc0bd2785f40..4a5c95f43d462 100644 --- a/core/java/android/view/ImeFocusController.java +++ b/core/java/android/view/ImeFocusController.java @@ -221,6 +221,25 @@ public final class ImeFocusController { mHasImeFocus = false; } + /** + * To handle the lifecycle of the input connection when the device interactivity state changed. + * (i.e. Calling IMS#onFinishInput when the device screen-off and Calling IMS#onStartInput + * when the device screen-on again). + */ + @UiThread + public void onInteractiveChanged(boolean interactive) { + final InputMethodManagerDelegate immDelegate = getImmDelegate(); + if (!immDelegate.isCurrentRootView(mViewRootImpl)) { + return; + } + if (interactive) { + final View focusedView = mViewRootImpl.mView.findFocus(); + onViewFocusChanged(focusedView, focusedView != null); + } else { + mDelegate.finishInputAndReportToIme(); + } + } + /** * @param windowAttribute {@link WindowManager.LayoutParams} to be checked. * @return Whether the window is in local focus mode or not. @@ -256,6 +275,7 @@ public final class ImeFocusController { @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode, int windowFlags, boolean forceNewFocus); void finishInput(); + void finishInputAndReportToIme(); void closeCurrentIme(); void finishComposingText(); void setCurrentRootView(ViewRootImpl rootView); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 4f944aa0fbe93..eaf72dce62fe2 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -606,6 +606,27 @@ public final class InputMethodManager { } } + /** + * Used by {@link ImeFocusController} to finish input connection and callback + * {@link InputMethodService#onFinishInput()}. + * + * This method is especially for when ImeFocusController received device screen-off event to + * ensure the entire finish input connection and the connection lifecycle callback to + * IME can be done for security concern. + */ + @Override + public void finishInputAndReportToIme() { + synchronized (mH) { + finishInputLocked(); + if (mCurMethod != null) { + try { + mCurMethod.finishInput(); + } catch (RemoteException e) { + } + } + } + } + /** * Used by {@link ImeFocusController} to hide current input method editor. */ @@ -864,12 +885,23 @@ public final class InputMethodManager { case MSG_SET_ACTIVE: { final boolean active = msg.arg1 != 0; final boolean fullscreen = msg.arg2 != 0; + final boolean reportToImeController = msg.obj != null && (boolean) msg.obj; if (DEBUG) { Log.i(TAG, "handleMessage: MSG_SET_ACTIVE " + active + ", was " + mActive); } synchronized (mH) { mActive = active; mFullscreenMode = fullscreen; + + // Report active state to ImeFocusController to handle IME input + // connection lifecycle callback when it allowed. + final ImeFocusController controller = getFocusController(); + final View rootView = mCurRootView != null ? mCurRootView.getView() : null; + if (controller != null && rootView != null && reportToImeController) { + rootView.post(() -> controller.onInteractiveChanged(active)); + return; + } + if (!active) { // Some other client has starting using the IME, so note // that this happened and make sure our own editor's @@ -1096,8 +1128,9 @@ public final class InputMethodManager { } @Override - public void setActive(boolean active, boolean fullscreen) { - mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0).sendToTarget(); + public void setActive(boolean active, boolean fullscreen, boolean reportToImeController) { + mH.obtainMessage(MSG_SET_ACTIVE, active ? 1 : 0, fullscreen ? 1 : 0, + reportToImeController).sendToTarget(); } @Override diff --git a/core/java/com/android/internal/view/IInputMethodClient.aidl b/core/java/com/android/internal/view/IInputMethodClient.aidl index 1145f51832061..ec9a0a2f48011 100644 --- a/core/java/com/android/internal/view/IInputMethodClient.aidl +++ b/core/java/com/android/internal/view/IInputMethodClient.aidl @@ -25,7 +25,7 @@ import com.android.internal.view.InputBindResult; oneway interface IInputMethodClient { void onBindMethod(in InputBindResult res); void onUnbindMethod(int sequence, int unbindReason); - void setActive(boolean active, boolean fullscreen); + void setActive(boolean active, boolean fullscreen, boolean reportToImeController); void scheduleStartInputIfNecessary(boolean fullscreen); void reportFullscreenMode(boolean fullscreen); void applyImeVisibility(boolean setVisible); diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index 0319e36373849..c6afd78ec04bd 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -52,4 +52,6 @@ oneway interface IInputMethodSession { void notifyImeHidden(); void removeImeSurface(); + + void finishInput(); } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 49101f2b5c05b..6395094038c76 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -15,6 +15,7 @@ package com.android.server.inputmethod; +import static android.inputmethodservice.InputMethodService.FINISH_INPUT_NO_FALLBACK_CONNECTION; import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; import static android.os.IServiceManager.DUMP_FLAG_PROTO; import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; @@ -155,6 +156,7 @@ import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodSubtype; import com.android.internal.annotations.GuardedBy; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.content.PackageMonitor; import com.android.internal.inputmethod.CallbackUtils; import com.android.internal.inputmethod.IInputBindResultResultCallback; @@ -722,6 +724,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub */ boolean mIsInteractive = true; + private IPlatformCompat mPlatformCompat; + int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; /** @@ -1685,6 +1689,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); mHasFeature = context.getPackageManager().hasSystemFeature( PackageManager.FEATURE_INPUT_METHODS); + mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); mIsLowRam = ActivityManager.isLowRamDeviceStatic(); @@ -2323,8 +2329,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( - MSG_SET_ACTIVE, 0, 0, mCurClient)); + scheduleSetActiveToClient(mCurClient, false /* active */, false /* fullscreen */, + false /* reportToImeController */); executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client)); mCurClient.sessionRequested = false; @@ -2472,7 +2478,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // If the screen is on, inform the new client it is active if (mIsInteractive) { - executeOrSendMessage(cs.client, mCaller.obtainMessageIO(MSG_SET_ACTIVE, 1, cs)); + scheduleSetActiveToClient(cs, true /* active */, false /* fullscreen */, + false /* reportToImeController */); } } @@ -4530,15 +4537,20 @@ public class InputMethodManagerService extends IInputMethodManager.Stub args.recycle(); return true; } - case MSG_SET_ACTIVE: + case MSG_SET_ACTIVE: { + args = (SomeArgs) msg.obj; + final ClientState clientState = (ClientState) args.arg1; try { - ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0); + clientState.client.setActive(args.argi1 != 0 /* active */, + args.argi2 != 0 /* fullScreen */, + args.argi3 != 0 /* reportToImeController */); } catch (RemoteException e) { Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " - + ((ClientState)msg.obj).pid + " uid " - + ((ClientState)msg.obj).uid); + + clientState.pid + " uid " + clientState.uid); } + args.recycle(); return true; + } case MSG_SET_INTERACTIVE: handleSetInteractive(msg.arg1 != 0); return true; @@ -4624,13 +4636,24 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // Inform the current client of the change in active status if (mCurClient != null && mCurClient.client != null) { - executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( - MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0, - mCurClient)); + boolean reportToImeController = false; + try { + reportToImeController = mPlatformCompat.isChangeEnabledByUid( + FINISH_INPUT_NO_FALLBACK_CONNECTION, mCurMethodUid); + } catch (RemoteException e) { + } + scheduleSetActiveToClient(mCurClient, mIsInteractive, mInFullscreenMode, + reportToImeController); } } } + private void scheduleSetActiveToClient(ClientState state, boolean active, boolean fullscreen, + boolean reportToImeController) { + executeOrSendMessage(state.client, mCaller.obtainMessageIIIIO(MSG_SET_ACTIVE, + active ? 1 : 0, fullscreen ? 1 : 0, reportToImeController ? 1 : 0, 0, state)); + } + private boolean chooseNewDefaultIMELocked() { final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( mSettings.getEnabledInputMethodListLocked()); diff --git a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java index 145662f10f2d8..6bdae63461b27 100644 --- a/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/MultiClientInputMethodManagerService.java @@ -1342,7 +1342,7 @@ public final class MultiClientInputMethodManagerService { switch (clientInfo.mState) { case InputMethodClientState.WAITING_FOR_IME_SESSION: try { - clientInfo.mClient.setActive(true, false); + clientInfo.mClient.setActive(true, false, false); } catch (RemoteException e) { // TODO(yukawa): Remove this client. return; @@ -1404,7 +1404,7 @@ public final class MultiClientInputMethodManagerService { return; } try { - clientInfo.mClient.setActive(active, false /* fullscreen */); + clientInfo.mClient.setActive(active, false /* fullscreen */, false); } catch (RemoteException e) { return; }