• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.view.inputmethod.ImeTracker.DEBUG_IME_VISIBILITY;
20 
21 import static com.android.internal.inputmethod.SoftInputShowHideReason.REMOVE_IME_SCREENSHOT_FROM_IMMS;
22 import static com.android.internal.inputmethod.SoftInputShowHideReason.SHOW_IME_SCREENSHOT_FROM_IMMS;
23 import static com.android.server.EventLogTags.IMF_HIDE_IME;
24 import static com.android.server.EventLogTags.IMF_SHOW_IME;
25 import static com.android.server.inputmethod.ImeProtoLogGroup.IME_VISIBILITY_APPLIER_DEBUG;
26 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME;
27 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_EXPLICIT;
28 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME_NOT_ALWAYS;
29 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_REMOVE_IME_SNAPSHOT;
30 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME;
31 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_IMPLICIT;
32 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME_SNAPSHOT;
33 
34 import android.annotation.NonNull;
35 import android.annotation.Nullable;
36 import android.annotation.UserIdInt;
37 import android.os.IBinder;
38 import android.os.ResultReceiver;
39 import android.util.EventLog;
40 import android.view.MotionEvent;
41 import android.view.inputmethod.Flags;
42 import android.view.inputmethod.ImeTracker;
43 import android.view.inputmethod.InputMethod;
44 import android.view.inputmethod.InputMethodManager;
45 
46 import com.android.internal.annotations.GuardedBy;
47 import com.android.internal.inputmethod.InputMethodDebug;
48 import com.android.internal.inputmethod.SoftInputShowHideReason;
49 import com.android.internal.protolog.ProtoLog;
50 import com.android.server.LocalServices;
51 import com.android.server.wm.ImeTargetVisibilityPolicy;
52 import com.android.server.wm.WindowManagerInternal;
53 
54 import java.util.Objects;
55 
56 /**
57  * A stateless helper class for IME visibility operations like show/hide and update Z-ordering
58  * relative to the IME targeted window.
59  */
60 final class DefaultImeVisibilityApplier {
61 
62     static final String TAG = "DefaultImeVisibilityApplier";
63 
64     private InputMethodManagerService mService;
65 
66     private final WindowManagerInternal mWindowManagerInternal;
67 
68     @NonNull
69     private final ImeTargetVisibilityPolicy mImeTargetVisibilityPolicy;
70 
DefaultImeVisibilityApplier(InputMethodManagerService service)71     DefaultImeVisibilityApplier(InputMethodManagerService service) {
72         mService = service;
73         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
74         mImeTargetVisibilityPolicy = LocalServices.getService(ImeTargetVisibilityPolicy.class);
75     }
76 
77     /**
78      * Performs showing IME on top of the given window.
79      *
80      * @param showInputToken a token that represents the requester to show IME
81      * @param statsToken     the token tracking the current IME request
82      * @param resultReceiver if non-null, this will be called back to the caller when
83      *                       it has processed request to tell what it has done
84      * @param reason         yhe reason for requesting to show IME
85      * @param userId         the target user when performing show IME
86      */
87     @GuardedBy("ImfLock.class")
performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken, @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)88     void performShowIme(IBinder showInputToken, @NonNull ImeTracker.Token statsToken,
89             @InputMethod.ShowFlags int showFlags, ResultReceiver resultReceiver,
90             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
91         final var userData = mService.getUserData(userId);
92         final var bindingController = userData.mBindingController;
93         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
94         if (curMethod != null) {
95             ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
96                     "Calling %s.showSoftInput(%s, %s, %s) for reason: %s", curMethod,
97                     showInputToken, showFlags, resultReceiver,
98                     InputMethodDebug.softInputDisplayReasonToString(reason));
99             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
100             if (curMethod.showSoftInput(showInputToken, statsToken, showFlags, resultReceiver)) {
101                 if (DEBUG_IME_VISIBILITY) {
102                     EventLog.writeEvent(IMF_SHOW_IME,
103                             statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
104                             Objects.toString(userData.mImeBindingState.mFocusedWindow),
105                             InputMethodDebug.softInputDisplayReasonToString(reason),
106                             InputMethodDebug.softInputModeToString(
107                                     userData.mImeBindingState.mFocusedWindowSoftInputMode));
108                 }
109                 mService.onShowHideSoftInputRequested(true /* show */, showInputToken, reason,
110                         statsToken, userId);
111             }
112         }
113     }
114 
115     /**
116      * Performs hiding IME to the given window
117      *
118      * @param hideInputToken a token that represents the requester to hide IME
119      * @param statsToken     the token tracking the current IME request
120      * @param resultReceiver if non-null, this will be called back to the caller when
121      *                       it has processed request to tell what it has done
122      * @param reason         the reason for requesting to hide IME
123      * @param userId         the target user when performing hide IME
124      */
125     @GuardedBy("ImfLock.class")
performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)126     void performHideIme(IBinder hideInputToken, @NonNull ImeTracker.Token statsToken,
127             ResultReceiver resultReceiver, @SoftInputShowHideReason int reason,
128             @UserIdInt int userId) {
129         final var userData = mService.getUserData(userId);
130         final var bindingController = userData.mBindingController;
131         final IInputMethodInvoker curMethod = bindingController.getCurMethod();
132         if (curMethod != null) {
133             // The IME will report its visible state again after the following message finally
134             // delivered to the IME process as an IPC.  Hence the inconsistency between
135             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
136             // the final state.
137             ProtoLog.v(IME_VISIBILITY_APPLIER_DEBUG,
138                     "Calling %s.hideSoftInput(0, %s, %s) for reason: %s", curMethod, hideInputToken,
139                     resultReceiver, InputMethodDebug.softInputDisplayReasonToString(reason));
140             // TODO(b/192412909): Check if we can always call onShowHideSoftInputRequested() or not.
141             if (curMethod.hideSoftInput(hideInputToken, statsToken, 0, resultReceiver)) {
142                 if (DEBUG_IME_VISIBILITY) {
143                     EventLog.writeEvent(IMF_HIDE_IME,
144                             statsToken != null ? statsToken.getTag() : ImeTracker.TOKEN_NONE,
145                             Objects.toString(userData.mImeBindingState.mFocusedWindow),
146                             InputMethodDebug.softInputDisplayReasonToString(reason),
147                             InputMethodDebug.softInputModeToString(
148                                     userData.mImeBindingState.mFocusedWindowSoftInputMode));
149                 }
150                 mService.onShowHideSoftInputRequested(false /* show */, hideInputToken, reason,
151                         statsToken, userId);
152             }
153         }
154     }
155 
156     /**
157      * Applies the IME visibility from {@link android.inputmethodservice.InputMethodService} with
158      * according to the given visibility state.
159      *
160      * @param windowToken the token of a window for applying the IME visibility
161      * @param statsToken  the token tracking the current IME request
162      * @param state       the new IME visibility state for the applier to handle
163      * @param reason      one of {@link SoftInputShowHideReason}
164      * @param userId      the target user when applying the IME visibility state
165      */
166     @GuardedBy("ImfLock.class")
applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken, @ImeVisibilityStateComputer.VisibilityState int state, @SoftInputShowHideReason int reason, @UserIdInt int userId)167     void applyImeVisibility(IBinder windowToken, @Nullable ImeTracker.Token statsToken,
168             @ImeVisibilityStateComputer.VisibilityState int state,
169             @SoftInputShowHideReason int reason, @UserIdInt int userId) {
170         final var userData = mService.getUserData(userId);
171         final var bindingController = userData.mBindingController;
172         final int displayIdToShowIme = bindingController.getDisplayIdToShowIme();
173         switch (state) {
174             case STATE_SHOW_IME:
175                 if (!Flags.refactorInsetsController()) {
176                     ImeTracker.forLogging().onProgress(statsToken,
177                             ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
178                     // Send to window manager to show IME after IME layout finishes.
179                     mWindowManagerInternal.showImePostLayout(windowToken, statsToken);
180                 }
181                 break;
182             case STATE_HIDE_IME:
183                 if (!Flags.refactorInsetsController()) {
184                     if (userData.mCurClient != null) {
185                         ImeTracker.forLogging().onProgress(statsToken,
186                                 ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
187                         // IMMS only knows of focused window, not the actual IME target.
188                         // e.g. it isn't aware of any window that has both
189                         // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
190                         // Send it to window manager to hide IME from the actual IME control target
191                         // of the target display.
192                         mWindowManagerInternal.hideIme(windowToken, displayIdToShowIme, statsToken);
193                     } else {
194                         ImeTracker.forLogging().onFailed(statsToken,
195                                 ImeTracker.PHASE_SERVER_APPLY_IME_VISIBILITY);
196                     }
197                 }
198                 break;
199             case STATE_HIDE_IME_EXPLICIT:
200                 if (Flags.refactorInsetsController()) {
201                     mService.setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
202                 } else {
203                     mService.hideCurrentInputLocked(windowToken, statsToken,
204                             0 /* flags */, null /* resultReceiver */, reason, userId);
205                 }
206                 break;
207             case STATE_HIDE_IME_NOT_ALWAYS:
208                 if (Flags.refactorInsetsController()) {
209                     mService.setImeVisibilityOnFocusedWindowClient(false, userData, statsToken);
210                 } else {
211                     mService.hideCurrentInputLocked(windowToken, statsToken,
212                             InputMethodManager.HIDE_NOT_ALWAYS, null /* resultReceiver */, reason,
213                             userId);
214                 }
215                 break;
216             case STATE_SHOW_IME_IMPLICIT:
217                 if (Flags.refactorInsetsController()) {
218                     // This can be triggered by IMMS#startInputOrWindowGainedFocus. We need to
219                     // set the requestedVisibleTypes in InsetsController first, before applying it.
220                     mService.setImeVisibilityOnFocusedWindowClient(true, userData, statsToken);
221                 } else {
222                     mService.showCurrentInputLocked(windowToken, statsToken,
223                             InputMethodManager.SHOW_IMPLICIT, MotionEvent.TOOL_TYPE_UNKNOWN,
224                             null /* resultReceiver */, reason, userId);
225                 }
226                 break;
227             case STATE_SHOW_IME_SNAPSHOT:
228                 showImeScreenshot(windowToken, displayIdToShowIme, userId);
229                 break;
230             case STATE_REMOVE_IME_SNAPSHOT:
231                 removeImeScreenshot(displayIdToShowIme, userId);
232                 break;
233             default:
234                 throw new IllegalArgumentException("Invalid IME visibility state: " + state);
235         }
236     }
237 
238     /**
239      * Shows the IME screenshot and attach it to the given IME target window.
240      *
241      * @param imeTarget   the token of a window to show the IME screenshot
242      * @param displayId   the unique id to identify the display
243      * @param userId      the target user when when showing the IME screenshot
244      * @return {@code true} if success, {@code false} otherwise
245      */
246     @GuardedBy("ImfLock.class")
showImeScreenshot(@onNull IBinder imeTarget, int displayId, @UserIdInt int userId)247     boolean showImeScreenshot(@NonNull IBinder imeTarget, int displayId,
248             @UserIdInt int userId) {
249         if (mImeTargetVisibilityPolicy.showImeScreenshot(imeTarget, displayId)) {
250             mService.onShowHideSoftInputRequested(false /* show */, imeTarget,
251                     SHOW_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId);
252             return true;
253         }
254         return false;
255     }
256 
257     /**
258      * Removes the IME screenshot on the given display.
259      *
260      * @param displayId the target display of showing IME screenshot
261      * @param userId    the target user of showing IME screenshot
262      * @return {@code true} if success, {@code false} otherwise
263      */
264     @GuardedBy("ImfLock.class")
removeImeScreenshot(int displayId, @UserIdInt int userId)265     boolean removeImeScreenshot(int displayId, @UserIdInt int userId) {
266         final var userData = mService.getUserData(userId);
267         if (mImeTargetVisibilityPolicy.removeImeScreenshot(displayId)) {
268             mService.onShowHideSoftInputRequested(false /* show */,
269                     userData.mImeBindingState.mFocusedWindow,
270                     REMOVE_IME_SCREENSHOT_FROM_IMMS, null /* statsToken */, userId);
271             return true;
272         }
273         return false;
274     }
275 }
276