summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Ming-Shin Lu <lumark@google.com> 2023-01-13 05:22:18 +0000
committer Android (Google) Code Review <android-gerrit@google.com> 2023-01-13 05:22:18 +0000
commitaabc5676c3822f12f1e63e311c86abe9de34bc3b (patch)
tree73fafe3ce2df6b2d5f115908ce5aab3ef53930b7
parent8a05fe90d857021f6557c4aa8287636a7396a3ea (diff)
parentc25f3681f3b987821ec685512c331cf7bde72ace (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
-rw-r--r--core/proto/android/server/inputmethod/inputmethodmanagerservice.proto2
-rw-r--r--services/core/java/com/android/server/inputmethod/DefaultImeVisibilityApplier.java21
-rw-r--r--services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java277
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodBindingController.java2
-rw-r--r--services/core/java/com/android/server/inputmethod/InputMethodManagerService.java262
-rw-r--r--services/core/java/com/android/server/wm/ActivityRecord.java9
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java6
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java116
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/ImeVisibilityStateComputerTest.java185
-rw-r--r--services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java13
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)