diff options
| author | 2023-01-13 05:22:18 +0000 | |
|---|---|---|
| committer | 2023-01-13 05:22:18 +0000 | |
| commit | aabc5676c3822f12f1e63e311c86abe9de34bc3b (patch) | |
| tree | 73fafe3ce2df6b2d5f115908ce5aab3ef53930b7 | |
| parent | 8a05fe90d857021f6557c4aa8287636a7396a3ea (diff) | |
| parent | c25f3681f3b987821ec685512c331cf7bde72ace (diff) | |
Merge changes from topic "new_ime_visibility_model"
* changes:
Add a unit test for verifying ImeVisibilityApplier
Remove IMMS#mShowRequested
Add unit tests for ImeVisibilityStateComputer
Migrate show/hide IME check softInputMode to ImeVisibilityStateComputer
10 files changed, 675 insertions, 218 deletions
diff --git a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto index 35aae8f92d93..5a18d9e627e8 100644 --- a/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto +++ b/core/proto/android/server/inputmethod/inputmethodmanagerservice.proto @@ -31,7 +31,7 @@ message InputMethodManagerServiceProto { optional string cur_focused_window_soft_input_mode = 6; optional .android.view.inputmethod.EditorInfoProto cur_attribute = 7; optional string cur_id = 8; - optional bool show_requested = 9; + reserved 9; // deprecated show_requested optional bool show_explicitly_requested = 10; optional bool show_forced = 11; optional bool input_shown = 12; diff --git a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java index 86a08579e38b..e21895aced1f 100644 --- a/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java +++ b/services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java @@ -21,7 +21,10 @@ import static android.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY; import static com.android.server.EventLogTags.IMF_HIDE_IME; import static com.android.server.EventLogTags.IMF_SHOW_IME; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS; import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT; import android.annotation.Nullable; import android.os.Binder; @@ -30,6 +33,7 @@ import android.os.ResultReceiver; import android.util.EventLog; import android.util.Slog; import android.view.inputmethod.ImeTracker; +import android.view.inputmethod.InputMethodManager; import com.android.internal.annotations.GuardedBy; import com.android.internal.inputmethod.InputMethodDebug; @@ -124,6 +128,12 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { @Override public void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state) { + applyImeVisibility(windowToken, statsToken, state, -1 /* ignore reason */); + } + + @GuardedBy("ImfLock.class") + void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, + @ImeVisibilityStateComputer.VisibilityState int state, int reason) { switch (state) { case STATE_SHOW_IME: ImeTracker.get().onProgress(statsToken, @@ -148,6 +158,17 @@ final class DefaultImeVisibilityApplier implements ImeVisibilityApplier { ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY); } break; + case STATE_HIDE_IME_EXPLICIT: + mService.hideCurrentInputLocked(windowToken, statsToken, 0, null, reason); + break; + case STATE_HIDE_IME_NOT_ALWAYS: + mService.hideCurrentInputLocked(windowToken, statsToken, + InputMethodManager.HIDE_NOT_ALWAYS, null, reason); + break; + case STATE_SHOW_IME_IMPLICIT: + mService.showCurrentInputLocked(windowToken, statsToken, + InputMethodManager.SHOW_IMPLICIT, null, reason); + break; default: throw new IllegalArgumentException("Invalid IME visibility state: " + state); } diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java index a2655f4c0f4d..795e4bf9d5cb 100644 --- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java +++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java @@ -18,11 +18,13 @@ package com.android.server.inputmethod; import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN; import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD; +import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN; import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED; import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; import static android.view.WindowManager.LayoutParams.SoftInputModeFlags; @@ -32,6 +34,7 @@ import static com.android.server.inputmethod.InputMethodManagerService.computeIm import android.accessibilityservice.AccessibilityService; import android.annotation.IntDef; import android.annotation.NonNull; +import android.content.res.Configuration; import android.os.IBinder; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -42,6 +45,8 @@ import android.view.inputmethod.ImeTracker; import android.view.inputmethod.InputMethod; import android.view.inputmethod.InputMethodManager; +import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.inputmethod.SoftInputShowHideReason; import com.android.server.LocalServices; import com.android.server.wm.WindowManagerInternal; @@ -88,6 +93,11 @@ public final class ImeVisibilityStateComputer { */ boolean mShowForced; + /** + * Set if we last told the input method to show itself. + */ + private boolean mInputShown; + /** Represent the invalid IME visibility state */ public static final int STATE_INVALID = -1; @@ -105,6 +115,12 @@ public final class ImeVisibilityStateComputer { /** State to handle showing an IME preview surface during the app was loosing the IME focus */ public static final int STATE_SHOW_IME_SNAPSHOT = 4; + + public static final int STATE_HIDE_IME_EXPLICIT = 5; + + public static final int STATE_HIDE_IME_NOT_ALWAYS = 6; + + public static final int STATE_SHOW_IME_IMPLICIT = 7; @IntDef({ STATE_INVALID, STATE_HIDE_IME, @@ -112,6 +128,9 @@ public final class ImeVisibilityStateComputer { STATE_SHOW_IME_ABOVE_OVERLAY, STATE_SHOW_IME_BEHIND_OVERLAY, STATE_SHOW_IME_SNAPSHOT, + STATE_HIDE_IME_EXPLICIT, + STATE_HIDE_IME_NOT_ALWAYS, + STATE_SHOW_IME_IMPLICIT, }) @interface VisibilityState {} @@ -120,11 +139,38 @@ public final class ImeVisibilityStateComputer { */ private final ImeVisibilityPolicy mPolicy; - public ImeVisibilityStateComputer(InputMethodManagerService service) { + public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service) { + this(service, + LocalServices.getService(WindowManagerInternal.class), + LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy, + new ImeVisibilityPolicy()); + } + + @VisibleForTesting + public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service, + @NonNull Injector injector) { + this(service, injector.getWmService(), injector.getImeValidator(), + new ImeVisibilityPolicy()); + } + + interface Injector { + default WindowManagerInternal getWmService() { + return null; + } + + default InputMethodManagerService.ImeDisplayValidator getImeValidator() { + return null; + } + } + + private ImeVisibilityStateComputer(InputMethodManagerService service, + WindowManagerInternal wmService, + InputMethodManagerService.ImeDisplayValidator imeDisplayValidator, + ImeVisibilityPolicy imePolicy) { mService = service; - mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); - mImeDisplayValidator = mWindowManagerInternal::getDisplayImePolicy; - mPolicy = new ImeVisibilityPolicy(); + mWindowManagerInternal = wmService; + mImeDisplayValidator = imeDisplayValidator; + mPolicy = imePolicy; } /** @@ -187,6 +233,7 @@ public final class ImeVisibilityStateComputer { void clearImeShowFlags() { mRequestedShowExplicitly = false; mShowForced = false; + mInputShown = false; } int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) { @@ -207,15 +254,22 @@ public final class ImeVisibilityStateComputer { * {@link #STATE_HIDE_IME}. */ void requestImeVisibility(IBinder windowToken, boolean showIme) { - final ImeTargetWindowState state = getOrCreateWindowState(windowToken); - state.setRequestedImeVisible(showIme); - setWindowState(windowToken, state); + ImeTargetWindowState state = getOrCreateWindowState(windowToken); + if (!mPolicy.mPendingA11yRequestingHideKeyboard) { + state.setRequestedImeVisible(showIme); + } else { + // As A11y requests no IME is just a temporary, so we don't change the requested IME + // visible in case the last visibility state goes wrong after leaving from the a11y + // policy. + mPolicy.mPendingA11yRequestingHideKeyboard = false; + } + setWindowStateInner(windowToken, state); } ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) { ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); if (state == null) { - state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, false, false); + state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, 0, false, false, false); } return state; } @@ -229,16 +283,176 @@ public final class ImeVisibilityStateComputer { ImeTargetWindowState state = getWindowStateOrNull(windowToken); if (state != null) { state.setRequestImeToken(token); - setWindowState(windowToken, state); + setWindowStateInner(windowToken, state); + } + } + + void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) { + final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); + if (state != null && newState.hasEdiorFocused()) { + // Inherit the last requested IME visible state when the target window is still + // focused with an editor. + newState.setRequestedImeVisible(state.mRequestedImeVisible); } + setWindowStateInner(windowToken, newState); } - void setWindowState(IBinder windowToken, ImeTargetWindowState newState) { - if (DEBUG) Slog.d(TAG, "setWindowState, windowToken=" + windowToken + private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) { + if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken + ", state=" + newState); mRequestWindowStateMap.put(windowToken, newState); } + static class ImeVisibilityResult { + private final @VisibilityState int mState; + private final @SoftInputShowHideReason int mReason; + + ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) { + mState = state; + mReason = reason; + } + + @VisibilityState int getState() { + return mState; + } + + @SoftInputShowHideReason int getReason() { + return mReason; + } + } + + ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) { + // TODO: Output the request IME visibility state according to the requested window state + final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE; + // Should we auto-show the IME even if the caller has not + // specified what should be done with it? + // We only do this automatically if the window can resize + // to accommodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. + final boolean doAutoShow = + (state.mSoftInputModeState & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) + == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE + || mService.mRes.getConfiguration().isLayoutSizeAtLeast( + Configuration.SCREENLAYOUT_SIZE_LARGE); + final boolean isForwardNavigation = (state.mSoftInputModeState + & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0; + + // We shows the IME when the system allows the IME focused target window to restore the + // IME visibility (e.g. switching to the app task when last time the IME is visible). + // Note that we don't restore IME visibility for some cases (e.g. when the soft input + // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation). + // Because the app might leverage these flags to hide soft-keyboard with showing their own + // UI for input. + if (state.hasEdiorFocused() && shouldRestoreImeVisibility(state)) { + if (DEBUG) Slog.v(TAG, "Will show input to restore visibility"); + // Inherit the last requested IME visible state when the target window is still + // focused with an editor. + state.setRequestedImeVisible(true); + setWindowStateInner(getWindowTokenFrom(state), state); + return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, + SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); + } + + switch (softInputVisibility) { + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: + if (state.hasImeFocusChanged() && (!state.hasEdiorFocused() || !doAutoShow)) { + if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) { + // There is no focus view, and this window will + // be behind any soft input window, so hide the + // soft input window if it is shown. + if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); + return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS, + SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); + } + } else if (state.hasEdiorFocused() && doAutoShow && isForwardNavigation) { + // There is a focus view, and we are navigating forward + // into the window, so show the input window for the user. + // We only do this automatically if the window can resize + // to accommodate the IME (so what the user sees will give + // them good context without input information being obscured + // by the IME) or if running on a large screen where there + // is more room for the target window + IME. + if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); + return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, + SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); + } + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: + // Do nothing. + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: + if (isForwardNavigation) { + if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); + return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, + SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); + } + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: + if (state.hasImeFocusChanged()) { + if (DEBUG) Slog.v(TAG, "Window asks to hide input"); + return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, + SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); + } + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: + if (isForwardNavigation) { + if (allowVisible) { + if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); + return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, + SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); + } else { + Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" + + " there is no focused view that also returns true from" + + " View#onCheckIsTextEditor()"); + } + } + break; + case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: + if (DEBUG) Slog.v(TAG, "Window asks to always show input"); + if (allowVisible) { + if (state.hasImeFocusChanged()) { + return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, + SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); + } + } else { + Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" + + " there is no focused view that also returns true from" + + " View#onCheckIsTextEditor()"); + } + break; + } + + if (!state.hasImeFocusChanged()) { + // On previous platforms, when Dialogs re-gained focus, the Activity behind + // would briefly gain focus first, and dismiss the IME. + // On R that behavior has been fixed, but unfortunately apps have come + // to rely on this behavior to hide the IME when the editor no longer has focus + // To maintain compatibility, we are now hiding the IME when we don't have + // an editor upon refocusing a window. + if (state.isStartInputByGainFocus()) { + if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); + return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, + SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); + } + } + if (!state.hasEdiorFocused() && mInputShown && state.isStartInputByGainFocus() + && mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) { + // Hide the soft-keyboard when the system do nothing for softInputModeState + // of the window being gained focus without an editor. This behavior benefits + // to resolve some unexpected IME visible cases while that window with following + // configurations being switched from an IME shown window: + // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor + // 2) SOFT_INPUT_STATE_VISIBLE state without an editor + // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor + if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); + return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, + SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR); + } + return null; + } + IBinder getWindowTokenFrom(IBinder requestImeToken) { for (IBinder windowToken : mRequestWindowStateMap.keySet()) { final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); @@ -273,11 +487,20 @@ public final class ImeVisibilityStateComputer { return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state)); } + boolean isInputShown() { + return mInputShown; + } + + void setInputShown(boolean inputShown) { + mInputShown = inputShown; + } + void dumpDebug(ProtoOutputStream proto, long fieldId) { proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly); proto.write(SHOW_FORCED, mShowForced); proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD, mPolicy.isA11yRequestNoSoftKeyboard()); + proto.write(INPUT_SHOWN, mInputShown); } void dump(PrintWriter pw) { @@ -285,6 +508,7 @@ public final class ImeVisibilityStateComputer { p.println(" mRequestedShowExplicitly=" + mRequestedShowExplicitly + " mShowForced=" + mShowForced); p.println(" mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy()); + p.println(" mInputShown=" + mInputShown); } /** @@ -309,6 +533,14 @@ public final class ImeVisibilityStateComputer { */ private boolean mA11yRequestingNoSoftKeyboard; + /** + * Used when A11y request to hide IME temporary when receiving + * {@link AccessibilityService#SHOW_MODE_HIDDEN} from + * {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without + * changing the requested IME visible state. + */ + private boolean mPendingA11yRequestingHideKeyboard; + void setImeHiddenByDisplayPolicy(boolean hideIme) { mImeHiddenByDisplayPolicy = hideIme; } @@ -320,6 +552,9 @@ public final class ImeVisibilityStateComputer { void setA11yRequestNoSoftKeyboard(int keyboardShowMode) { mA11yRequestingNoSoftKeyboard = (keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN; + if (mA11yRequestingNoSoftKeyboard) { + mPendingA11yRequestingHideKeyboard = true; + } } boolean isA11yRequestNoSoftKeyboard() { @@ -335,11 +570,14 @@ public final class ImeVisibilityStateComputer { * A class that represents the current state of the IME target window. */ static class ImeTargetWindowState { - ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, boolean imeFocusChanged, - boolean hasFocusedEditor) { + ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags, + boolean imeFocusChanged, boolean hasFocusedEditor, + boolean isStartInputByGainFocus) { mSoftInputModeState = softInputModeState; + mWindowFlags = windowFlags; mImeFocusChanged = imeFocusChanged; mHasFocusedEditor = hasFocusedEditor; + mIsStartInputByGainFocus = isStartInputByGainFocus; } /** @@ -347,6 +585,8 @@ public final class ImeVisibilityStateComputer { */ private final @SoftInputModeFlags int mSoftInputModeState; + private final int mWindowFlags; + /** * {@code true} means the IME focus changed from the previous window, {@code false} * otherwise. @@ -358,6 +598,8 @@ public final class ImeVisibilityStateComputer { */ private final boolean mHasFocusedEditor; + private final boolean mIsStartInputByGainFocus; + /** * Set if the client has asked for the input method to be shown. */ @@ -382,10 +624,18 @@ public final class ImeVisibilityStateComputer { return mHasFocusedEditor; } + boolean isStartInputByGainFocus() { + return mIsStartInputByGainFocus; + } + int getSoftInputModeState() { return mSoftInputModeState; } + int getWindowFlags() { + return mWindowFlags; + } + private void setImeDisplayId(int imeDisplayId) { mImeDisplayId = imeDisplayId; } @@ -418,6 +668,7 @@ public final class ImeVisibilityStateComputer { + " requestedImeVisible " + mRequestedImeVisible + " imeDisplayId " + mImeDisplayId + " softInputModeState " + softInputModeToString(mSoftInputModeState) + + " isStartInputByGainFocus " + mIsStartInputByGainFocus + "}"; } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java index 079234c2f95c..187de930cff3 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java @@ -350,7 +350,7 @@ final class InputMethodBindingController { // should now try to restart the service for us. mLastBindTime = SystemClock.uptimeMillis(); clearCurMethodAndSessions(); - mService.clearInputShowRequestLocked(); + mService.clearInputShownLocked(); mService.unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); } } diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 2dbbb1085bb1..a94c90c8ee63 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -33,13 +33,11 @@ import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKE import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID; import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION; import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY; -import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN; import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE; import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE; import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME; import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_SWITCH_USER_ID; import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD; -import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_REQUESTED; import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY; import static android.view.Display.DEFAULT_DISPLAY; import static android.view.Display.INVALID_DISPLAY; @@ -47,6 +45,7 @@ import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult; import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; @@ -77,7 +76,6 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManagerInternal; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; -import android.content.res.Configuration; import android.content.res.Resources; import android.database.ContentObserver; import android.graphics.Matrix; @@ -624,16 +622,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub return mBindingController.hasConnection(); } - /** - * Set if the client has asked for the input method to be shown. - */ - private boolean mShowRequested; - - /** - * Set if we last told the input method to show itself. - */ - private boolean mInputShown; - /** The token tracking the current IME request or {@code null} otherwise. */ @Nullable private ImeTracker.Token mCurStatsToken; @@ -689,7 +677,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub * The display ID of the input method indicates the fallback display which returned by * {@link #computeImeDisplayIdForTarget}. */ - private static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY; + static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY; /** * If non-null, this is the input method service we are currently connected @@ -1174,12 +1162,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mVisibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( accessibilitySoftKeyboardSetting); if (mVisibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { - final boolean showRequested = mShowRequested; hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, null /* resultReceiver */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); - mShowRequested = showRequested; - } else if (mShowRequested) { + } else if (isShowRequestedForCurrentWindow()) { showCurrentInputImplicitLocked(mCurFocusedWindow, SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); } @@ -2299,9 +2285,20 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } @GuardedBy("ImfLock.class") - void clearInputShowRequestLocked() { - mShowRequested = mInputShown; - mInputShown = false; + void clearInputShownLocked() { + mVisibilityStateComputer.setInputShown(false); + } + + @GuardedBy("ImfLock.class") + private boolean isInputShown() { + return mVisibilityStateComputer.isInputShown(); + } + + @GuardedBy("ImfLock.class") + private boolean isShowRequestedForCurrentWindow() { + final ImeTargetWindowState state = mVisibilityStateComputer.getWindowStateOrNull( + mCurFocusedWindow); + return state != null && state.isRequestedImeVisible(); } @GuardedBy("ImfLock.class") @@ -2340,7 +2337,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub setEnabledSessionLocked(session); session.mMethod.startInput(startInputToken, mCurInputConnection, mCurEditorInfo, restarting, navButtonFlags, mCurImeDispatcher); - if (mShowRequested) { + if (isShowRequestedForCurrentWindow()) { if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); // Re-use current statsToken, if it exists. final ImeTracker.Token statsToken = mCurStatsToken; @@ -2559,7 +2556,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub if (!mPreventImeStartupUnlessTextEditor) { return false; } - if (mShowRequested) { + if (isShowRequestedForCurrentWindow()) { return false; } if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { @@ -3370,9 +3367,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub ImeTracker.ORIGIN_SERVER_START_INPUT, reason); } - // TODO(b/246309664): make mShowRequested as per-window state. - mShowRequested = true; - if (!mVisibilityStateComputer.onImeShowFlags(statsToken, flags)) { return false; } @@ -3398,8 +3392,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mVisibilityApplier.performShowIme(windowToken, statsToken, mVisibilityStateComputer.getImeShowFlags(), resultReceiver, reason); - // TODO(b/246309664): make mInputShown tracked by the Ime visibility computer. - mInputShown = true; + mVisibilityStateComputer.setInputShown(true); return true; } else { ImeTracker.get().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME); @@ -3417,7 +3410,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub "InputMethodManagerService#hideSoftInput"); synchronized (ImfLock.class) { if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken)) { - if (mInputShown) { + if (isInputShown()) { ImeTracker.get().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); } else { ImeTracker.get().onCancelled(statsToken, @@ -3468,10 +3461,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // application process as a valid request, and have even promised such a behavior with CTS // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only // IMMS#InputShown indicates that the software keyboard is shown. - // TODO(b/246309664): Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. + // TODO(b/246309664): Clean up IMMS#mImeWindowVis IInputMethodInvoker curMethod = getCurMethodLocked(); - final boolean shouldHideSoftInput = (curMethod != null) - && (mInputShown || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); + final boolean shouldHideSoftInput = curMethod != null + && (isInputShown() || (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); mVisibilityStateComputer.requestImeVisibility(windowToken, false); if (shouldHideSoftInput) { @@ -3486,8 +3479,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } mBindingController.setCurrentMethodNotVisible(); mVisibilityStateComputer.clearImeShowFlags(); - mInputShown = false; - mShowRequested = false; // Cancel existing statsToken for show IME as we got a hide request. ImeTracker.get().onCancelled(mCurStatsToken, ImeTracker.PHASE_SERVER_WAIT_IME); mCurStatsToken = null; @@ -3666,8 +3657,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // Init the focused window state (e.g. whether the editor has focused or IME focus has // changed from another window). - final ImeTargetWindowState windowState = new ImeTargetWindowState( - softInputMode, !sameWindowFocused, isTextEditor); + final ImeTargetWindowState windowState = new ImeTargetWindowState(softInputMode, + windowFlags, !sameWindowFocused, isTextEditor, startInputByWinGainedFocus); mVisibilityStateComputer.setWindowState(windowToken, windowState); if (sameWindowFocused && isTextEditor) { @@ -3692,74 +3683,21 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurFocusedWindowClient = cs; mCurPerceptible = true; - // Should we auto-show the IME even if the caller has not - // specified what should be done with it? - // We only do this automatically if the window can resize - // to accommodate the IME (so what the user sees will give - // them good context without input information being obscured - // by the IME) or if running on a large screen where there - // is more room for the target window + IME. - final boolean doAutoShow = - (softInputMode & LayoutParams.SOFT_INPUT_MASK_ADJUST) - == LayoutParams.SOFT_INPUT_ADJUST_RESIZE - || mRes.getConfiguration().isLayoutSizeAtLeast( - Configuration.SCREENLAYOUT_SIZE_LARGE); - // We want to start input before showing the IME, but after closing // it. We want to do this after closing it to help the IME disappear // more quickly (not get stuck behind it initializing itself for the // new focused input, even if its window wants to hide the IME). boolean didStart = false; - InputBindResult res = null; - // We show the IME when the system allows the IME focused target window to restore the - // IME visibility (e.g. switching to the app task when last time the IME is visible). - // Note that we don't restore IME visibility for some cases (e.g. when the soft input - // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation). - // Because the app might leverage these flags to hide soft-keyboard with showing their own - // UI for input. - if (isTextEditor && editorInfo != null - && mVisibilityStateComputer.shouldRestoreImeVisibility(windowState)) { - if (DEBUG) Slog.v(TAG, "Will show input to restore visibility"); - res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, - editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); - showCurrentInputImplicitLocked(windowToken, - SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); - return res; - } - - switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { - case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: - if (!sameWindowFocused && (!isTextEditor || !doAutoShow)) { - if (LayoutParams.mayUseInputMethod(windowFlags)) { - // There is no focus view, and this window will - // be behind any soft input window, so hide the - // soft input window if it is shown. - if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, - SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); - - // If focused display changed, we should unbind current method - // to make app window in previous display relayout after Ime - // window token removed. - // Note that we can trust client's display ID as long as it matches - // to the display ID obtained from the window. - if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { - mBindingController.unbindCurrentMethod(); - } - } - } else if (isTextEditor && doAutoShow - && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { - // There is a focus view, and we are navigating forward - // into the window, so show the input window for the user. - // We only do this automatically if the window can resize - // to accommodate the IME (so what the user sees will give - // them good context without input information being obscured - // by the IME) or if running on a large screen where there - // is more room for the target window + IME. - if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); + + final ImeVisibilityResult imeVisRes = mVisibilityStateComputer.computeState(windowState, + isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)); + if (imeVisRes != null) { + switch (imeVisRes.getReason()) { + case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: + case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV: + case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV: + case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE: if (editorInfo != null) { res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, @@ -3767,106 +3705,25 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub imeDispatcher); didStart = true; } - showCurrentInputImplicitLocked(windowToken, - SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); - } - break; - case LayoutParams.SOFT_INPUT_STATE_UNCHANGED: - if (DEBUG) { - Slog.v(TAG, "Window asks to keep the input in whatever state it was last in"); - } - // Do nothing. - break; - case LayoutParams.SOFT_INPUT_STATE_HIDDEN: - if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { - if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, - SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); - } - break; - case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: - if (!sameWindowFocused) { - if (DEBUG) Slog.v(TAG, "Window asks to hide input"); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, - SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); - } - break; - case LayoutParams.SOFT_INPUT_STATE_VISIBLE: - if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { - if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); - if (isSoftInputModeStateVisibleAllowed( - unverifiedTargetSdkVersion, startInputFlags)) { - if (editorInfo != null) { - res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); - didStart = true; - } - showCurrentInputImplicitLocked(windowToken, - SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); - } else { - Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" - + " there is no focused view that also returns true from" - + " View#onCheckIsTextEditor()"); - } - } - break; - case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: - if (DEBUG) Slog.v(TAG, "Window asks to always show input"); - if (isSoftInputModeStateVisibleAllowed( - unverifiedTargetSdkVersion, startInputFlags)) { - if (!sameWindowFocused) { - if (editorInfo != null) { - res = startInputUncheckedLocked(cs, inputContext, - remoteAccessibilityInputConnection, editorInfo, startInputFlags, - startInputReason, unverifiedTargetSdkVersion, - imeDispatcher); - didStart = true; - } - showCurrentInputImplicitLocked(windowToken, - SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); - } - } else { - Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" - + " there is no focused view that also returns true from" - + " View#onCheckIsTextEditor()"); + break; + } + + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, null /* statsToken */, + imeVisRes.getState(), imeVisRes.getReason()); + + if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { + // If focused display changed, we should unbind current method + // to make app window in previous display relayout after Ime + // window token removed. + // Note that we can trust client's display ID as long as it matches + // to the display ID obtained from the window. + if (cs.mSelfReportedDisplayId != mCurTokenDisplayId) { + mBindingController.unbindCurrentMethod(); } - break; + } } - if (!didStart) { if (editorInfo != null) { - if (sameWindowFocused) { - // On previous platforms, when Dialogs re-gained focus, the Activity behind - // would briefly gain focus first, and dismiss the IME. - // On R that behavior has been fixed, but unfortunately apps have come - // to rely on this behavior to hide the IME when the editor no longer has focus - // To maintain compatibility, we are now hiding the IME when we don't have - // an editor upon refocusing a window. - if (startInputByWinGainedFocus) { - if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, - 0 /* flags */, null /* resultReceiver */, - SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); - } - } - if (!isTextEditor && mInputShown && startInputByWinGainedFocus - && mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) { - // Hide the soft-keyboard when the system do nothing for softInputModeState - // of the window being gained focus without an editor. This behavior benefits - // to resolve some unexpected IME visible cases while that window with following - // configurations being switched from an IME shown window: - // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor - // 2) SOFT_INPUT_STATE_VISIBLE state without an editor - // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor - if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); - hideCurrentInputLocked(mCurFocusedWindow, null /* statsToken */, 0 /* flags */, - null /* resultReceiver */, - SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR); - } res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection, editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion, @@ -4639,9 +4496,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); } proto.write(CUR_ID, getCurIdLocked()); - proto.write(SHOW_REQUESTED, mShowRequested); mVisibilityStateComputer.dumpDebug(proto, fieldId); - proto.write(INPUT_SHOWN, mInputShown); proto.write(IN_FULLSCREEN_MODE, mInFullscreenMode); proto.write(CUR_TOKEN, Objects.toString(getCurTokenLocked())); proto.write(CUR_TOKEN_DISPLAY_ID, mCurTokenDisplayId); @@ -4785,6 +4640,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); } + @VisibleForTesting + ImeVisibilityStateComputer getVisibilityStateComputer() { + return mVisibilityStateComputer; + } + + @VisibleForTesting + ImeVisibilityApplier getVisibilityApplier() { + return mVisibilityApplier; + } + @GuardedBy("ImfLock.class") void setEnabledSessionLocked(SessionState session) { if (mEnabledSession != session) { @@ -4847,7 +4712,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub // This is undocumented so far, but IMM#showInputMethodPicker() has been // implemented so that auxiliary subtypes will be excluded when the soft // keyboard is invisible. - showAuxSubtypes = mInputShown; + synchronized (ImfLock.class) { + showAuxSubtypes = isInputShown(); + } break; case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: showAuxSubtypes = true; @@ -4876,7 +4743,7 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub synchronized (ImfLock.class) { try { if (mEnabledSession != null && mEnabledSession.mSession != null - && !mShowRequested) { + && !isShowRequestedForCurrentWindow()) { mEnabledSession.mSession.removeImeSurface(); } } catch (RemoteException e) { @@ -5864,7 +5731,6 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub method = getCurMethodLocked(); p.println(" mCurMethod=" + getCurMethodLocked()); p.println(" mEnabledSession=" + mEnabledSession); - p.println(" mShowRequested=" + mShowRequested + " mInputShown=" + mInputShown); mVisibilityStateComputer.dump(pw); p.println(" mInFullscreenMode=" + mInFullscreenMode); p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java index 45ae3d8210c8..6abd3d7ef9ec 100644 --- a/services/core/java/com/android/server/wm/ActivityRecord.java +++ b/services/core/java/com/android/server/wm/ActivityRecord.java @@ -9357,6 +9357,15 @@ final class ActivityRecord extends WindowToken implements WindowManagerService.A configChangeFlags = 0; return; } + if (!preserveWindow) { + // If the activity is the IME input target, ensure storing the last IME shown state + // before relaunching it for restoring the IME visibility once its new window focused. + final InputTarget imeInputTarget = mDisplayContent.getImeInputTarget(); + mLastImeShown = imeInputTarget != null && imeInputTarget.getWindowState() != null + && imeInputTarget.getWindowState().mActivityRecord == this + && mDisplayContent.mInputMethodWindow != null + && mDisplayContent.mInputMethodWindow.isVisible(); + } // Do not waiting for translucent activity if it is going to relaunch. final Task rootTask = getRootTask(); if (rootTask != null && rootTask.mTranslucentActivityWaiting == this) { diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java index 4e7613b66d73..0243a98ee51a 100644 --- a/services/core/java/com/android/server/wm/WindowManagerService.java +++ b/services/core/java/com/android/server/wm/WindowManagerService.java @@ -9177,6 +9177,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean shouldRestoreImeVisibility(IBinder imeTargetWindowToken) { final Task imeTargetWindowTask; + boolean hadRequestedShowIme = false; synchronized (mGlobalLock) { final WindowState imeTargetWindow = mWindowMap.get(imeTargetWindowToken); if (imeTargetWindow == null) { @@ -9186,11 +9187,14 @@ public class WindowManagerService extends IWindowManager.Stub if (imeTargetWindowTask == null) { return false; } + if (imeTargetWindow.mActivityRecord != null) { + hadRequestedShowIme = imeTargetWindow.mActivityRecord.mLastImeShown; + } } final TaskSnapshot snapshot = getTaskSnapshot(imeTargetWindowTask.mTaskId, imeTargetWindowTask.mUserId, false /* isLowResolution */, false /* restoreFromDisk */); - return snapshot != null && snapshot.hasImeSurface(); + return snapshot != null && snapshot.hasImeSurface() || hadRequestedShowIme; } @Override diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java new file mode 100644 index 000000000000..73d04c64bc63 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static android.inputmethodservice.InputMethodService.IME_ACTIVE; + +import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT; +import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_SOFT_INPUT; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_INVALID; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME; +import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT; + +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.eq; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +import android.os.RemoteException; +import android.view.inputmethod.InputMethodManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test the behavior of {@link DefaultImeVisibilityApplier} when performing or applying the IME + * visibility state. + * + * Build/Install/Run: + * atest FrameworksInputMethodSystemServerTests:DefaultImeVisibilityApplierTest + */ +@RunWith(AndroidJUnit4.class) +public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTestBase { + private DefaultImeVisibilityApplier mVisibilityApplier; + + @Before + public void setUp() throws RemoteException { + super.setUp(); + mVisibilityApplier = + (DefaultImeVisibilityApplier) mInputMethodManagerService.getVisibilityApplier(); + mInputMethodManagerService.mCurFocusedWindowClient = mock( + InputMethodManagerService.ClientState.class); + } + + @Test + public void testPerformShowIme() throws Exception { + mVisibilityApplier.performShowIme(mWindowToken, null, null, SHOW_SOFT_INPUT); + verifyShowSoftInput(false, true, InputMethodManager.SHOW_IMPLICIT); + } + + @Test + public void testPerformHideIme() throws Exception { + mVisibilityApplier.performHideIme(mWindowToken, null, null, HIDE_SOFT_INPUT); + verifyHideSoftInput(false, true); + } + + @Test + public void testApplyImeVisibility_throwForInvalidState() { + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID); + assertThrows(IllegalArgumentException.class, + () -> mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_INVALID)); + } + + @Test + public void testApplyImeVisibility_showIme() { + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME); + verify(mMockWindowManagerInternal).showImePostLayout(eq(mWindowToken), any()); + } + + @Test + public void testApplyImeVisibility_hideIme() { + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME); + verify(mMockWindowManagerInternal).hideIme(eq(mWindowToken), anyInt(), any()); + } + + @Test + public void testApplyImeVisibility_hideImeExplicit() throws Exception { + mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_EXPLICIT); + verifyHideSoftInput(true, true); + } + + @Test + public void testApplyImeVisibility_hideNotAlways() throws Exception { + mInputMethodManagerService.mImeWindowVis = IME_ACTIVE; + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_HIDE_IME_NOT_ALWAYS); + verifyHideSoftInput(true, true); + } + + @Test + public void testApplyImeVisibility_showImeImplicit() throws Exception { + mVisibilityApplier.applyImeVisibility(mWindowToken, null, STATE_SHOW_IME_IMPLICIT); + verifyShowSoftInput(true, true, InputMethodManager.SHOW_IMPLICIT); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java new file mode 100644 index 000000000000..8415fe120c51 --- /dev/null +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.inputmethod; + +import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.Display.INVALID_DISPLAY; +import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY; +import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; +import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; +import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED; + +import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; +import static com.android.server.inputmethod.InputMethodManagerService.FALLBACK_DISPLAY_ID; +import static com.android.server.inputmethod.InputMethodManagerService.ImeDisplayValidator; + +import static com.google.common.truth.Truth.assertThat; + +import android.os.IBinder; +import android.os.RemoteException; +import android.view.inputmethod.InputMethodManager; + +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import com.android.server.wm.WindowManagerInternal; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** + * Test the behavior of {@link ImeVisibilityStateComputer} and {@link ImeVisibilityApplier} when + * requesting the IME visibility. + * + * Build/Install/Run: + * atest FrameworksInputMethodSystemServerTests:ImeVisibilityStateComputerTest + */ +@RunWith(AndroidJUnit4.class) +public class ImeVisibilityStateComputerTest extends InputMethodManagerServiceTestBase { + private ImeVisibilityStateComputer mComputer; + private int mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; + + @Before + public void setUp() throws RemoteException { + super.setUp(); + ImeVisibilityStateComputer.Injector injector = new ImeVisibilityStateComputer.Injector() { + @Override + public WindowManagerInternal getWmService() { + return mMockWindowManagerInternal; + } + + @Override + public ImeDisplayValidator getImeValidator() { + return displayId -> mImeDisplayPolicy; + } + }; + mComputer = new ImeVisibilityStateComputer(mInputMethodManagerService, injector); + } + + @Test + public void testRequestImeVisibility_showImplicit() { + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEdiorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isTrue(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } + + @Test + public void testRequestImeVisibility_showExplicit() { + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(null, 0 /* show explicit */); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEdiorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isTrue(); + + assertThat(mComputer.mRequestedShowExplicitly).isTrue(); + } + + @Test + public void testRequestImeVisibility_showImplicit_a11yNoImePolicy() { + // Precondition: set AccessibilityService#SHOW_MODE_HIDDEN policy + mComputer.getImePolicy().setA11yRequestNoSoftKeyboard(SHOW_MODE_HIDDEN); + + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEdiorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } + + @Test + public void testRequestImeVisibility_showImplicit_imeHiddenPolicy() { + // Precondition: set IME hidden display policy before calling showSoftInput + mComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); + + initImeTargetWindowState(mWindowToken); + boolean res = mComputer.onImeShowFlags(null, InputMethodManager.SHOW_IMPLICIT); + mComputer.requestImeVisibility(mWindowToken, res); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEdiorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + + assertThat(mComputer.mRequestedShowExplicitly).isFalse(); + } + + @Test + public void testRequestImeVisibility_hideNotAlways() { + // Precondition: ensure IME has shown before hiding request. + mComputer.setInputShown(true); + + initImeTargetWindowState(mWindowToken); + assertThat(mComputer.canHideIme(null, InputMethodManager.HIDE_NOT_ALWAYS)).isTrue(); + mComputer.requestImeVisibility(mWindowToken, false); + + final ImeTargetWindowState state = mComputer.getWindowStateOrNull(mWindowToken); + assertThat(state).isNotNull(); + assertThat(state.hasEdiorFocused()).isTrue(); + assertThat(state.getSoftInputModeState()).isEqualTo(SOFT_INPUT_STATE_UNCHANGED); + assertThat(state.isRequestedImeVisible()).isFalse(); + } + + @Test + public void testComputeImeDisplayId() { + final ImeTargetWindowState state = mComputer.getOrCreateWindowState(mWindowToken); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_LOCAL; + mComputer.computeImeDisplayId(state, DEFAULT_DISPLAY); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(DEFAULT_DISPLAY); + + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(10); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_HIDE; + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isTrue(); + assertThat(state.getImeDisplayId()).isEqualTo(INVALID_DISPLAY); + + mImeDisplayPolicy = DISPLAY_IME_POLICY_FALLBACK_DISPLAY; + mComputer.computeImeDisplayId(state, 10 /* displayId */); + assertThat(mComputer.getImePolicy().isImeHiddenByDisplayPolicy()).isFalse(); + assertThat(state.getImeDisplayId()).isEqualTo(FALLBACK_DISPLAY_ID); + } + + private void initImeTargetWindowState(IBinder windowToken) { + final ImeTargetWindowState state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNCHANGED, + 0, true, true, true); + mComputer.setWindowState(windowToken, state); + } +} diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java index 640bde330cee..804bb495bafe 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java @@ -26,6 +26,7 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.eq; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -200,9 +201,8 @@ public class InputMethodManagerServiceTestBase { "TestServiceThread", Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */ false); - mInputMethodManagerService = - new InputMethodManagerService( - mContext, mServiceThread, mMockInputMethodBindingController); + mInputMethodManagerService = new InputMethodManagerService(mContext, mServiceThread, + mMockInputMethodBindingController); // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of // InputMethodManagerService, which is closer to the real situation. @@ -239,12 +239,17 @@ public class InputMethodManagerServiceTestBase { protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput) throws RemoteException { + verifyShowSoftInput(setVisible, showSoftInput, anyInt()); + } + + protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput, int showFlags) + throws RemoteException { synchronized (ImfLock.class) { verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0)) .setCurrentMethodVisible(); } verify(mMockInputMethod, times(showSoftInput ? 1 : 0)) - .showSoftInput(any(), any(), anyInt(), any()); + .showSoftInput(any(), any(), eq(showFlags), any()); } protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput) |