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