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