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