diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java index 4dbd82065a667..bc6038d9e17c1 100644 --- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java +++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java @@ -48,6 +48,7 @@ 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.ImeVisibilityStateComputer.STATE_HIDE_IME; import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; @@ -2360,6 +2361,28 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub } } + /** + * Called when {@link #resetCurrentMethodAndClientLocked(int)} invoked for clean-up states + * before unbinding the current method. + */ + @GuardedBy("ImfLock.class") + void onUnbindCurrentMethodByReset() { + final ImeTargetWindowState winState = mVisibilityStateComputer.getWindowStateOrNull( + mCurFocusedWindow); + if (winState != null && !winState.isRequestedImeVisible() + && !mVisibilityStateComputer.isInputShown()) { + // Normally, the focus window will apply the IME visibility state to + // WindowManager when the IME has applied it. But it would be too late when + // switching IMEs in between different users. (Since the focused IME will + // first unbind the service to switch to bind the next user of the IME + // service, that wouldn't make the attached IME token validity check in time) + // As a result, we have to notify WM to apply IME visibility before clearing the + // binding states in the first place. + mVisibilityApplier.applyImeVisibility(mCurFocusedWindow, mCurStatsToken, + STATE_HIDE_IME); + } + } + /** {@code true} when a {@link ClientState} has attached from starting the input connection. */ @GuardedBy("ImfLock.class") boolean hasAttachedClient() { @@ -2831,6 +2854,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub @GuardedBy("ImfLock.class") void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason) { setSelectedMethodIdLocked(null); + // Callback before clean-up binding states. + onUnbindCurrentMethodByReset(); mBindingController.unbindCurrentMethod(); unbindCurrentClientLocked(unbindClientReason); } diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java index a6ada4d772531..869497c28deff 100644 --- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java +++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/DefaultImeVisibilityApplierTest.java @@ -20,6 +20,7 @@ import static android.inputmethodservice.InputMethodService.IME_ACTIVE; import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE; import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SOFT_INPUT; +import static com.android.internal.inputmethod.SoftInputShowHideReason.HIDE_SWITCH_USER; 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; @@ -43,6 +44,7 @@ import android.view.inputmethod.InputMethodManager; import androidx.test.ext.junit.runners.AndroidJUnit4; +import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.internal.inputmethod.InputBindResult; import com.android.internal.inputmethod.StartInputFlags; import com.android.internal.inputmethod.StartInputReason; @@ -165,6 +167,29 @@ public class DefaultImeVisibilityApplierTest extends InputMethodManagerServiceTe verify(mMockImeTargetVisibilityPolicy).removeImeScreenshot(eq(Display.DEFAULT_DISPLAY)); } + @Test + public void testApplyImeVisibility_hideImeWhenUnbinding() { + mInputMethodManagerService.setAttachedClientForTesting(null); + startInputOrWindowGainedFocus(mWindowToken, SOFT_INPUT_STATE_ALWAYS_VISIBLE); + ExtendedMockito.spyOn(mVisibilityApplier); + + synchronized (ImfLock.class) { + // Simulate the system hides the IME when switching IME services in different users. + // (e.g. unbinding the IME from the current user to the profile user) + final int displayIdToShowIme = mInputMethodManagerService.getDisplayIdToShowImeLocked(); + mInputMethodManagerService.hideCurrentInputLocked(mWindowToken, null, 0, null, + HIDE_SWITCH_USER); + mInputMethodManagerService.onUnbindCurrentMethodByReset(); + + // Expects applyImeVisibility() -> hideIme() will be called to notify WM for syncing + // the IME hidden state. + verify(mVisibilityApplier).applyImeVisibility(eq(mWindowToken), any(), + eq(STATE_HIDE_IME)); + verify(mInputMethodManagerService.mWindowManagerInternal).hideIme( + eq(mWindowToken), eq(displayIdToShowIme), eq(null)); + } + } + private InputBindResult startInputOrWindowGainedFocus(IBinder windowToken, int softInputMode) { return mInputMethodManagerService.startInputOrWindowGainedFocus( StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,