1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.inputmethod; 18 19 import static android.accessibilityservice.AccessibilityService.SHOW_MODE_HIDDEN; 20 import static android.server.inputmethod.InputMethodManagerServiceProto.ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD; 21 import static android.server.inputmethod.InputMethodManagerServiceProto.INPUT_SHOWN; 22 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_EXPLICITLY_REQUESTED; 23 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_FORCED; 24 import static android.view.Display.DEFAULT_DISPLAY; 25 import static android.view.Display.INVALID_DISPLAY; 26 import static android.view.MotionEvent.TOOL_TYPE_UNKNOWN; 27 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION; 28 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE; 29 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED; 30 import static android.view.WindowManager.LayoutParams.SoftInputModeFlags; 31 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; 32 33 import static com.android.internal.inputmethod.InputMethodDebug.softInputModeToString; 34 import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS; 35 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS; 36 import static com.android.server.inputmethod.InputMethodManagerService.computeImeDisplayIdForTarget; 37 38 import android.accessibilityservice.AccessibilityService; 39 import android.annotation.IntDef; 40 import android.annotation.NonNull; 41 import android.content.res.Configuration; 42 import android.os.Binder; 43 import android.os.IBinder; 44 import android.util.PrintWriterPrinter; 45 import android.util.Printer; 46 import android.util.Slog; 47 import android.util.proto.ProtoOutputStream; 48 import android.view.MotionEvent; 49 import android.view.WindowManager; 50 import android.view.inputmethod.ImeTracker; 51 import android.view.inputmethod.InputMethod; 52 import android.view.inputmethod.InputMethodManager; 53 54 import com.android.internal.annotations.VisibleForTesting; 55 import com.android.internal.inputmethod.SoftInputShowHideReason; 56 import com.android.server.LocalServices; 57 import com.android.server.wm.ImeTargetChangeListener; 58 import com.android.server.wm.WindowManagerInternal; 59 60 import java.io.PrintWriter; 61 import java.util.WeakHashMap; 62 63 /** 64 * A computer used by {@link InputMethodManagerService} that computes the IME visibility state 65 * according the given {@link ImeTargetWindowState} from the focused window or the app requested IME 66 * visibility from {@link InputMethodManager}. 67 */ 68 public final class ImeVisibilityStateComputer { 69 70 private static final String TAG = "ImeVisibilityStateComputer"; 71 72 private static final boolean DEBUG = InputMethodManagerService.DEBUG; 73 74 private final InputMethodManagerService mService; 75 private final WindowManagerInternal mWindowManagerInternal; 76 77 final InputMethodManagerService.ImeDisplayValidator mImeDisplayValidator; 78 79 /** 80 * A map used to track the requested IME target window and its state. The key represents the 81 * token of the window and the value is the corresponding IME window state. 82 */ 83 private final WeakHashMap<IBinder, ImeTargetWindowState> mRequestWindowStateMap = 84 new WeakHashMap<>(); 85 86 /** 87 * Set if IME was explicitly told to show the input method. 88 * 89 * @see InputMethodManager#SHOW_IMPLICIT that we set the value is {@code false}. 90 * @see InputMethodManager#HIDE_IMPLICIT_ONLY that system will not hide IME when the value is 91 * {@code true}. 92 */ 93 boolean mRequestedShowExplicitly; 94 95 /** 96 * Set if we were forced to be shown. 97 * 98 * @see InputMethodManager#SHOW_FORCED 99 * @see InputMethodManager#HIDE_NOT_ALWAYS 100 */ 101 boolean mShowForced; 102 103 /** 104 * Set if we last told the input method to show itself. 105 */ 106 private boolean mInputShown; 107 108 /** 109 * Set if we called 110 * {@link com.android.server.wm.ImeTargetVisibilityPolicy#showImeScreenshot(IBinder, int)}. 111 */ 112 private boolean mRequestedImeScreenshot; 113 114 /** The window token of the current visible IME layering target overlay. */ 115 private IBinder mCurVisibleImeLayeringOverlay; 116 117 /** The window token of the current visible IME input target. */ 118 private IBinder mCurVisibleImeInputTarget; 119 120 /** Represent the invalid IME visibility state */ 121 public static final int STATE_INVALID = -1; 122 123 /** State to handle hiding the IME window requested by the app. */ 124 public static final int STATE_HIDE_IME = 0; 125 126 /** State to handle showing the IME window requested by the app. */ 127 public static final int STATE_SHOW_IME = 1; 128 129 /** State to handle showing the IME window with making the overlay window above it. */ 130 public static final int STATE_SHOW_IME_ABOVE_OVERLAY = 2; 131 132 /** State to handle showing the IME window with making the overlay window behind it. */ 133 public static final int STATE_SHOW_IME_BEHIND_OVERLAY = 3; 134 135 /** State to handle showing an IME preview surface during the app was loosing the IME focus */ 136 public static final int STATE_SHOW_IME_SNAPSHOT = 4; 137 138 public static final int STATE_HIDE_IME_EXPLICIT = 5; 139 140 public static final int STATE_HIDE_IME_NOT_ALWAYS = 6; 141 142 public static final int STATE_SHOW_IME_IMPLICIT = 7; 143 144 /** State to handle removing an IME preview surface when necessary. */ 145 public static final int STATE_REMOVE_IME_SNAPSHOT = 8; 146 147 @IntDef({ 148 STATE_INVALID, 149 STATE_HIDE_IME, 150 STATE_SHOW_IME, 151 STATE_SHOW_IME_ABOVE_OVERLAY, 152 STATE_SHOW_IME_BEHIND_OVERLAY, 153 STATE_SHOW_IME_SNAPSHOT, 154 STATE_HIDE_IME_EXPLICIT, 155 STATE_HIDE_IME_NOT_ALWAYS, 156 STATE_SHOW_IME_IMPLICIT, 157 STATE_REMOVE_IME_SNAPSHOT, 158 }) 159 @interface VisibilityState {} 160 161 /** 162 * The policy to configure the IME visibility. 163 */ 164 private final ImeVisibilityPolicy mPolicy; 165 ImeVisibilityStateComputer(@onNull InputMethodManagerService service)166 public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service) { 167 this(service, 168 LocalServices.getService(WindowManagerInternal.class), 169 LocalServices.getService(WindowManagerInternal.class)::getDisplayImePolicy, 170 new ImeVisibilityPolicy()); 171 } 172 173 @VisibleForTesting ImeVisibilityStateComputer(@onNull InputMethodManagerService service, @NonNull Injector injector)174 public ImeVisibilityStateComputer(@NonNull InputMethodManagerService service, 175 @NonNull Injector injector) { 176 this(service, injector.getWmService(), injector.getImeValidator(), 177 new ImeVisibilityPolicy()); 178 } 179 180 interface Injector { getWmService()181 default WindowManagerInternal getWmService() { 182 return null; 183 } 184 getImeValidator()185 default InputMethodManagerService.ImeDisplayValidator getImeValidator() { 186 return null; 187 } 188 } 189 ImeVisibilityStateComputer(InputMethodManagerService service, WindowManagerInternal wmService, InputMethodManagerService.ImeDisplayValidator imeDisplayValidator, ImeVisibilityPolicy imePolicy)190 private ImeVisibilityStateComputer(InputMethodManagerService service, 191 WindowManagerInternal wmService, 192 InputMethodManagerService.ImeDisplayValidator imeDisplayValidator, 193 ImeVisibilityPolicy imePolicy) { 194 mService = service; 195 mWindowManagerInternal = wmService; 196 mImeDisplayValidator = imeDisplayValidator; 197 mPolicy = imePolicy; 198 mWindowManagerInternal.setInputMethodTargetChangeListener(new ImeTargetChangeListener() { 199 @Override 200 public void onImeTargetOverlayVisibilityChanged(IBinder overlayWindowToken, 201 @WindowManager.LayoutParams.WindowType int windowType, boolean visible, 202 boolean removed) { 203 mCurVisibleImeLayeringOverlay = 204 // Ignoring the starting window since it's ok to cover the IME target 205 // window in temporary without affecting the IME visibility. 206 (visible && !removed && windowType != TYPE_APPLICATION_STARTING) 207 ? overlayWindowToken : null; 208 } 209 210 @Override 211 public void onImeInputTargetVisibilityChanged(IBinder imeInputTarget, 212 boolean visibleRequested, boolean removed) { 213 if (mCurVisibleImeInputTarget == imeInputTarget && (!visibleRequested || removed) 214 && mCurVisibleImeLayeringOverlay != null) { 215 mService.onApplyImeVisibilityFromComputer(imeInputTarget, 216 new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, 217 SoftInputShowHideReason.HIDE_WHEN_INPUT_TARGET_INVISIBLE)); 218 } 219 mCurVisibleImeInputTarget = (visibleRequested && !removed) ? imeInputTarget : null; 220 } 221 }); 222 } 223 224 /** 225 * Called when {@link InputMethodManagerService} is processing the show IME request. 226 * 227 * @param statsToken The token for tracking this show request. 228 * @return {@code true} when the show request can proceed. 229 */ onImeShowFlags(@onNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int showFlags)230 boolean onImeShowFlags(@NonNull ImeTracker.Token statsToken, 231 @InputMethodManager.ShowFlags int showFlags) { 232 if (mPolicy.mA11yRequestingNoSoftKeyboard || mPolicy.mImeHiddenByDisplayPolicy) { 233 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); 234 return false; 235 } 236 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_ACCESSIBILITY); 237 // We only "set" the state corresponding to the flags, as this will be reset 238 // in clearImeShowFlags during a hide request. 239 // Thus, we keep the strongest values set (e.g. an implicit show right after 240 // an explicit show will still be considered explicit, likewise for forced). 241 if ((showFlags & InputMethodManager.SHOW_FORCED) != 0) { 242 mRequestedShowExplicitly = true; 243 mShowForced = true; 244 } else if ((showFlags & InputMethodManager.SHOW_IMPLICIT) == 0) { 245 mRequestedShowExplicitly = true; 246 } 247 return true; 248 } 249 250 /** 251 * Called when {@link InputMethodManagerService} is processing the hide IME request. 252 * 253 * @param statsToken The token for tracking this hide request. 254 * @return {@code true} when the hide request can proceed. 255 */ canHideIme(@onNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int hideFlags)256 boolean canHideIme(@NonNull ImeTracker.Token statsToken, 257 @InputMethodManager.HideFlags int hideFlags) { 258 if ((hideFlags & InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 259 && (mRequestedShowExplicitly || mShowForced)) { 260 if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); 261 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_IMPLICIT); 262 return false; 263 } 264 if (mShowForced && (hideFlags & InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 265 if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); 266 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS); 267 return false; 268 } 269 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HIDE_NOT_ALWAYS); 270 return true; 271 } 272 273 /** 274 * Returns the show flags for IME. This translates from {@link InputMethodManager.ShowFlags} 275 * to {@link InputMethod.ShowFlags}. 276 */ 277 @InputMethod.ShowFlags getShowFlagsForInputMethodServiceOnly()278 int getShowFlagsForInputMethodServiceOnly() { 279 int flags = 0; 280 if (mShowForced) { 281 flags |= InputMethod.SHOW_FORCED | InputMethod.SHOW_EXPLICIT; 282 } else if (mRequestedShowExplicitly) { 283 flags |= InputMethod.SHOW_EXPLICIT; 284 } 285 return flags; 286 } 287 288 /** 289 * Returns the show flags for IMM. This translates from {@link InputMethod.ShowFlags} 290 * to {@link InputMethodManager.ShowFlags}. 291 */ 292 @InputMethodManager.ShowFlags getShowFlags()293 int getShowFlags() { 294 int flags = 0; 295 if (mShowForced) { 296 flags |= InputMethodManager.SHOW_FORCED; 297 } else if (!mRequestedShowExplicitly) { 298 flags |= InputMethodManager.SHOW_IMPLICIT; 299 } 300 return flags; 301 } 302 clearImeShowFlags()303 void clearImeShowFlags() { 304 mRequestedShowExplicitly = false; 305 mShowForced = false; 306 mInputShown = false; 307 } 308 computeImeDisplayId(@onNull ImeTargetWindowState state, int displayId)309 int computeImeDisplayId(@NonNull ImeTargetWindowState state, int displayId) { 310 final int displayToShowIme = computeImeDisplayIdForTarget(displayId, mImeDisplayValidator); 311 state.setImeDisplayId(displayToShowIme); 312 final boolean imeHiddenByPolicy = displayToShowIme == INVALID_DISPLAY; 313 mPolicy.setImeHiddenByDisplayPolicy(imeHiddenByPolicy); 314 return displayToShowIme; 315 } 316 317 /** 318 * Request to show/hide IME from the given window. 319 * 320 * @param windowToken The window which requests to show/hide IME. 321 * @param showIme {@code true} means to show IME, {@code false} otherwise. 322 * Note that in the computer will take this option to compute the 323 * visibility state, it could be {@link #STATE_SHOW_IME} or 324 * {@link #STATE_HIDE_IME}. 325 */ requestImeVisibility(IBinder windowToken, boolean showIme)326 void requestImeVisibility(IBinder windowToken, boolean showIme) { 327 ImeTargetWindowState state = getOrCreateWindowState(windowToken); 328 if (!mPolicy.mPendingA11yRequestingHideKeyboard) { 329 state.setRequestedImeVisible(showIme); 330 } else { 331 // As A11y requests no IME is just a temporary, so we don't change the requested IME 332 // visible in case the last visibility state goes wrong after leaving from the a11y 333 // policy. 334 mPolicy.mPendingA11yRequestingHideKeyboard = false; 335 } 336 // create a placeholder token for IMS so that IMS cannot inject windows into client app. 337 state.setRequestImeToken(new Binder()); 338 setWindowStateInner(windowToken, state); 339 } 340 getOrCreateWindowState(IBinder windowToken)341 ImeTargetWindowState getOrCreateWindowState(IBinder windowToken) { 342 ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); 343 if (state == null) { 344 state = new ImeTargetWindowState(SOFT_INPUT_STATE_UNSPECIFIED, 0, false, false, false); 345 } 346 return state; 347 } 348 getWindowStateOrNull(IBinder windowToken)349 ImeTargetWindowState getWindowStateOrNull(IBinder windowToken) { 350 ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); 351 return state; 352 } 353 setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState)354 void setWindowState(IBinder windowToken, @NonNull ImeTargetWindowState newState) { 355 final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); 356 if (state != null && newState.hasEditorFocused() 357 && newState.mToolType != MotionEvent.TOOL_TYPE_STYLUS) { 358 // Inherit the last requested IME visible state when the target window is still 359 // focused with an editor. 360 newState.setRequestedImeVisible(state.mRequestedImeVisible); 361 } 362 setWindowStateInner(windowToken, newState); 363 } 364 setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState)365 private void setWindowStateInner(IBinder windowToken, @NonNull ImeTargetWindowState newState) { 366 if (DEBUG) Slog.d(TAG, "setWindowStateInner, windowToken=" + windowToken 367 + ", state=" + newState); 368 mRequestWindowStateMap.put(windowToken, newState); 369 } 370 371 static class ImeVisibilityResult { 372 private final @VisibilityState int mState; 373 private final @SoftInputShowHideReason int mReason; 374 ImeVisibilityResult(@isibilityState int state, @SoftInputShowHideReason int reason)375 ImeVisibilityResult(@VisibilityState int state, @SoftInputShowHideReason int reason) { 376 mState = state; 377 mReason = reason; 378 } 379 getState()380 @VisibilityState int getState() { 381 return mState; 382 } 383 getReason()384 @SoftInputShowHideReason int getReason() { 385 return mReason; 386 } 387 } 388 computeState(ImeTargetWindowState state, boolean allowVisible)389 ImeVisibilityResult computeState(ImeTargetWindowState state, boolean allowVisible) { 390 // TODO: Output the request IME visibility state according to the requested window state 391 final int softInputVisibility = state.mSoftInputModeState & SOFT_INPUT_MASK_STATE; 392 // Should we auto-show the IME even if the caller has not 393 // specified what should be done with it? 394 // We only do this automatically if the window can resize 395 // to accommodate the IME (so what the user sees will give 396 // them good context without input information being obscured 397 // by the IME) or if running on a large screen where there 398 // is more room for the target window + IME. 399 final boolean doAutoShow = 400 (state.mSoftInputModeState & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 401 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 402 || mService.mRes.getConfiguration().isLayoutSizeAtLeast( 403 Configuration.SCREENLAYOUT_SIZE_LARGE); 404 final boolean isForwardNavigation = (state.mSoftInputModeState 405 & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0; 406 407 // We shows the IME when the system allows the IME focused target window to restore the 408 // IME visibility (e.g. switching to the app task when last time the IME is visible). 409 // Note that we don't restore IME visibility for some cases (e.g. when the soft input 410 // state is ALWAYS_HIDDEN or STATE_HIDDEN with forward navigation). 411 // Because the app might leverage these flags to hide soft-keyboard with showing their own 412 // UI for input. 413 if (state.hasEditorFocused() && shouldRestoreImeVisibility(state)) { 414 if (DEBUG) Slog.v(TAG, "Will show input to restore visibility"); 415 // Inherit the last requested IME visible state when the target window is still 416 // focused with an editor. 417 state.setRequestedImeVisible(true); 418 setWindowStateInner(getWindowTokenFrom(state), state); 419 return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, 420 SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY); 421 } 422 423 switch (softInputVisibility) { 424 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 425 if (state.hasImeFocusChanged() && (!state.hasEditorFocused() || !doAutoShow)) { 426 if (WindowManager.LayoutParams.mayUseInputMethod(state.getWindowFlags())) { 427 // There is no focus view, and this window will 428 // be behind any soft input window, so hide the 429 // soft input window if it is shown. 430 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 431 return new ImeVisibilityResult(STATE_HIDE_IME_NOT_ALWAYS, 432 SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); 433 } 434 } else if (state.hasEditorFocused() && doAutoShow && isForwardNavigation) { 435 // There is a focus view, and we are navigating forward 436 // into the window, so show the input window for the user. 437 // We only do this automatically if the window can resize 438 // to accommodate the IME (so what the user sees will give 439 // them good context without input information being obscured 440 // by the IME) or if running on a large screen where there 441 // is more room for the target window + IME. 442 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 443 return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, 444 SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); 445 } 446 break; 447 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 448 // Do nothing but preserving the last IME requested visibility state. 449 final ImeTargetWindowState lastState = 450 getWindowStateOrNull(mService.mLastImeTargetWindow); 451 if (lastState != null) { 452 state.setRequestedImeVisible(lastState.mRequestedImeVisible); 453 } 454 break; 455 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 456 if (isForwardNavigation) { 457 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 458 return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, 459 SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); 460 } 461 break; 462 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 463 if (state.hasImeFocusChanged()) { 464 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 465 return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, 466 SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); 467 } 468 break; 469 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 470 if (isForwardNavigation) { 471 if (allowVisible) { 472 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 473 return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, 474 SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); 475 } else { 476 Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" 477 + " there is no focused view that also returns true from" 478 + " View#onCheckIsTextEditor()"); 479 } 480 } 481 break; 482 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 483 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 484 if (allowVisible) { 485 if (state.hasImeFocusChanged()) { 486 return new ImeVisibilityResult(STATE_SHOW_IME_IMPLICIT, 487 SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); 488 } 489 } else { 490 Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" 491 + " there is no focused view that also returns true from" 492 + " View#onCheckIsTextEditor()"); 493 } 494 break; 495 } 496 497 if (!state.hasImeFocusChanged()) { 498 // On previous platforms, when Dialogs re-gained focus, the Activity behind 499 // would briefly gain focus first, and dismiss the IME. 500 // On R that behavior has been fixed, but unfortunately apps have come 501 // to rely on this behavior to hide the IME when the editor no longer has focus 502 // To maintain compatibility, we are now hiding the IME when we don't have 503 // an editor upon refocusing a window. 504 if (state.isStartInputByGainFocus()) { 505 if (DEBUG) Slog.v(TAG, "Same window without editor will hide input"); 506 return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, 507 SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR); 508 } 509 } 510 if (!state.hasEditorFocused() && mInputShown && state.isStartInputByGainFocus() 511 && mService.mInputMethodDeviceConfigs.shouldHideImeWhenNoEditorFocus()) { 512 // Hide the soft-keyboard when the system do nothing for softInputModeState 513 // of the window being gained focus without an editor. This behavior benefits 514 // to resolve some unexpected IME visible cases while that window with following 515 // configurations being switched from an IME shown window: 516 // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor 517 // 2) SOFT_INPUT_STATE_VISIBLE state without an editor 518 // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor 519 if (DEBUG) Slog.v(TAG, "Window without editor will hide input"); 520 return new ImeVisibilityResult(STATE_HIDE_IME_EXPLICIT, 521 SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR); 522 } 523 return null; 524 } 525 526 @VisibleForTesting onInteractiveChanged(IBinder windowToken, boolean interactive)527 ImeVisibilityResult onInteractiveChanged(IBinder windowToken, boolean interactive) { 528 final ImeTargetWindowState state = getWindowStateOrNull(windowToken); 529 if (state != null && state.isRequestedImeVisible() && mInputShown && !interactive) { 530 mRequestedImeScreenshot = true; 531 return new ImeVisibilityResult(STATE_SHOW_IME_SNAPSHOT, SHOW_IME_SCREENSHOT_FROM_IMMS); 532 } 533 if (interactive && mRequestedImeScreenshot) { 534 mRequestedImeScreenshot = false; 535 return new ImeVisibilityResult(STATE_REMOVE_IME_SNAPSHOT, 536 REMOVE_IME_SCREENSHOT_FROM_IMMS); 537 } 538 return null; 539 } 540 getWindowTokenFrom(IBinder requestImeToken)541 IBinder getWindowTokenFrom(IBinder requestImeToken) { 542 for (IBinder windowToken : mRequestWindowStateMap.keySet()) { 543 final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); 544 if (state.getRequestImeToken() == requestImeToken) { 545 return windowToken; 546 } 547 } 548 // Fallback to the focused window for some edge cases (e.g. relaunching the activity) 549 return mService.mCurFocusedWindow; 550 } 551 getWindowTokenFrom(ImeTargetWindowState windowState)552 IBinder getWindowTokenFrom(ImeTargetWindowState windowState) { 553 for (IBinder windowToken : mRequestWindowStateMap.keySet()) { 554 final ImeTargetWindowState state = mRequestWindowStateMap.get(windowToken); 555 if (state == windowState) { 556 return windowToken; 557 } 558 } 559 return null; 560 } 561 shouldRestoreImeVisibility(@onNull ImeTargetWindowState state)562 boolean shouldRestoreImeVisibility(@NonNull ImeTargetWindowState state) { 563 final int softInputMode = state.getSoftInputModeState(); 564 switch (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 565 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 566 return false; 567 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 568 if ((softInputMode & SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 569 return false; 570 } 571 } 572 return mWindowManagerInternal.shouldRestoreImeVisibility(getWindowTokenFrom(state)); 573 } 574 isInputShown()575 boolean isInputShown() { 576 return mInputShown; 577 } 578 setInputShown(boolean inputShown)579 void setInputShown(boolean inputShown) { 580 mInputShown = inputShown; 581 } 582 dumpDebug(ProtoOutputStream proto, long fieldId)583 void dumpDebug(ProtoOutputStream proto, long fieldId) { 584 proto.write(SHOW_EXPLICITLY_REQUESTED, mRequestedShowExplicitly); 585 proto.write(SHOW_FORCED, mShowForced); 586 proto.write(ACCESSIBILITY_REQUESTING_NO_SOFT_KEYBOARD, 587 mPolicy.isA11yRequestNoSoftKeyboard()); 588 proto.write(INPUT_SHOWN, mInputShown); 589 } 590 dump(PrintWriter pw)591 void dump(PrintWriter pw) { 592 final Printer p = new PrintWriterPrinter(pw); 593 p.println(" mRequestedShowExplicitly=" + mRequestedShowExplicitly 594 + " mShowForced=" + mShowForced); 595 p.println(" mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy()); 596 p.println(" mInputShown=" + mInputShown); 597 } 598 599 /** 600 * A settings class to manage all IME related visibility policies or settings. 601 * 602 * This is used for the visibility computer to manage and tell 603 * {@link InputMethodManagerService} if the requested IME visibility is valid from 604 * application call or the focus window. 605 */ 606 static class ImeVisibilityPolicy { 607 /** 608 * {@code true} if the Ime policy has been set to 609 * {@link WindowManager#DISPLAY_IME_POLICY_HIDE}. 610 * 611 * This prevents the IME from showing when it otherwise may have shown. 612 */ 613 private boolean mImeHiddenByDisplayPolicy; 614 615 /** 616 * Set when the accessibility service requests to hide IME by 617 * {@link AccessibilityService.SoftKeyboardController#setShowMode} 618 */ 619 private boolean mA11yRequestingNoSoftKeyboard; 620 621 /** 622 * Used when A11y request to hide IME temporary when receiving 623 * {@link AccessibilityService#SHOW_MODE_HIDDEN} from 624 * {@link android.provider.Settings.Secure#ACCESSIBILITY_SOFT_KEYBOARD_MODE} without 625 * changing the requested IME visible state. 626 */ 627 private boolean mPendingA11yRequestingHideKeyboard; 628 setImeHiddenByDisplayPolicy(boolean hideIme)629 void setImeHiddenByDisplayPolicy(boolean hideIme) { 630 mImeHiddenByDisplayPolicy = hideIme; 631 } 632 isImeHiddenByDisplayPolicy()633 boolean isImeHiddenByDisplayPolicy() { 634 return mImeHiddenByDisplayPolicy; 635 } 636 setA11yRequestNoSoftKeyboard(int keyboardShowMode)637 void setA11yRequestNoSoftKeyboard(int keyboardShowMode) { 638 mA11yRequestingNoSoftKeyboard = 639 (keyboardShowMode & AccessibilityService.SHOW_MODE_MASK) == SHOW_MODE_HIDDEN; 640 if (mA11yRequestingNoSoftKeyboard) { 641 mPendingA11yRequestingHideKeyboard = true; 642 } 643 } 644 isA11yRequestNoSoftKeyboard()645 boolean isA11yRequestNoSoftKeyboard() { 646 return mA11yRequestingNoSoftKeyboard; 647 } 648 } 649 getImePolicy()650 ImeVisibilityPolicy getImePolicy() { 651 return mPolicy; 652 } 653 654 /** 655 * A class that represents the current state of the IME target window. 656 */ 657 static class ImeTargetWindowState { 658 ImeTargetWindowState(@oftInputModeFlags int softInputModeState, int windowFlags, boolean imeFocusChanged, boolean hasFocusedEditor, boolean isStartInputByGainFocus)659 ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags, 660 boolean imeFocusChanged, boolean hasFocusedEditor, 661 boolean isStartInputByGainFocus) { 662 this(softInputModeState, windowFlags, imeFocusChanged, hasFocusedEditor, 663 isStartInputByGainFocus, TOOL_TYPE_UNKNOWN); 664 } 665 ImeTargetWindowState(@oftInputModeFlags int softInputModeState, int windowFlags, boolean imeFocusChanged, boolean hasFocusedEditor, boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType)666 ImeTargetWindowState(@SoftInputModeFlags int softInputModeState, int windowFlags, 667 boolean imeFocusChanged, boolean hasFocusedEditor, 668 boolean isStartInputByGainFocus, @MotionEvent.ToolType int toolType) { 669 mSoftInputModeState = softInputModeState; 670 mWindowFlags = windowFlags; 671 mImeFocusChanged = imeFocusChanged; 672 mHasFocusedEditor = hasFocusedEditor; 673 mIsStartInputByGainFocus = isStartInputByGainFocus; 674 mToolType = toolType; 675 } 676 677 /** 678 * Visibility state for this window. By default no state has been specified. 679 */ 680 private final @SoftInputModeFlags int mSoftInputModeState; 681 682 private final int mWindowFlags; 683 684 /** 685 * {@link MotionEvent#getToolType(int)} that was used to click editor. 686 */ 687 private final int mToolType; 688 689 /** 690 * {@code true} means the IME focus changed from the previous window, {@code false} 691 * otherwise. 692 */ 693 private final boolean mImeFocusChanged; 694 695 /** 696 * {@code true} when the window has focused an editor, {@code false} otherwise. 697 */ 698 private final boolean mHasFocusedEditor; 699 700 private final boolean mIsStartInputByGainFocus; 701 702 /** 703 * Set if the client has asked for the input method to be shown. 704 */ 705 private boolean mRequestedImeVisible; 706 707 /** 708 * A identifier for knowing the requester of {@link InputMethodManager#showSoftInput} or 709 * {@link InputMethodManager#hideSoftInputFromWindow}. 710 */ 711 private IBinder mRequestImeToken; 712 713 /** 714 * The IME target display id for which the latest startInput was called. 715 */ 716 private int mImeDisplayId = DEFAULT_DISPLAY; 717 hasImeFocusChanged()718 boolean hasImeFocusChanged() { 719 return mImeFocusChanged; 720 } 721 hasEditorFocused()722 boolean hasEditorFocused() { 723 return mHasFocusedEditor; 724 } 725 isStartInputByGainFocus()726 boolean isStartInputByGainFocus() { 727 return mIsStartInputByGainFocus; 728 } 729 getSoftInputModeState()730 int getSoftInputModeState() { 731 return mSoftInputModeState; 732 } 733 getWindowFlags()734 int getWindowFlags() { 735 return mWindowFlags; 736 } 737 getToolType()738 int getToolType() { 739 return mToolType; 740 } 741 setImeDisplayId(int imeDisplayId)742 private void setImeDisplayId(int imeDisplayId) { 743 mImeDisplayId = imeDisplayId; 744 } 745 getImeDisplayId()746 int getImeDisplayId() { 747 return mImeDisplayId; 748 } 749 setRequestedImeVisible(boolean requestedImeVisible)750 private void setRequestedImeVisible(boolean requestedImeVisible) { 751 mRequestedImeVisible = requestedImeVisible; 752 } 753 isRequestedImeVisible()754 boolean isRequestedImeVisible() { 755 return mRequestedImeVisible; 756 } 757 setRequestImeToken(IBinder token)758 void setRequestImeToken(IBinder token) { 759 mRequestImeToken = token; 760 } 761 getRequestImeToken()762 IBinder getRequestImeToken() { 763 return mRequestImeToken; 764 } 765 766 @Override toString()767 public String toString() { 768 return "ImeTargetWindowState{ imeToken " + mRequestImeToken 769 + " imeFocusChanged " + mImeFocusChanged 770 + " hasEditorFocused " + mHasFocusedEditor 771 + " requestedImeVisible " + mRequestedImeVisible 772 + " imeDisplayId " + mImeDisplayId 773 + " softInputModeState " + softInputModeToString(mSoftInputModeState) 774 + " isStartInputByGainFocus " + mIsStartInputByGainFocus 775 + "}"; 776 } 777 } 778 } 779