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.content.Context.DEVICE_ID_DEFAULT; 20 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL; 21 import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL; 22 import static android.os.IServiceManager.DUMP_FLAG_PROTO; 23 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER; 24 import static android.os.UserManager.USER_TYPE_SYSTEM_HEADLESS; 25 import static android.server.inputmethod.InputMethodManagerServiceProto.BACK_DISPOSITION; 26 import static android.server.inputmethod.InputMethodManagerServiceProto.BOUND_TO_METHOD; 27 import static android.server.inputmethod.InputMethodManagerServiceProto.CONCURRENT_MULTI_USER_MODE_ENABLED; 28 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ATTRIBUTE; 29 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_CLIENT; 30 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE; 31 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_ID; 32 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_METHOD_ID; 33 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_SEQ; 34 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN; 35 import static android.server.inputmethod.InputMethodManagerServiceProto.CUR_TOKEN_DISPLAY_ID; 36 import static android.server.inputmethod.InputMethodManagerServiceProto.HAVE_CONNECTION; 37 import static android.server.inputmethod.InputMethodManagerServiceProto.IME_WINDOW_VISIBILITY; 38 import static android.server.inputmethod.InputMethodManagerServiceProto.IN_FULLSCREEN_MODE; 39 import static android.server.inputmethod.InputMethodManagerServiceProto.IS_INTERACTIVE; 40 import static android.server.inputmethod.InputMethodManagerServiceProto.LAST_IME_TARGET_WINDOW_NAME; 41 import static android.server.inputmethod.InputMethodManagerServiceProto.SHOW_IME_WITH_HARD_KEYBOARD; 42 import static android.server.inputmethod.InputMethodManagerServiceProto.SYSTEM_READY; 43 import static android.view.Display.DEFAULT_DISPLAY; 44 import static android.view.Display.INVALID_DISPLAY; 45 import static android.view.WindowManager.DISPLAY_IME_POLICY_HIDE; 46 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL; 47 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_OTHER; 48 import static android.view.inputmethod.ConnectionlessHandwritingCallback.CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED; 49 50 import static com.android.server.inputmethod.ImeProtoLogGroup.IMMS_DEBUG; 51 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeTargetWindowState; 52 import static com.android.server.inputmethod.ImeVisibilityStateComputer.ImeVisibilityResult; 53 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_HIDE_IME; 54 import static com.android.server.inputmethod.ImeVisibilityStateComputer.STATE_SHOW_IME; 55 import static com.android.server.inputmethod.InputMethodBindingController.TIME_TO_RECONNECT; 56 import static com.android.server.inputmethod.InputMethodSettings.INVALID_SUBTYPE_HASHCODE; 57 import static com.android.server.inputmethod.InputMethodSubtypeSwitchingController.MODE_AUTO; 58 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_INDEX; 59 import static com.android.server.inputmethod.InputMethodUtils.isSoftInputModeStateVisibleAllowed; 60 61 import static java.lang.annotation.RetentionPolicy.SOURCE; 62 63 import android.Manifest; 64 import android.annotation.AnyThread; 65 import android.annotation.BinderThread; 66 import android.annotation.DrawableRes; 67 import android.annotation.DurationMillisLong; 68 import android.annotation.IntDef; 69 import android.annotation.NonNull; 70 import android.annotation.Nullable; 71 import android.annotation.UiThread; 72 import android.annotation.UserIdInt; 73 import android.annotation.WorkerThread; 74 import android.app.ActivityManagerInternal; 75 import android.content.BroadcastReceiver; 76 import android.content.ComponentName; 77 import android.content.ContentProvider; 78 import android.content.Context; 79 import android.content.Intent; 80 import android.content.IntentFilter; 81 import android.content.pm.ApplicationInfo; 82 import android.content.pm.PackageManager; 83 import android.content.pm.PackageManagerInternal; 84 import android.content.pm.ResolveInfo; 85 import android.content.pm.ServiceInfo; 86 import android.content.pm.UserInfo; 87 import android.content.res.Resources; 88 import android.graphics.Region; 89 import android.hardware.display.DisplayManagerInternal; 90 import android.hardware.input.InputManager; 91 import android.inputmethodservice.InputMethodService; 92 import android.inputmethodservice.InputMethodService.BackDispositionMode; 93 import android.inputmethodservice.InputMethodService.ImeWindowVisibility; 94 import android.media.AudioManagerInternal; 95 import android.net.Uri; 96 import android.os.Binder; 97 import android.os.Debug; 98 import android.os.Handler; 99 import android.os.IBinder; 100 import android.os.LocaleList; 101 import android.os.Looper; 102 import android.os.Message; 103 import android.os.Process; 104 import android.os.RemoteException; 105 import android.os.ResultReceiver; 106 import android.os.ShellCallback; 107 import android.os.ShellCommand; 108 import android.os.SystemClock; 109 import android.os.Trace; 110 import android.os.UserHandle; 111 import android.os.UserManager; 112 import android.provider.Settings; 113 import android.text.TextUtils; 114 import android.util.ArrayMap; 115 import android.util.ArraySet; 116 import android.util.EventLog; 117 import android.util.IndentingPrintWriter; 118 import android.util.IntArray; 119 import android.util.Pair; 120 import android.util.PrintWriterPrinter; 121 import android.util.Printer; 122 import android.util.Slog; 123 import android.util.SparseArray; 124 import android.util.proto.ProtoOutputStream; 125 import android.view.Display; 126 import android.view.InputChannel; 127 import android.view.InputDevice; 128 import android.view.MotionEvent; 129 import android.view.WindowManager; 130 import android.view.WindowManager.DisplayImePolicy; 131 import android.view.WindowManager.LayoutParams; 132 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 133 import android.view.inputmethod.CursorAnchorInfo; 134 import android.view.inputmethod.EditorInfo; 135 import android.view.inputmethod.Flags; 136 import android.view.inputmethod.ImeTracker; 137 import android.view.inputmethod.InputConnection; 138 import android.view.inputmethod.InputMethod; 139 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceFileProto; 140 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto; 141 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceFileProto; 142 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodManagerServiceTraceProto; 143 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceFileProto; 144 import android.view.inputmethod.InputMethodEditorTraceProto.InputMethodServiceTraceProto; 145 import android.view.inputmethod.InputMethodInfo; 146 import android.view.inputmethod.InputMethodManager; 147 import android.view.inputmethod.InputMethodSubtype; 148 import android.window.ImeOnBackInvokedDispatcher; 149 150 import com.android.internal.annotations.GuardedBy; 151 import com.android.internal.annotations.VisibleForTesting; 152 import com.android.internal.content.PackageMonitor; 153 import com.android.internal.infra.AndroidFuture; 154 import com.android.internal.inputmethod.DirectBootAwareness; 155 import com.android.internal.inputmethod.IAccessibilityInputMethodSession; 156 import com.android.internal.inputmethod.IBooleanListener; 157 import com.android.internal.inputmethod.IConnectionlessHandwritingCallback; 158 import com.android.internal.inputmethod.IImeTracker; 159 import com.android.internal.inputmethod.IInputContentUriToken; 160 import com.android.internal.inputmethod.IInputMethod; 161 import com.android.internal.inputmethod.IInputMethodClient; 162 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; 163 import com.android.internal.inputmethod.IInputMethodSession; 164 import com.android.internal.inputmethod.IInputMethodSessionCallback; 165 import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection; 166 import com.android.internal.inputmethod.IRemoteInputConnection; 167 import com.android.internal.inputmethod.ImeTracing; 168 import com.android.internal.inputmethod.InlineSuggestionsRequestCallback; 169 import com.android.internal.inputmethod.InlineSuggestionsRequestInfo; 170 import com.android.internal.inputmethod.InputBindResult; 171 import com.android.internal.inputmethod.InputMethodDebug; 172 import com.android.internal.inputmethod.InputMethodInfoSafeList; 173 import com.android.internal.inputmethod.InputMethodNavButtonFlags; 174 import com.android.internal.inputmethod.InputMethodSubtypeHandle; 175 import com.android.internal.inputmethod.SoftInputShowHideReason; 176 import com.android.internal.inputmethod.StartInputFlags; 177 import com.android.internal.inputmethod.StartInputReason; 178 import com.android.internal.inputmethod.UnbindReason; 179 import com.android.internal.os.TransferPipe; 180 import com.android.internal.protolog.ProtoLog; 181 import com.android.internal.util.ArrayUtils; 182 import com.android.internal.util.CollectionUtils; 183 import com.android.internal.util.DumpUtils; 184 import com.android.internal.util.Preconditions; 185 import com.android.server.AccessibilityManagerInternal; 186 import com.android.server.EventLogTags; 187 import com.android.server.LocalServices; 188 import com.android.server.ServiceThread; 189 import com.android.server.SystemService; 190 import com.android.server.companion.virtual.VirtualDeviceManagerInternal; 191 import com.android.server.input.InputManagerInternal; 192 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener; 193 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; 194 import com.android.server.pm.UserManagerInternal; 195 import com.android.server.statusbar.StatusBarManagerInternal; 196 import com.android.server.utils.PriorityDump; 197 import com.android.server.wm.WindowManagerInternal; 198 199 import java.io.FileDescriptor; 200 import java.io.IOException; 201 import java.io.PrintWriter; 202 import java.lang.annotation.ElementType; 203 import java.lang.annotation.Retention; 204 import java.lang.annotation.Target; 205 import java.security.InvalidParameterException; 206 import java.util.ArrayList; 207 import java.util.Arrays; 208 import java.util.Collections; 209 import java.util.List; 210 import java.util.Objects; 211 import java.util.OptionalInt; 212 import java.util.WeakHashMap; 213 import java.util.concurrent.CopyOnWriteArrayList; 214 import java.util.concurrent.TimeUnit; 215 import java.util.function.Consumer; 216 import java.util.function.IntFunction; 217 218 /** 219 * This class provides a system service that manages input methods. 220 */ 221 public final class InputMethodManagerService implements IInputMethodManagerImpl.Callback, 222 ZeroJankProxy.Callback, Handler.Callback { 223 224 // Virtual device id for test. 225 private static final Integer VIRTUAL_STYLUS_ID_FOR_TEST = 999999; 226 static final boolean DEBUG = false; 227 static final String TAG = "InputMethodManagerService"; 228 public static final String PROTO_ARG = "--proto"; 229 230 /** 231 * Timeout in milliseconds in {@link #systemRunning()} to make sure that users are initialized 232 * in {@link Lifecycle#initializeUsersAsync(int[])}. 233 */ 234 @DurationMillisLong 235 private static final long SYSTEM_READY_USER_INIT_TIMEOUT = 3000; 236 237 @Retention(SOURCE) 238 @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE}) 239 private @interface ShellCommandResult { 240 int SUCCESS = 0; 241 int FAILURE = -1; 242 } 243 244 /** 245 * Indicates that the annotated field is shared by all the users. 246 * 247 * <p>See b/305849394 for details.</p> 248 */ 249 @Retention(SOURCE) 250 @Target({ElementType.FIELD}) 251 private @interface SharedByAllUsersField { 252 } 253 254 /** 255 * Indicates that the annotated field is not yet ready for concurrent multi-user support. 256 * 257 * <p>See b/305849394 for details.</p> 258 */ 259 @Retention(SOURCE) 260 @Target({ElementType.FIELD}) 261 private @interface MultiUserUnawareField { 262 } 263 264 private static final int MSG_HIDE_INPUT_METHOD = 1035; 265 private static final int MSG_REMOVE_IME_SURFACE = 1060; 266 private static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; 267 268 private static final int MSG_RESET_HANDWRITING = 1090; 269 private static final int MSG_START_HANDWRITING = 1100; 270 private static final int MSG_FINISH_HANDWRITING = 1110; 271 private static final int MSG_REMOVE_HANDWRITING_WINDOW = 1120; 272 273 private static final int MSG_PREPARE_HANDWRITING_DELEGATION = 1130; 274 275 private static final int MSG_SET_INTERACTIVE = 3030; 276 277 private static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; 278 279 private static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; 280 281 private static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; 282 283 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; 284 private static final String HANDLER_THREAD_NAME = "android.imms"; 285 private static final String PACKAGE_MONITOR_THREAD_NAME = "android.imms2"; 286 287 /** 288 * When set, {@link #startInputUncheckedLocked} will return 289 * {@link InputBindResult#NO_EDITOR} instead of starting an IME connection 290 * unless {@link StartInputFlags#IS_TEXT_EDITOR} is set. This behavior overrides 291 * {@link LayoutParams#SOFT_INPUT_STATE_VISIBLE SOFT_INPUT_STATE_VISIBLE} and 292 * {@link LayoutParams#SOFT_INPUT_STATE_ALWAYS_VISIBLE SOFT_INPUT_STATE_ALWAYS_VISIBLE} 293 * starting from {@link android.os.Build.VERSION_CODES#P}. 294 */ 295 @SharedByAllUsersField 296 private final boolean mPreventImeStartupUnlessTextEditor; 297 298 /** 299 * These IMEs are known not to behave well when evicted from memory and thus are exempt 300 * from the IME startup avoidance behavior that is enabled by 301 * {@link #mPreventImeStartupUnlessTextEditor}. 302 */ 303 @SharedByAllUsersField 304 @NonNull 305 private final String[] mNonPreemptibleInputMethods; 306 307 /** 308 * See {@link #shouldEnableConcurrentMultiUserMode(Context)} about when set to be {@code true}. 309 */ 310 @SharedByAllUsersField 311 private final boolean mConcurrentMultiUserModeEnabled; 312 313 /** 314 * Returns {@code true} if the concurrent multi-user mode is enabled. 315 * 316 * <p>Currently not compatible with profiles (e.g. work profile).</p> 317 * 318 * @param context {@link Context} to be used to query 319 * {@link PackageManager#FEATURE_AUTOMOTIVE} 320 * @return {@code true} if the concurrent multi-user mode is enabled. 321 */ shouldEnableConcurrentMultiUserMode(@onNull Context context)322 static boolean shouldEnableConcurrentMultiUserMode(@NonNull Context context) { 323 return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE) 324 && UserManager.isVisibleBackgroundUsersEnabled() 325 && context.getResources().getBoolean(android.R.bool.config_perDisplayFocusEnabled) 326 && Flags.concurrentInputMethods(); 327 } 328 329 /** 330 * Figures out the target IME user ID for a given {@link Binder} IPC. 331 * 332 * @param callingProcessUserId the user ID of the calling process 333 * @return the user ID to be used for this {@link Binder} call 334 */ 335 @GuardedBy("ImfLock.class") 336 @UserIdInt 337 @BinderThread resolveImeUserIdLocked(@serIdInt int callingProcessUserId)338 private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId) { 339 return mConcurrentMultiUserModeEnabled ? callingProcessUserId : mCurrentImeUserId; 340 } 341 342 /** 343 * Figures out the targetIMuser for a given {@link Binder} IPC. In case 344 * {@code callingProcessUserId} is SYSTEM user, then it will return the owner of the display 345 * associated with the {@code client} passed as parameter. 346 * 347 * @param callingProcessUserId the user ID of the calling process 348 * @param client the input method client used to retrieve the user id in case 349 * {@code callingProcessUserId} is assigned to SYSTEM user 350 * @return the user ID to be used for this {@link Binder} call 351 */ 352 @GuardedBy("ImfLock.class") 353 @UserIdInt 354 @BinderThread resolveImeUserIdLocked(@serIdInt int callingProcessUserId, @NonNull IInputMethodClient client)355 private int resolveImeUserIdLocked(@UserIdInt int callingProcessUserId, 356 @NonNull IInputMethodClient client) { 357 if (mConcurrentMultiUserModeEnabled) { 358 if (callingProcessUserId == UserHandle.USER_SYSTEM) { 359 final var clientState = mClientController.getClient(client.asBinder()); 360 return mUserManagerInternal.getUserAssignedToDisplay( 361 clientState.mSelfReportedDisplayId); 362 } 363 return callingProcessUserId; 364 } 365 return mCurrentImeUserId; 366 } 367 368 /** 369 * Figures out the target IME user ID associated with the given {@code displayId}. 370 * 371 * @param displayId the display ID to be queried about 372 * @return User ID to be used for this {@code displayId}. 373 */ 374 @GuardedBy("ImfLock.class") 375 @UserIdInt resolveImeUserIdFromDisplayIdLocked(int displayId)376 private int resolveImeUserIdFromDisplayIdLocked(int displayId) { 377 return mConcurrentMultiUserModeEnabled 378 ? mUserManagerInternal.getUserAssignedToDisplay(displayId) : mCurrentImeUserId; 379 } 380 381 /** 382 * Figures out the target IME user ID associated with the given {@code windowToken}. 383 * 384 * @param windowToken the Window token to be queried about 385 * @return User ID to be used for this {@code displayId}. 386 */ 387 @GuardedBy("ImfLock.class") 388 @UserIdInt resolveImeUserIdFromWindowLocked(@onNull IBinder windowToken)389 private int resolveImeUserIdFromWindowLocked(@NonNull IBinder windowToken) { 390 if (mConcurrentMultiUserModeEnabled) { 391 final int displayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); 392 return mUserManagerInternal.getUserAssignedToDisplay(displayId); 393 } 394 return mCurrentImeUserId; 395 } 396 397 final Context mContext; 398 final Resources mRes; 399 private final Handler mHandler; 400 401 private final InputMethodManagerInternal mInputMethodManagerInternal; 402 @NonNull 403 private final Handler mIoHandler; 404 405 /** 406 * The user ID whose IME should be used if {@link #mConcurrentMultiUserModeEnabled} is 407 * {@code false}, otherwise remains to be the initial value, which is obtained by 408 * {@link ActivityManagerInternal#getCurrentUserId()} while the device is booting up. 409 * 410 * <p>Never get confused with {@link ActivityManagerInternal#getCurrentUserId()}, which is 411 * in general useless when designing and implementing interactions between apps and IMEs.</p> 412 * 413 * <p>You can also not assume that the IME client process belongs to {@link #mCurrentImeUserId}. 414 * A most important outlier is System UI process, which always runs under 415 * {@link UserHandle#USER_SYSTEM} in all the known configurations including Headless System User 416 * Mode (HSUM).</p> 417 */ 418 @MultiUserUnawareField 419 @UserIdInt 420 @GuardedBy("ImfLock.class") 421 private int mCurrentImeUserId; 422 423 /** Holds all user related data */ 424 @SharedByAllUsersField 425 private final UserDataRepository mUserDataRepository; 426 427 final WindowManagerInternal mWindowManagerInternal; 428 private final ActivityManagerInternal mActivityManagerInternal; 429 final PackageManagerInternal mPackageManagerInternal; 430 final InputManagerInternal mInputManagerInternal; 431 final ImePlatformCompatUtils mImePlatformCompatUtils; 432 @SharedByAllUsersField 433 final InputMethodDeviceConfigs mInputMethodDeviceConfigs; 434 435 private final UserManagerInternal mUserManagerInternal; 436 @MultiUserUnawareField 437 private final InputMethodMenuController mMenuController; 438 private final InputMethodMenuControllerNew mMenuControllerNew; 439 440 @GuardedBy("ImfLock.class") 441 @SharedByAllUsersField 442 @NonNull 443 private final DefaultImeVisibilityApplier mVisibilityApplier; 444 445 /** 446 * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}. 447 * 448 * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is 449 * not necessary.</p> 450 */ 451 @Nullable 452 private AudioManagerInternal mAudioManagerInternal = null; 453 @Nullable 454 private VirtualDeviceManagerInternal mVdmInternal = null; 455 @Nullable 456 private DisplayManagerInternal mDisplayManagerInternal = null; 457 458 // Mapping from deviceId to the device-specific imeId for that device. 459 @GuardedBy("ImfLock.class") 460 @SharedByAllUsersField 461 private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>(); 462 463 @Nullable 464 private StatusBarManagerInternal mStatusBarManagerInternal; 465 @SharedByAllUsersField 466 private boolean mShowOngoingImeSwitcherForPhones; 467 @GuardedBy("ImfLock.class") 468 @MultiUserUnawareField 469 private final HandwritingModeController mHwController; 470 @GuardedBy("ImfLock.class") 471 @SharedByAllUsersField 472 private IntArray mStylusIds; 473 474 private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() { 475 /** 476 * {@inheritDoc} 477 */ 478 @Override 479 public void dumpToProto(ProtoOutputStream proto, @Nullable byte[] icProto) { 480 dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE); 481 } 482 }; 483 484 static class SessionState { 485 486 @NonNull 487 final ClientState mClient; 488 @NonNull 489 final IInputMethodInvoker mMethod; 490 491 @Nullable 492 IInputMethodSession mSession; 493 @Nullable 494 InputChannel mChannel; 495 496 @UserIdInt 497 final int mUserId; 498 499 @Override toString()500 public String toString() { 501 return "SessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid 502 + " method=" + Integer.toHexString( 503 IInputMethodInvoker.getBinderIdentityHashCode(mMethod)) 504 + " session=" + Integer.toHexString(System.identityHashCode(mSession)) 505 + " channel=" + mChannel 506 + " userId=" + mUserId 507 + "}"; 508 } 509 SessionState(@onNull ClientState client, @NonNull IInputMethodInvoker method, IInputMethodSession session, InputChannel channel, @UserIdInt int userId)510 SessionState(@NonNull ClientState client, @NonNull IInputMethodInvoker method, 511 IInputMethodSession session, InputChannel channel, @UserIdInt int userId) { 512 mClient = client; 513 mMethod = method; 514 mSession = session; 515 mChannel = channel; 516 mUserId = userId; 517 } 518 } 519 520 /** 521 * Record session state for an accessibility service. 522 */ 523 static class AccessibilitySessionState { 524 525 @NonNull 526 final ClientState mClient; 527 // Id of the accessibility service. 528 final int mId; 529 530 @Nullable 531 IAccessibilityInputMethodSession mSession; 532 533 @Override toString()534 public String toString() { 535 return "AccessibilitySessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid 536 + " id=" + Integer.toHexString(mId) 537 + " session=" + Integer.toHexString(System.identityHashCode(mSession)) 538 + "}"; 539 } 540 AccessibilitySessionState(@onNull ClientState client, int id, IAccessibilityInputMethodSession session)541 AccessibilitySessionState(@NonNull ClientState client, int id, 542 IAccessibilityInputMethodSession session) { 543 mClient = client; 544 mId = id; 545 mSession = session; 546 } 547 } 548 549 /** 550 * Manages the IME clients. 551 */ 552 @SharedByAllUsersField 553 @NonNull 554 private final ClientController mClientController; 555 556 /** 557 * Set once the system is ready to run third party code. 558 */ 559 @SharedByAllUsersField 560 boolean mSystemReady; 561 562 @AnyThread 563 @NonNull getUserData(@serIdInt int userId)564 UserData getUserData(@UserIdInt int userId) { 565 return mUserDataRepository.getOrCreate(userId); 566 } 567 568 @AnyThread 569 @NonNull getInputMethodBindingController(@serIdInt int userId)570 InputMethodBindingController getInputMethodBindingController(@UserIdInt int userId) { 571 return getUserData(userId).mBindingController; 572 } 573 574 /** 575 * Map of window perceptible states indexed by their associated window tokens. 576 * 577 * The value {@code true} indicates that IME has not been mostly hidden via 578 * {@link android.view.InsetsController} for the given window. 579 */ 580 @GuardedBy("ImfLock.class") 581 @SharedByAllUsersField 582 private final WeakHashMap<IBinder, Boolean> mFocusedWindowPerceptible = new WeakHashMap<>(); 583 584 /** 585 * The display ID of the input method indicates the fallback display which returned by 586 * {@link #computeImeDisplayIdForTarget}. 587 */ 588 static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY; 589 590 /** 591 * If non-null, this is the input method service we are currently connected 592 * to. 593 */ 594 @GuardedBy("ImfLock.class") 595 @Nullable getCurMethodLocked()596 IInputMethodInvoker getCurMethodLocked() { 597 return getInputMethodBindingController(mCurrentImeUserId).getCurMethod(); 598 } 599 600 /** 601 * True if the device is currently interactive with user. The value is true initially. 602 */ 603 @MultiUserUnawareField 604 boolean mIsInteractive = true; 605 606 @SharedByAllUsersField 607 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); 608 609 @SharedByAllUsersField 610 private final String mSlotIme; 611 612 /** 613 * Registered {@link InputMethodListListener}. 614 * This variable can be accessed from both of MainThread and BinderThread. 615 */ 616 @SharedByAllUsersField 617 private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners = 618 new CopyOnWriteArrayList<>(); 619 620 @GuardedBy("ImfLock.class") 621 @SharedByAllUsersField 622 private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>(); 623 624 @GuardedBy("ImfLock.class") 625 @SharedByAllUsersField 626 @NonNull 627 private final StartInputHistory mStartInputHistory = new StartInputHistory(); 628 629 @GuardedBy("ImfLock.class") 630 @SharedByAllUsersField 631 @NonNull 632 private final SoftInputShowHideHistory mSoftInputShowHideHistory = 633 new SoftInputShowHideHistory(); 634 635 @SharedByAllUsersField 636 @NonNull 637 private final ImeTrackerService mImeTrackerService; 638 639 @GuardedBy("ImfLock.class") onSecureSettingsChangedLocked(@onNull String key, @UserIdInt int userId)640 private void onSecureSettingsChangedLocked(@NonNull String key, @UserIdInt int userId) { 641 switch (key) { 642 case Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD: { 643 if (!Flags.imeSwitcherRevamp()) { 644 if (userId == mCurrentImeUserId) { 645 mMenuController.updateKeyboardFromSettingsLocked(userId); 646 } 647 } 648 break; 649 } 650 case Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE: { 651 final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser( 652 mContext.getContentResolver(), 653 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, userId); 654 final var userData = getUserData(userId); 655 final var visibilityStateComputer = userData.mVisibilityStateComputer; 656 visibilityStateComputer.getImePolicy().setA11yRequestNoSoftKeyboard( 657 accessibilitySoftKeyboardSetting); 658 if (visibilityStateComputer.getImePolicy().isA11yRequestNoSoftKeyboard()) { 659 if (Flags.refactorInsetsController()) { 660 final var statsToken = createStatsTokenForFocusedClient(false /* show */, 661 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, userId); 662 setImeVisibilityOnFocusedWindowClient(false, userData, statsToken); 663 } else { 664 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 665 0 /* flags */, SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE, 666 userId); 667 } 668 } else if (isShowRequestedForCurrentWindow(userId)) { 669 if (Flags.refactorInsetsController()) { 670 final var statsToken = createStatsTokenForFocusedClient(true /* show */, 671 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId); 672 setImeVisibilityOnFocusedWindowClient(true, userData, statsToken); 673 } else { 674 showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 675 InputMethodManager.SHOW_IMPLICIT, 676 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE, userId); 677 } 678 } 679 break; 680 } 681 case Settings.Secure.STYLUS_HANDWRITING_ENABLED: { 682 InputMethodManager.invalidateLocalStylusHandwritingAvailabilityCaches(); 683 InputMethodManager 684 .invalidateLocalConnectionlessStylusHandwritingAvailabilityCaches(); 685 break; 686 } 687 case Settings.Secure.DEFAULT_INPUT_METHOD: 688 case Settings.Secure.ENABLED_INPUT_METHODS: 689 case Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE: { 690 boolean enabledChanged = false; 691 String newEnabled = InputMethodSettingsRepository.get(userId) 692 .getEnabledInputMethodsStr(); 693 final var userData = getUserData(userId); 694 if (!userData.mLastEnabledInputMethodsStr.equals(newEnabled)) { 695 userData.mLastEnabledInputMethodsStr = newEnabled; 696 enabledChanged = true; 697 } 698 updateInputMethodsFromSettingsLocked(enabledChanged, userId); 699 break; 700 } 701 } 702 } 703 704 /** 705 * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users. 706 */ 707 private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver { 708 @Override onReceive(Context context, Intent intent)709 public void onReceive(Context context, Intent intent) { 710 final String action = intent.getAction(); 711 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 712 final PendingResult pendingResult = getPendingResult(); 713 if (pendingResult == null) { 714 return; 715 } 716 // sender userId can be a real user ID or USER_ALL. 717 final int senderUserId = pendingResult.getSendingUserId(); 718 synchronized (ImfLock.class) { 719 if (senderUserId != UserHandle.USER_ALL && senderUserId != mCurrentImeUserId) { 720 // A background user is trying to hide the dialog. Ignore. 721 return; 722 } 723 final int userId = mCurrentImeUserId; 724 if (Flags.imeSwitcherRevamp()) { 725 final var bindingController = getInputMethodBindingController(userId); 726 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); 727 } else { 728 mMenuController.hideInputMethodMenuLocked(userId); 729 } 730 } 731 } else { 732 Slog.w(TAG, "Unexpected intent " + intent); 733 } 734 } 735 } 736 737 /** 738 * Handles {@link Intent#ACTION_LOCALE_CHANGED}. 739 * 740 * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all 741 * the users.</p> 742 */ 743 @WorkerThread onActionLocaleChanged(@onNull LocaleList prevLocales, @NonNull LocaleList newLocales)744 void onActionLocaleChanged(@NonNull LocaleList prevLocales, @NonNull LocaleList newLocales) { 745 ProtoLog.v(IMMS_DEBUG, "onActionLocaleChanged prev=%s new=%s", prevLocales, newLocales); 746 synchronized (ImfLock.class) { 747 if (!mSystemReady) { 748 return; 749 } 750 for (int userId : mUserManagerInternal.getUserIds()) { 751 // Does InputMethodInfo really have data dependency on system locale? 752 // TODO(b/356679261): Check if we really need to update RawInputMethodInfo here. 753 { 754 final var userData = getUserData(userId); 755 final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); 756 final var rawMethodMap = queryRawInputMethodServiceMap(mContext, userId); 757 userData.mRawInputMethodMap.set(rawMethodMap); 758 final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, 759 DirectBootAwareness.AUTO, 760 userData.mIsUnlockingOrUnlocked.get()); 761 final var settings = InputMethodSettings.create(methodMap, userId); 762 InputMethodSettingsRepository.put(userId, settings); 763 } 764 postInputMethodSettingUpdatedLocked(true /* resetDefaultEnabledIme */, userId); 765 // If the locale is changed, needs to reset the default ime 766 resetDefaultImeLocked(mContext, userId); 767 updateFromSettingsLocked(true, userId); 768 } 769 } 770 } 771 772 final class MyPackageMonitor extends PackageMonitor { 773 /** 774 * Remembers package names passed to {@link #onPackageDataCleared(String, int)}. 775 * 776 * <p>This field must be accessed only from callback methods in {@link PackageMonitor}, 777 * which should be bound to {@link #getRegisteredHandler()}.</p> 778 */ 779 private ArrayList<String> mDataClearedPackages = new ArrayList<>(); 780 MyPackageMonitor()781 private MyPackageMonitor() { 782 super(true); 783 } 784 785 @Override onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)786 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 787 synchronized (ImfLock.class) { 788 final int userId = getChangingUserId(); 789 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 790 String curInputMethodId = settings.getSelectedInputMethod(); 791 final List<InputMethodInfo> methodList = settings.getMethodList(); 792 final int numImes = methodList.size(); 793 if (curInputMethodId != null) { 794 for (int i = 0; i < numImes; i++) { 795 InputMethodInfo imi = methodList.get(i); 796 if (imi.getId().equals(curInputMethodId)) { 797 for (String pkg : packages) { 798 if (imi.getPackageName().equals(pkg)) { 799 if (!doit) { 800 return true; 801 } 802 resetSelectedInputMethodAndSubtypeLocked("", userId); 803 chooseNewDefaultIMELocked(userId); 804 return true; 805 } 806 } 807 } 808 } 809 } 810 } 811 return false; 812 } 813 814 @Override onBeginPackageChanges()815 public void onBeginPackageChanges() { 816 clearPackageChangeState(); 817 } 818 819 @Override onPackageDataCleared(String packageName, int uid)820 public void onPackageDataCleared(String packageName, int uid) { 821 mDataClearedPackages.add(packageName); 822 } 823 824 @Override onFinishPackageChanges()825 public void onFinishPackageChanges() { 826 onFinishPackageChangesInternal(); 827 clearPackageChangeState(); 828 } 829 clearPackageChangeState()830 private void clearPackageChangeState() { 831 // No need to lock them because we access these fields only on getRegisteredHandler(). 832 mDataClearedPackages.clear(); 833 } 834 onFinishPackageChangesInternal()835 private void onFinishPackageChangesInternal() { 836 final int userId = getChangingUserId(); 837 final var userData = getUserData(userId); 838 839 userData.mRawInputMethodMap.set(queryRawInputMethodServiceMap(mContext, userId)); 840 841 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 842 843 InputMethodInfo curIm = null; 844 String curInputMethodId = settings.getSelectedInputMethod(); 845 final List<InputMethodInfo> methodList = settings.getMethodList(); 846 847 final ArrayList<String> imesToClearAdditionalSubtypes = new ArrayList<>(); 848 final ArrayList<String> imesToBeDisabled = new ArrayList<>(); 849 final int numImes = methodList.size(); 850 for (int i = 0; i < numImes; i++) { 851 InputMethodInfo imi = methodList.get(i); 852 final String imiId = imi.getId(); 853 if (imiId.equals(curInputMethodId)) { 854 curIm = imi; 855 } 856 if (mDataClearedPackages.contains(imi.getPackageName())) { 857 imesToClearAdditionalSubtypes.add(imiId); 858 } 859 int change = isPackageDisappearing(imi.getPackageName()); 860 if (change == PACKAGE_PERMANENT_CHANGE) { 861 Slog.i(TAG, "Input method uninstalled, disabling: " + imi.getComponent()); 862 imesToBeDisabled.add(imi.getId()); 863 } else if (change == PACKAGE_UPDATING) { 864 Slog.i(TAG, "Input method reinstalling, clearing additional subtypes: " 865 + imi.getComponent()); 866 imesToClearAdditionalSubtypes.add(imiId); 867 } 868 } 869 870 // Clear additional subtypes as a batch operation. 871 final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); 872 final AdditionalSubtypeMap newAdditionalSubtypeMap = 873 additionalSubtypeMap.cloneWithRemoveOrSelf(imesToClearAdditionalSubtypes); 874 final boolean additionalSubtypeChanged = 875 (newAdditionalSubtypeMap != additionalSubtypeMap); 876 if (additionalSubtypeChanged) { 877 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, 878 settings.getMethodMap()); 879 } 880 881 final var newMethodMap = userData.mRawInputMethodMap.get().toInputMethodMap( 882 newAdditionalSubtypeMap, 883 DirectBootAwareness.AUTO, 884 userData.mIsUnlockingOrUnlocked.get()); 885 886 final boolean noUpdate = InputMethodMap.areSame(settings.getMethodMap(), newMethodMap); 887 if (noUpdate && imesToBeDisabled.isEmpty()) { 888 return; 889 } 890 891 // Here we start remaining tasks that need to be done with the lock (b/340221861). 892 synchronized (ImfLock.class) { 893 final int numImesToBeDisabled = imesToBeDisabled.size(); 894 for (int i = 0; i < numImesToBeDisabled; ++i) { 895 setInputMethodEnabledLocked(imesToBeDisabled.get(i), false /* enabled */, 896 userId); 897 } 898 if (noUpdate) { 899 return; 900 } 901 InputMethodSettingsRepository.put(userId, 902 InputMethodSettings.create(newMethodMap, userId)); 903 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId); 904 905 boolean changed = false; 906 907 if (curIm != null) { 908 int change = isPackageDisappearing(curIm.getPackageName()); 909 if (change == PACKAGE_TEMPORARY_CHANGE 910 || change == PACKAGE_PERMANENT_CHANGE) { 911 final PackageManager userAwarePackageManager = 912 getPackageManagerForUser(mContext, userId); 913 ServiceInfo si = null; 914 try { 915 si = userAwarePackageManager.getServiceInfo(curIm.getComponent(), 916 PackageManager.ComponentInfoFlags.of(0)); 917 } catch (PackageManager.NameNotFoundException ignored) { 918 } 919 if (si == null) { 920 // Uh oh, current input method is no longer around! 921 // Pick another one... 922 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 923 final var bindingController = getInputMethodBindingController(userId); 924 updateSystemUiLocked(0 /* vis */, 925 bindingController.getBackDisposition(), userId); 926 if (!chooseNewDefaultIMELocked(userId)) { 927 changed = true; 928 curIm = null; 929 Slog.i(TAG, "Unsetting current input method"); 930 resetSelectedInputMethodAndSubtypeLocked("", userId); 931 } 932 } 933 } 934 } 935 936 if (curIm == null) { 937 // We currently don't have a default input method... is 938 // one now available? 939 changed = chooseNewDefaultIMELocked(userId); 940 } else if (!changed && isPackageModified(curIm.getPackageName())) { 941 // Even if the current input method is still available, current subtype could 942 // be obsolete when the package is modified in practice. 943 changed = true; 944 } 945 946 if (changed) { 947 updateFromSettingsLocked(false, userId); 948 } 949 } 950 } 951 } 952 953 private static final class UserSwitchHandlerTask implements Runnable { 954 final InputMethodManagerService mService; 955 956 @UserIdInt 957 final int mToUserId; 958 959 @Nullable 960 IInputMethodClientInvoker mClientToBeReset; 961 UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, @Nullable IInputMethodClientInvoker clientToBeReset)962 UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, 963 @Nullable IInputMethodClientInvoker clientToBeReset) { 964 mService = service; 965 mToUserId = toUserId; 966 mClientToBeReset = clientToBeReset; 967 } 968 969 @Override run()970 public void run() { 971 synchronized (ImfLock.class) { 972 if (mService.mUserSwitchHandlerTask != this) { 973 // This task was already canceled before it is handled here. So do nothing. 974 return; 975 } 976 mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId, 977 mClientToBeReset); 978 mService.mUserSwitchHandlerTask = null; 979 } 980 } 981 } 982 983 /** 984 * When non-{@code null}, this represents pending user-switch task, which is to be executed as 985 * a handler callback. This needs to be set and unset only within the lock. 986 */ 987 @Nullable 988 @GuardedBy("ImfLock.class") 989 @MultiUserUnawareField 990 private UserSwitchHandlerTask mUserSwitchHandlerTask; 991 992 /** 993 * {@link SystemService} used to publish and manage the lifecycle of 994 * {@link InputMethodManagerService}. 995 */ 996 public static final class Lifecycle extends SystemService 997 implements UserManagerInternal.UserLifecycleListener { 998 private final InputMethodManagerService mService; 999 Lifecycle(Context context)1000 public Lifecycle(Context context) { 1001 this(context, createServiceForProduction(context)); 1002 1003 // For production code, hook up user lifecycle 1004 mService.mUserManagerInternal.addUserLifecycleListener(this); 1005 1006 // Hook up resource change first before initializeUsersAsync() starts reading the 1007 // seemingly initial data so that we can eliminate the race condition. 1008 InputMethodDrawsNavBarResourceMonitor.registerCallback(context, mService.mIoHandler, 1009 mService::onUpdateResourceOverlay); 1010 1011 // Also schedule user init tasks onto an I/O thread. 1012 initializeUsersAsync(mService.mUserManagerInternal.getUserIds()); 1013 } 1014 1015 @VisibleForTesting Lifecycle(Context context, @NonNull InputMethodManagerService inputMethodManagerService)1016 Lifecycle(Context context, @NonNull InputMethodManagerService inputMethodManagerService) { 1017 super(context); 1018 mService = inputMethodManagerService; 1019 } 1020 1021 /** 1022 * Does initialization then instantiate {@link InputMethodManagerService} for production 1023 * configurations. 1024 * 1025 * <p>We have this abstraction just because several unit tests directly initialize 1026 * {@link InputMethodManagerService} with some mocked/emulated dependencies.</p> 1027 * 1028 * @param context {@link Context} to be used to set up 1029 * @return {@link InputMethodManagerService} object to be used 1030 */ 1031 @NonNull createServiceForProduction( @onNull Context context)1032 private static InputMethodManagerService createServiceForProduction( 1033 @NonNull Context context) { 1034 final ServiceThread thread = new ServiceThread(HANDLER_THREAD_NAME, 1035 Process.THREAD_PRIORITY_FOREGROUND, false /* allowIo */); 1036 thread.start(); 1037 1038 final ServiceThread ioThread = new ServiceThread(PACKAGE_MONITOR_THREAD_NAME, 1039 Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */); 1040 ioThread.start(); 1041 1042 SecureSettingsWrapper.setContentResolver(context.getContentResolver()); 1043 1044 return new InputMethodManagerService(context, 1045 shouldEnableConcurrentMultiUserMode(context), thread.getLooper(), 1046 Handler.createAsync(ioThread.getLooper()), 1047 null /* bindingControllerForTesting */); 1048 } 1049 1050 @Override onStart()1051 public void onStart() { 1052 mService.publishLocalService(); 1053 IInputMethodManagerImpl.Callback service; 1054 if (Flags.useZeroJankProxy()) { 1055 service = new ZeroJankProxy(mService.mHandler::post, mService); 1056 } else { 1057 service = mService; 1058 } 1059 publishBinderService(Context.INPUT_METHOD_SERVICE, 1060 IInputMethodManagerImpl.create(service), false /*allowIsolated*/, 1061 DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PRIORITY_NORMAL | DUMP_FLAG_PROTO); 1062 if (Flags.refactorInsetsController()) { 1063 mService.registerImeRequestedChangedListener(); 1064 } 1065 } 1066 1067 @Override onUserSwitching(@ullable TargetUser from, @NonNull TargetUser to)1068 public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) { 1069 // Called on ActivityManager thread. 1070 synchronized (ImfLock.class) { 1071 if (mService.mConcurrentMultiUserModeEnabled) { 1072 // In concurrent multi-user mode, we in general do not rely on the concept of 1073 // current user. 1074 return; 1075 } 1076 mService.scheduleSwitchUserTaskLocked(to.getUserIdentifier(), 1077 /* clientToBeReset= */ null); 1078 } 1079 } 1080 1081 @Override onBootPhase(int phase)1082 public void onBootPhase(int phase) { 1083 // Called on ActivityManager thread. 1084 // TODO: Dispatch this to a worker thread as needed. 1085 if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { 1086 mService.systemRunning(); 1087 } 1088 } 1089 1090 @Override onUserCreated(UserInfo user, @Nullable Object token)1091 public void onUserCreated(UserInfo user, @Nullable Object token) { 1092 // Called directly from UserManagerService. Do not block the calling thread. 1093 final int userId = user.id; 1094 AdditionalSubtypeMapRepository.onUserCreated(userId); 1095 initializeUsersAsync(new int[]{ userId }); 1096 } 1097 1098 @Override onUserRemoved(UserInfo user)1099 public void onUserRemoved(UserInfo user) { 1100 // Called directly from UserManagerService. Do not block the calling thread. 1101 final int userId = user.id; 1102 SecureSettingsWrapper.onUserRemoved(userId); 1103 AdditionalSubtypeMapRepository.remove(userId); 1104 InputMethodSettingsRepository.remove(userId); 1105 mService.mUserDataRepository.remove(userId); 1106 synchronized (ImfLock.class) { 1107 final int nextOrCurrentUser = mService.mUserSwitchHandlerTask != null 1108 ? mService.mUserSwitchHandlerTask.mToUserId : mService.mCurrentImeUserId; 1109 if (!mService.mConcurrentMultiUserModeEnabled && userId == nextOrCurrentUser) { 1110 // The current user was removed without an ongoing switch, or the user targeted 1111 // by the ongoing switch was removed. Switch to the current non-profile user 1112 // to allow starting input on it or one of its profile users later. 1113 // Note: non-profile users cannot be removed while they are the current user. 1114 final int currentUserId = mService.mActivityManagerInternal.getCurrentUserId(); 1115 mService.scheduleSwitchUserTaskLocked(currentUserId, 1116 null /* clientToBeReset */); 1117 } 1118 } 1119 } 1120 1121 @Override onUserUnlocking(@onNull TargetUser user)1122 public void onUserUnlocking(@NonNull TargetUser user) { 1123 // Called on ActivityManager thread. Do not block the calling thread. 1124 final int userId = user.getUserIdentifier(); 1125 final var userData = mService.getUserData(userId); 1126 final boolean userUnlocked = true; 1127 userData.mIsUnlockingOrUnlocked.set(userUnlocked); 1128 SecureSettingsWrapper.onUserUnlocking(userId); 1129 final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( 1130 AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, 1131 userUnlocked); 1132 final var newSettings = InputMethodSettings.create(methodMap, userId); 1133 InputMethodSettingsRepository.put(userId, newSettings); 1134 mService.mIoHandler.post(() -> { 1135 synchronized (ImfLock.class) { 1136 if (!mService.mSystemReady) { 1137 return; 1138 } 1139 // We need to rebuild IMEs. 1140 mService.postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, 1141 userId); 1142 mService.updateInputMethodsFromSettingsLocked(true /* enabledChanged */, 1143 userId); 1144 } 1145 }); 1146 } 1147 1148 @Override onUserStarting(TargetUser user)1149 public void onUserStarting(TargetUser user) { 1150 // Called on ActivityManager thread. 1151 final int userId = user.getUserIdentifier(); 1152 SecureSettingsWrapper.onUserStarting(userId); 1153 mService.mIoHandler.post(() -> { 1154 synchronized (ImfLock.class) { 1155 if (mService.mSystemReady) { 1156 mService.onUserReadyLocked(userId); 1157 } 1158 } 1159 }); 1160 } 1161 1162 @AnyThread initializeUsersAsync(@serIdInt int[] userIds)1163 private void initializeUsersAsync(@UserIdInt int[] userIds) { 1164 Slog.d(TAG, "Schedule initialization for users=" + Arrays.toString(userIds)); 1165 mService.mIoHandler.post(() -> { 1166 final var service = mService; 1167 final var context = service.mContext; 1168 final var userManagerInternal = service.mUserManagerInternal; 1169 1170 for (int userId : userIds) { 1171 Slog.d(TAG, "Start initialization for user=" + userId); 1172 final var userData = mService.getUserData(userId); 1173 1174 AdditionalSubtypeMapRepository.initializeIfNecessary(userId); 1175 final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); 1176 final var rawMethodMap = queryRawInputMethodServiceMap(context, userId); 1177 userData.mRawInputMethodMap.set(rawMethodMap); 1178 1179 final boolean unlocked = userManagerInternal.isUserUnlockingOrUnlocked(userId); 1180 userData.mIsUnlockingOrUnlocked.set(unlocked); 1181 final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, 1182 DirectBootAwareness.AUTO, unlocked); 1183 1184 final var settings = InputMethodSettings.create(methodMap, userId); 1185 InputMethodSettingsRepository.put(userId, settings); 1186 1187 final int profileParentId = userManagerInternal.getProfileParentId(userId); 1188 final boolean value = 1189 InputMethodDrawsNavBarResourceMonitor.evaluate(context, 1190 profileParentId); 1191 userData.mImeDrawsNavBar.set(value); 1192 1193 userData.mBackgroundLoadLatch.countDown(); 1194 Slog.d(TAG, "Complete initialization for user=" + userId); 1195 } 1196 }); 1197 } 1198 1199 @Override onUserStopped(@onNull TargetUser user)1200 public void onUserStopped(@NonNull TargetUser user) { 1201 final int userId = user.getUserIdentifier(); 1202 // Called on ActivityManager thread. 1203 1204 // Following operations should be trivial and fast enough, so do not dispatch them to 1205 // the IO thread. 1206 SecureSettingsWrapper.onUserStopped(userId); 1207 final var userData = mService.getUserData(userId); 1208 final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); 1209 final var rawMethodMap = userData.mRawInputMethodMap.get(); 1210 final boolean userUnlocked = false; // Stopping a user also locks their storage. 1211 userData.mIsUnlockingOrUnlocked.set(userUnlocked); 1212 final var methodMap = rawMethodMap.toInputMethodMap(additionalSubtypeMap, 1213 DirectBootAwareness.AUTO, userUnlocked); 1214 InputMethodSettingsRepository.put(userId, 1215 InputMethodSettings.create(methodMap, userId)); 1216 } 1217 } 1218 1219 @GuardedBy("ImfLock.class") scheduleSwitchUserTaskLocked(@serIdInt int userId, @Nullable IInputMethodClientInvoker clientToBeReset)1220 void scheduleSwitchUserTaskLocked(@UserIdInt int userId, 1221 @Nullable IInputMethodClientInvoker clientToBeReset) { 1222 if (mUserSwitchHandlerTask != null) { 1223 if (mUserSwitchHandlerTask.mToUserId == userId) { 1224 mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset; 1225 return; 1226 } 1227 mIoHandler.removeCallbacks(mUserSwitchHandlerTask); 1228 } 1229 // Hide soft input before user switch task since switch task may block main handler a while 1230 // and delayed the hideCurrentInputLocked(). 1231 final var userData = getUserData(userId); 1232 if (Flags.refactorInsetsController()) { 1233 final var statsToken = createStatsTokenForFocusedClient(false /* show */, 1234 SoftInputShowHideReason.HIDE_SWITCH_USER, userId); 1235 setImeVisibilityOnFocusedWindowClient(false, userData, statsToken); 1236 } else { 1237 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, 1238 SoftInputShowHideReason.HIDE_SWITCH_USER, userId); 1239 } 1240 final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, 1241 clientToBeReset); 1242 mUserSwitchHandlerTask = task; 1243 mIoHandler.post(task); 1244 } 1245 1246 @VisibleForTesting InputMethodManagerService( Context context, boolean concurrentMultiUserModeEnabled, @NonNull Looper uiLooper, @NonNull Handler ioHandler, @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting)1247 InputMethodManagerService( 1248 Context context, 1249 boolean concurrentMultiUserModeEnabled, 1250 @NonNull Looper uiLooper, 1251 @NonNull Handler ioHandler, 1252 @Nullable IntFunction<InputMethodBindingController> bindingControllerForTesting) { 1253 synchronized (ImfLock.class) { 1254 mConcurrentMultiUserModeEnabled = concurrentMultiUserModeEnabled; 1255 mContext = context; 1256 mRes = context.getResources(); 1257 1258 mHandler = Handler.createAsync(uiLooper, this); 1259 mIoHandler = ioHandler; 1260 SystemLocaleWrapper.onStart(context, this::onActionLocaleChanged, mIoHandler); 1261 mImeTrackerService = new ImeTrackerService(mHandler); 1262 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); 1263 mActivityManagerInternal = LocalServices.getService(ActivityManagerInternal.class); 1264 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 1265 mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); 1266 mImePlatformCompatUtils = new ImePlatformCompatUtils(); 1267 mInputMethodDeviceConfigs = new InputMethodDeviceConfigs(); 1268 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 1269 1270 mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); 1271 1272 mShowOngoingImeSwitcherForPhones = false; 1273 1274 ProtoLog.init(ImeProtoLogGroup.values()); 1275 1276 mCurrentImeUserId = mActivityManagerInternal.getCurrentUserId(); 1277 final IntFunction<InputMethodBindingController> 1278 bindingControllerFactory = userId -> new InputMethodBindingController(userId, 1279 InputMethodManagerService.this); 1280 final IntFunction<ImeVisibilityStateComputer> visibilityStateComputerFactory = 1281 userId -> new ImeVisibilityStateComputer(InputMethodManagerService.this, 1282 userId); 1283 mUserDataRepository = new UserDataRepository( 1284 bindingControllerForTesting != null ? bindingControllerForTesting 1285 : bindingControllerFactory, visibilityStateComputerFactory); 1286 1287 mMenuController = new InputMethodMenuController(this); 1288 mMenuControllerNew = Flags.imeSwitcherRevamp() 1289 ? new InputMethodMenuControllerNew() : null; 1290 mVisibilityApplier = new DefaultImeVisibilityApplier(this); 1291 1292 mClientController = new ClientController(mPackageManagerInternal); 1293 mClientController.addClientControllerCallback(c -> onClientRemoved(c)); 1294 1295 mPreventImeStartupUnlessTextEditor = mRes.getBoolean( 1296 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor); 1297 mNonPreemptibleInputMethods = mRes.getStringArray( 1298 com.android.internal.R.array.config_nonPreemptibleInputMethods); 1299 Runnable discardDelegationTextRunnable = () -> discardHandwritingDelegationText(); 1300 mHwController = new HandwritingModeController(mContext, uiLooper, 1301 new InkWindowInitializer(), discardDelegationTextRunnable); 1302 registerDeviceListenerAndCheckStylusSupport(); 1303 mInputMethodManagerInternal = new LocalServiceImpl(); 1304 } 1305 } 1306 1307 private final class InkWindowInitializer implements Runnable { run()1308 public void run() { 1309 synchronized (ImfLock.class) { 1310 IInputMethodInvoker curMethod = getCurMethodLocked(); 1311 if (curMethod != null) { 1312 curMethod.initInkWindow(); 1313 } 1314 } 1315 } 1316 } 1317 1318 @GuardedBy("ImfLock.class") onUpdateEditorToolTypeLocked(@otionEvent.ToolType int toolType, @UserIdInt int userId)1319 private void onUpdateEditorToolTypeLocked(@MotionEvent.ToolType int toolType, 1320 @UserIdInt int userId) { 1321 final var curMethod = getInputMethodBindingController(userId).getCurMethod(); 1322 if (curMethod != null) { 1323 curMethod.updateEditorToolType(toolType); 1324 } 1325 } 1326 discardHandwritingDelegationText()1327 private void discardHandwritingDelegationText() { 1328 synchronized (ImfLock.class) { 1329 IInputMethodInvoker curMethod = getCurMethodLocked(); 1330 if (curMethod != null) { 1331 curMethod.discardHandwritingDelegationText(); 1332 } 1333 } 1334 } 1335 1336 @GuardedBy("ImfLock.class") resetDefaultImeLocked(Context context, @UserIdInt int userId)1337 private void resetDefaultImeLocked(Context context, @UserIdInt int userId) { 1338 final var bindingController = getInputMethodBindingController(userId); 1339 // Do not reset the default (current) IME when it is a 3rd-party IME 1340 String selectedMethodId = bindingController.getSelectedMethodId(); 1341 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 1342 final InputMethodInfo selectedImi = settings.getMethodMap().get(selectedMethodId); 1343 if (selectedImi != null && !selectedImi.isSystem()) { 1344 return; 1345 } 1346 final List<InputMethodInfo> suitableImes = InputMethodInfoUtils.getDefaultEnabledImes( 1347 context, settings.getEnabledInputMethodList()); 1348 if (suitableImes.isEmpty()) { 1349 Slog.i(TAG, "No default found"); 1350 return; 1351 } 1352 final InputMethodInfo defIm = suitableImes.get(0); 1353 ProtoLog.v(IMMS_DEBUG, "Default found, using %s", defIm.getId()); 1354 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_INDEX, false, userId); 1355 } 1356 1357 @NonNull getPackageManagerForUser(@onNull Context context, @UserIdInt int userId)1358 private static PackageManager getPackageManagerForUser(@NonNull Context context, 1359 @UserIdInt int userId) { 1360 return context.getUserId() == userId 1361 ? context.getPackageManager() 1362 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */) 1363 .getPackageManager(); 1364 } 1365 1366 @GuardedBy("ImfLock.class") switchUserOnHandlerLocked(@serIdInt int newUserId, IInputMethodClientInvoker clientToBeReset)1367 private void switchUserOnHandlerLocked(@UserIdInt int newUserId, 1368 IInputMethodClientInvoker clientToBeReset) { 1369 final int prevUserId = mCurrentImeUserId; 1370 ProtoLog.v(IMMS_DEBUG, "Switching user stage 1/3. newUserId=%s prevUserId=%s", newUserId, 1371 prevUserId); 1372 1373 // Clean up stuff for mCurrentImeUserId, which soon becomes the previous user. 1374 1375 // TODO(b/338461930): Check if this is still necessary or not. 1376 onUnbindCurrentMethodByReset(prevUserId); 1377 1378 // Note that in b/197848765 we want to see if we can keep the binding alive for better 1379 // profile switching. 1380 final var bindingController = getInputMethodBindingController(prevUserId); 1381 bindingController.unbindCurrentMethod(); 1382 1383 unbindCurrentClientLocked(UnbindReason.SWITCH_USER, prevUserId); 1384 1385 // Hereafter we start initializing things for "newUserId". 1386 1387 final var newUserData = getUserData(newUserId); 1388 1389 // TODO(b/342027196): Double check if we need to always reset upon user switching. 1390 newUserData.mLastEnabledInputMethodsStr = ""; 1391 1392 mCurrentImeUserId = newUserId; 1393 final String defaultImiId = SecureSettingsWrapper.getString( 1394 Settings.Secure.DEFAULT_INPUT_METHOD, null, newUserId); 1395 1396 ProtoLog.v(IMMS_DEBUG, "Switching user stage 2/3. newUserId=%s defaultImiId=%s", newUserId, 1397 defaultImiId); 1398 1399 // For secondary users, the list of enabled IMEs may not have been updated since the 1400 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may 1401 // not be empty even if the IME has been uninstalled by the primary user. 1402 // Even in such cases, IMMS works fine because it will find the most applicable 1403 // IME for that user. 1404 final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); 1405 1406 final InputMethodSettings newSettings = InputMethodSettingsRepository.get(newUserId); 1407 postInputMethodSettingUpdatedLocked(initialUserSwitch /* resetDefaultEnabledIme */, 1408 newUserId); 1409 if (TextUtils.isEmpty(newSettings.getSelectedInputMethod())) { 1410 // This is the first time of the user switch and 1411 // set the current ime to the proper one. 1412 resetDefaultImeLocked(mContext, newUserId); 1413 } 1414 updateFromSettingsLocked(true, newUserId); 1415 1416 // Special workaround for b/356879517. 1417 // KeyboardLayoutManager still expects onInputMethodSubtypeChangedForKeyboardLayoutMapping 1418 // to be called back upon IME user switching, while we are actively deprecating the concept 1419 // of "current IME user" at b/350386877. 1420 // TODO(b/356879517): Come up with a way to avoid this special handling. 1421 if (newUserData.mSubtypeForKeyboardLayoutMapping != null) { 1422 final var subtypeHandleAndSubtype = newUserData.mSubtypeForKeyboardLayoutMapping; 1423 mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping( 1424 newUserId, subtypeHandleAndSubtype.first, subtypeHandleAndSubtype.second); 1425 } 1426 1427 if (initialUserSwitch) { 1428 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( 1429 getPackageManagerForUser(mContext, newUserId), 1430 newSettings.getEnabledInputMethodList()); 1431 } 1432 1433 ProtoLog.v(IMMS_DEBUG, "Switching user stage 3/3. newUserId=%s selectedIme=%s", newUserId, 1434 newSettings.getSelectedInputMethod()); 1435 1436 if (mIsInteractive && clientToBeReset != null) { 1437 final ClientState cs = mClientController.getClient(clientToBeReset.asBinder()); 1438 if (cs == null) { 1439 // The client is already gone. 1440 return; 1441 } 1442 cs.mClient.scheduleStartInputIfNecessary(newUserData.mInFullscreenMode); 1443 } 1444 } 1445 waitForUserInitialization()1446 private void waitForUserInitialization() { 1447 final int[] userIds = mUserManagerInternal.getUserIds(); 1448 final long deadlineNanos = SystemClock.elapsedRealtimeNanos() 1449 + TimeUnit.MILLISECONDS.toNanos(SYSTEM_READY_USER_INIT_TIMEOUT); 1450 boolean interrupted = false; 1451 try { 1452 for (int userId : userIds) { 1453 final var latch = getUserData(userId).mBackgroundLoadLatch; 1454 boolean awaitResult; 1455 while (true) { 1456 try { 1457 final long remainingNanos = 1458 Math.max(deadlineNanos - SystemClock.elapsedRealtimeNanos(), 0); 1459 awaitResult = latch.await(remainingNanos, TimeUnit.NANOSECONDS); 1460 break; 1461 } catch (InterruptedException ignored) { 1462 interrupted = true; 1463 } 1464 } 1465 if (!awaitResult) { 1466 Slog.w(TAG, "Timed out for user#" + userId + " to be initialized"); 1467 } 1468 } 1469 } finally { 1470 if (interrupted) { 1471 Thread.currentThread().interrupt(); 1472 } 1473 } 1474 } 1475 1476 /** 1477 * TODO(b/32343335): The entire systemRunning() method needs to be revisited. 1478 */ systemRunning()1479 public void systemRunning() { 1480 waitForUserInitialization(); 1481 1482 synchronized (ImfLock.class) { 1483 ProtoLog.v(IMMS_DEBUG, "--- systemReady"); 1484 if (!mSystemReady) { 1485 mSystemReady = true; 1486 final int currentImeUserId = mCurrentImeUserId; 1487 mStatusBarManagerInternal = 1488 LocalServices.getService(StatusBarManagerInternal.class); 1489 hideStatusBarIconLocked(currentImeUserId); 1490 final var bindingController = getInputMethodBindingController(currentImeUserId); 1491 updateSystemUiLocked(bindingController.getImeWindowVis(), 1492 bindingController.getBackDisposition(), currentImeUserId); 1493 mShowOngoingImeSwitcherForPhones = mRes.getBoolean( 1494 com.android.internal.R.bool.show_ongoing_ime_switcher); 1495 if (mShowOngoingImeSwitcherForPhones) { 1496 mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(available -> { 1497 mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, 1498 available ? 1 : 0, 0 /* unused */).sendToTarget(); 1499 }); 1500 } 1501 1502 mMyPackageMonitor.register(mContext, UserHandle.ALL, mIoHandler); 1503 SecureSettingsChangeCallback.register(mHandler, mContext.getContentResolver(), 1504 new String[] { 1505 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 1506 Settings.Secure.DEFAULT_INPUT_METHOD, 1507 Settings.Secure.ENABLED_INPUT_METHODS, 1508 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, 1509 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, 1510 Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1511 }, (key, flags, userId) -> { 1512 synchronized (ImfLock.class) { 1513 onSecureSettingsChangedLocked(key, userId); 1514 } 1515 }); 1516 1517 final IntentFilter broadcastFilterForAllUsers = new IntentFilter(); 1518 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1519 mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(), 1520 UserHandle.ALL, broadcastFilterForAllUsers, null, null, 1521 Context.RECEIVER_EXPORTED); 1522 1523 AdditionalSubtypeMapRepository.startWriterThread(); 1524 1525 for (int userId : mUserManagerInternal.getUserIds()) { 1526 onUserReadyLocked(userId); 1527 } 1528 } 1529 } 1530 } 1531 1532 @GuardedBy("ImfLock.class") onUserReadyLocked(@serIdInt int userId)1533 void onUserReadyLocked(@UserIdInt int userId) { 1534 if (!mUserManagerInternal.isUserRunning(userId)) { 1535 return; 1536 } 1537 1538 final String defaultImiId = SecureSettingsWrapper.getString( 1539 Settings.Secure.DEFAULT_INPUT_METHOD, null, userId); 1540 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); 1541 final var settings = InputMethodSettingsRepository.get(userId); 1542 postInputMethodSettingUpdatedLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */, 1543 userId); 1544 updateFromSettingsLocked(true, userId); 1545 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( 1546 getPackageManagerForUser(mContext, userId), settings.getEnabledInputMethodList()); 1547 } 1548 registerImeRequestedChangedListener()1549 void registerImeRequestedChangedListener() { 1550 mWindowManagerInternal.setOnImeRequestedChangedListener( 1551 (windowToken, imeVisible, statsToken) -> { 1552 if (Flags.refactorInsetsController()) { 1553 if (imeVisible) { 1554 showCurrentInputInternal(windowToken, statsToken); 1555 } else { 1556 hideCurrentInputInternal(windowToken, statsToken); 1557 } 1558 } 1559 }); 1560 } 1561 1562 @BinderThread 1563 @Nullable 1564 @Override getCurrentInputMethodInfoAsUser(@serIdInt int userId)1565 public InputMethodInfo getCurrentInputMethodInfoAsUser(@UserIdInt int userId) { 1566 if (UserHandle.getCallingUserId() != userId) { 1567 mContext.enforceCallingOrSelfPermission( 1568 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1569 } 1570 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 1571 final String selectedImeId; 1572 if (Flags.consistentGetCurrentInputMethodInfo()) { 1573 final var bindingController = getInputMethodBindingController(userId); 1574 synchronized (ImfLock.class) { 1575 selectedImeId = bindingController.getSelectedMethodId(); 1576 } 1577 } else { 1578 selectedImeId = settings.getSelectedInputMethod(); 1579 } 1580 return settings.getMethodMap().get(selectedImeId); 1581 } 1582 1583 @BinderThread 1584 @NonNull 1585 @Override getInputMethodList(@serIdInt int userId, @DirectBootAwareness int directBootAwareness)1586 public InputMethodInfoSafeList getInputMethodList(@UserIdInt int userId, 1587 @DirectBootAwareness int directBootAwareness) { 1588 if (UserHandle.getCallingUserId() != userId) { 1589 mContext.enforceCallingOrSelfPermission( 1590 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1591 } 1592 if (!mUserManagerInternal.exists(userId)) { 1593 return InputMethodInfoSafeList.empty(); 1594 } 1595 final int callingUid = Binder.getCallingUid(); 1596 final long ident = Binder.clearCallingIdentity(); 1597 try { 1598 return InputMethodInfoSafeList.create(getInputMethodListInternal( 1599 userId, directBootAwareness, callingUid)); 1600 } finally { 1601 Binder.restoreCallingIdentity(ident); 1602 } 1603 } 1604 1605 @BinderThread 1606 @NonNull 1607 @Override getEnabledInputMethodList(@serIdInt int userId)1608 public InputMethodInfoSafeList getEnabledInputMethodList(@UserIdInt int userId) { 1609 if (UserHandle.getCallingUserId() != userId) { 1610 mContext.enforceCallingOrSelfPermission( 1611 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1612 } 1613 if (!mUserManagerInternal.exists(userId)) { 1614 return InputMethodInfoSafeList.empty(); 1615 } 1616 final int callingUid = Binder.getCallingUid(); 1617 final long ident = Binder.clearCallingIdentity(); 1618 try { 1619 return InputMethodInfoSafeList.create( 1620 getEnabledInputMethodListInternal(userId, callingUid)); 1621 } finally { 1622 Binder.restoreCallingIdentity(ident); 1623 } 1624 } 1625 1626 @BinderThread 1627 @NonNull 1628 @Override getInputMethodListLegacy(@serIdInt int userId, @DirectBootAwareness int directBootAwareness)1629 public List<InputMethodInfo> getInputMethodListLegacy(@UserIdInt int userId, 1630 @DirectBootAwareness int directBootAwareness) { 1631 if (UserHandle.getCallingUserId() != userId) { 1632 mContext.enforceCallingOrSelfPermission( 1633 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1634 } 1635 if (!mUserManagerInternal.exists(userId)) { 1636 return Collections.emptyList(); 1637 } 1638 final int callingUid = Binder.getCallingUid(); 1639 final long ident = Binder.clearCallingIdentity(); 1640 try { 1641 return getInputMethodListInternal(userId, directBootAwareness, callingUid); 1642 } finally { 1643 Binder.restoreCallingIdentity(ident); 1644 } 1645 } 1646 1647 @BinderThread 1648 @NonNull 1649 @Override getEnabledInputMethodListLegacy(@serIdInt int userId)1650 public List<InputMethodInfo> getEnabledInputMethodListLegacy(@UserIdInt int userId) { 1651 if (UserHandle.getCallingUserId() != userId) { 1652 mContext.enforceCallingOrSelfPermission( 1653 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1654 } 1655 if (!mUserManagerInternal.exists(userId)) { 1656 return Collections.emptyList(); 1657 } 1658 final int callingUid = Binder.getCallingUid(); 1659 final long ident = Binder.clearCallingIdentity(); 1660 try { 1661 return getEnabledInputMethodListInternal(userId, callingUid); 1662 } finally { 1663 Binder.restoreCallingIdentity(ident); 1664 } 1665 } 1666 1667 @Override isStylusHandwritingAvailableAsUser( @serIdInt int userId, boolean connectionless)1668 public boolean isStylusHandwritingAvailableAsUser( 1669 @UserIdInt int userId, boolean connectionless) { 1670 if (UserHandle.getCallingUserId() != userId) { 1671 mContext.enforceCallingOrSelfPermission( 1672 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1673 } 1674 1675 synchronized (ImfLock.class) { 1676 if (!isStylusHandwritingEnabled(mContext, userId)) { 1677 return false; 1678 } 1679 1680 // Check if selected IME of current user supports handwriting. 1681 if (userId == mCurrentImeUserId) { 1682 final var bindingController = getInputMethodBindingController(userId); 1683 return bindingController.supportsStylusHandwriting() 1684 && (!connectionless 1685 || bindingController.supportsConnectionlessStylusHandwriting()); 1686 } 1687 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 1688 final InputMethodInfo imi = settings.getMethodMap().get( 1689 settings.getSelectedInputMethod()); 1690 return imi != null && imi.supportsStylusHandwriting() 1691 && (!connectionless || imi.supportsConnectionlessStylusHandwriting()); 1692 } 1693 } 1694 isStylusHandwritingEnabled( @onNull Context context, @UserIdInt int userId)1695 private boolean isStylusHandwritingEnabled( 1696 @NonNull Context context, @UserIdInt int userId) { 1697 // If user is a profile, use preference of it`s parent profile. 1698 final int profileParentUserId = mUserManagerInternal.getProfileParentId(userId); 1699 if (Settings.Secure.getIntForUser(context.getContentResolver(), 1700 Settings.Secure.STYLUS_HANDWRITING_ENABLED, 1701 Settings.Secure.STYLUS_HANDWRITING_DEFAULT_VALUE, profileParentUserId) == 0) { 1702 return false; 1703 } 1704 return true; 1705 } 1706 getInputMethodListInternal(@serIdInt int userId, @DirectBootAwareness int directBootAwareness, int callingUid)1707 private List<InputMethodInfo> getInputMethodListInternal(@UserIdInt int userId, 1708 @DirectBootAwareness int directBootAwareness, int callingUid) { 1709 final var userData = getUserData(userId); 1710 final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( 1711 AdditionalSubtypeMapRepository.get(userId), directBootAwareness, 1712 userData.mIsUnlockingOrUnlocked.get()); 1713 final var settings = InputMethodSettings.create(methodMap, userId); 1714 // Create a copy. 1715 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(settings.getMethodList()); 1716 // filter caller's access to input methods 1717 methodList.removeIf(imi -> 1718 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); 1719 return methodList; 1720 } 1721 getEnabledInputMethodListInternal(@serIdInt int userId, int callingUid)1722 private List<InputMethodInfo> getEnabledInputMethodListInternal(@UserIdInt int userId, 1723 int callingUid) { 1724 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 1725 final ArrayList<InputMethodInfo> methodList = settings.getEnabledInputMethodList(); 1726 // filter caller's access to input methods 1727 methodList.removeIf(imi -> 1728 !canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)); 1729 return methodList; 1730 } 1731 1732 /** 1733 * Gets enabled subtypes of the specified {@link InputMethodInfo}. 1734 * 1735 * @param imiId if null, returns enabled subtypes for the current 1736 * {@link InputMethodInfo} 1737 * @param allowsImplicitlyEnabledSubtypes {@code true} to return the implicitly enabled 1738 * subtypes 1739 * @param userId the user ID to be queried about 1740 */ 1741 @Override getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId)1742 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, 1743 boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { 1744 if (UserHandle.getCallingUserId() != userId) { 1745 mContext.enforceCallingOrSelfPermission( 1746 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1747 } 1748 1749 final int callingUid = Binder.getCallingUid(); 1750 final long ident = Binder.clearCallingIdentity(); 1751 try { 1752 return getEnabledInputMethodSubtypeListInternal(imiId, 1753 allowsImplicitlyEnabledSubtypes, userId, callingUid); 1754 } finally { 1755 Binder.restoreCallingIdentity(ident); 1756 } 1757 } 1758 getEnabledInputMethodSubtypeListInternal(String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid)1759 private List<InputMethodSubtype> getEnabledInputMethodSubtypeListInternal(String imiId, 1760 boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId, int callingUid) { 1761 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 1762 final InputMethodInfo imi = settings.getMethodMap().get(imiId); 1763 if (imi == null) { 1764 return Collections.emptyList(); 1765 } 1766 if (!canCallerAccessInputMethod(imi.getPackageName(), callingUid, userId, settings)) { 1767 return Collections.emptyList(); 1768 } 1769 return settings.getEnabledInputMethodSubtypeList( 1770 imi, allowsImplicitlyEnabledSubtypes); 1771 } 1772 1773 /** 1774 * Called by each application process as a preparation to start interacting with 1775 * {@link InputMethodManagerService}. 1776 * 1777 * <p>As a general principle, IPCs from the application process that take 1778 * {@link IInputMethodClient} will be rejected without this step.</p> 1779 * 1780 * @param client {@link android.os.Binder} proxy that is associated with the 1781 * singleton instance of 1782 * {@link android.view.inputmethod.InputMethodManager} that runs 1783 * on the client process 1784 * @param fallbackInputConnection communication channel for the fallback {@link InputConnection} 1785 * @param selfReportedDisplayId self-reported display ID to which the client is associated. 1786 * Whether the client is still allowed to access to this display 1787 * or not needs to be evaluated every time the client interacts 1788 * with the display 1789 */ 1790 @Override addClient(@onNull IInputMethodClient client, @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId)1791 public void addClient(@NonNull IInputMethodClient client, 1792 @NonNull IRemoteInputConnection fallbackInputConnection, int selfReportedDisplayId) { 1793 Objects.requireNonNull(client, "client must not be null"); 1794 Objects.requireNonNull(fallbackInputConnection, "fallbackInputConnection must not be null"); 1795 // Here there are two scenarios where this method is called: 1796 // A. IMM is being instantiated in a different process and this is an IPC from that process 1797 // B. IMM is being instantiated in the same process but Binder.clearCallingIdentity() is 1798 // called in the caller side if necessary. 1799 // In either case the following UID/PID should be the ones where InputMethodManager is 1800 // actually running. 1801 final int callerUid = Binder.getCallingUid(); 1802 final int callerPid = Binder.getCallingPid(); 1803 final var clientInvoker = IInputMethodClientInvoker.create(client, mHandler); 1804 synchronized (ImfLock.class) { 1805 mClientController.addClient(clientInvoker, fallbackInputConnection, 1806 selfReportedDisplayId, callerUid, callerPid); 1807 } 1808 } 1809 1810 @GuardedBy("ImfLock.class") onClientRemoved(@onNull ClientState client)1811 private void onClientRemoved(@NonNull ClientState client) { 1812 clearClientSessionLocked(client); 1813 clearClientSessionForAccessibilityLocked(client); 1814 // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. 1815 @SuppressWarnings("GuardedBy") Consumer<UserData> clientRemovedForUser = 1816 userData -> onClientRemovedInternalLocked(client, userData); 1817 mUserDataRepository.forAllUserData(clientRemovedForUser); 1818 } 1819 1820 /** 1821 * Hide the IME if the removed user is the current user. 1822 */ 1823 // TODO(b/325515685): Move this method to InputMethodBindingController 1824 @GuardedBy("ImfLock.class") onClientRemovedInternalLocked(@onNull ClientState client, @NonNull UserData userData)1825 private void onClientRemovedInternalLocked(@NonNull ClientState client, 1826 @NonNull UserData userData) { 1827 final int userId = userData.mUserId; 1828 if (userData.mCurClient == client) { 1829 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, 1830 SoftInputShowHideReason.HIDE_REMOVE_CLIENT, userId); 1831 if (userData.mBoundToMethod) { 1832 userData.mBoundToMethod = false; 1833 final var userBindingController = userData.mBindingController; 1834 IInputMethodInvoker curMethod = userBindingController.getCurMethod(); 1835 if (curMethod != null) { 1836 // When we unbind input, we are unbinding the client, so we always 1837 // unbind ime and a11y together. 1838 curMethod.unbindInput(); 1839 AccessibilityManagerInternal.get().unbindInput(); 1840 } 1841 } 1842 userData.mBoundToAccessibility = false; 1843 userData.mCurClient = null; 1844 if (userData.mImeBindingState.mFocusedWindowClient == client) { 1845 userData.mImeBindingState = ImeBindingState.newEmptyState(); 1846 } 1847 } 1848 } 1849 1850 @Nullable 1851 @GuardedBy("ImfLock.class") 1852 @Override getClientStateLocked(IInputMethodClient client)1853 public ClientState getClientStateLocked(IInputMethodClient client) { 1854 return mClientController.getClient(client.asBinder()); 1855 } 1856 1857 @GuardedBy("ImfLock.class") unbindCurrentClientLocked(@nbindReason int unbindClientReason, @UserIdInt int userId)1858 void unbindCurrentClientLocked(@UnbindReason int unbindClientReason, @UserIdInt int userId) { 1859 final var userData = getUserData(userId); 1860 if (userData.mCurClient != null) { 1861 ProtoLog.v(IMMS_DEBUG, "unbindCurrentInputLocked: client=%s", 1862 userData.mCurClient.mClient.asBinder()); 1863 final var bindingController = userData.mBindingController; 1864 if (userData.mBoundToMethod) { 1865 userData.mBoundToMethod = false; 1866 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 1867 if (curMethod != null) { 1868 curMethod.unbindInput(); 1869 } 1870 } 1871 userData.mBoundToAccessibility = false; 1872 1873 // Since we set active false to current client and set mCurClient to null, let's unbind 1874 // all accessibility too. That means, when input method get disconnected (including 1875 // switching ime), we also unbind accessibility 1876 userData.mCurClient.mClient.setActive(false /* active */, false /* fullscreen */); 1877 1878 userData.mCurClient.mClient.onUnbindMethod(bindingController.getSequenceNumber(), 1879 unbindClientReason); 1880 userData.mCurClient.mSessionRequested = false; 1881 userData.mCurClient.mSessionRequestedForAccessibility = false; 1882 userData.mCurClient = null; 1883 ImeTracker.forLogging().onFailed(userData.mCurStatsToken, 1884 ImeTracker.PHASE_SERVER_WAIT_IME); 1885 userData.mCurStatsToken = null; 1886 // TODO: Make mMenuController multi-user aware 1887 if (Flags.imeSwitcherRevamp()) { 1888 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); 1889 } else { 1890 mMenuController.hideInputMethodMenuLocked(userId); 1891 } 1892 } 1893 } 1894 1895 /** 1896 * TODO(b/338404383) Remove 1897 * Called when {@link #resetCurrentMethodAndClientLocked(int, int)} invoked for clean-up states 1898 * before unbinding the current method. 1899 */ 1900 @GuardedBy("ImfLock.class") onUnbindCurrentMethodByReset(@serIdInt int userId)1901 void onUnbindCurrentMethodByReset(@UserIdInt int userId) { 1902 final var userData = getUserData(userId); 1903 final var visibilityStateComputer = userData.mVisibilityStateComputer; 1904 final ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull( 1905 userData.mImeBindingState.mFocusedWindow); 1906 if (winState != null && !winState.isRequestedImeVisible() 1907 && !visibilityStateComputer.isInputShown()) { 1908 // Normally, the focus window will apply the IME visibility state to 1909 // WindowManager when the IME has applied it. But it would be too late when 1910 // switching IMEs in between different users. (Since the focused IME will 1911 // first unbind the service to switch to bind the next user of the IME 1912 // service, that wouldn't make the attached IME token validity check in time) 1913 // As a result, we have to notify WM to apply IME visibility before clearing the 1914 // binding states in the first place. 1915 final var statsToken = createStatsTokenForFocusedClient(false /* show */, 1916 SoftInputShowHideReason.UNBIND_CURRENT_METHOD, userId); 1917 mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, 1918 statsToken, STATE_HIDE_IME, SoftInputShowHideReason.NOT_SET /* ignore reason */, 1919 userId); 1920 } 1921 } 1922 1923 @GuardedBy("ImfLock.class") isShowRequestedForCurrentWindow(@serIdInt int userId)1924 private boolean isShowRequestedForCurrentWindow(@UserIdInt int userId) { 1925 final var userData = getUserData(userId); 1926 final var visibilityStateComputer = userData.mVisibilityStateComputer; 1927 final ImeTargetWindowState state = visibilityStateComputer.getWindowStateOrNull( 1928 userData.mImeBindingState.mFocusedWindow); 1929 return state != null && state.isRequestedImeVisible(); 1930 } 1931 1932 @GuardedBy("ImfLock.class") 1933 @NonNull attachNewInputLocked(@tartInputReason int startInputReason, boolean initial, @UserIdInt int userId)1934 InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial, 1935 @UserIdInt int userId) { 1936 final var userData = getUserData(userId); 1937 final var bindingController = userData.mBindingController; 1938 if (!userData.mBoundToMethod) { 1939 bindingController.getCurMethod().bindInput(userData.mCurClient.mBinding); 1940 userData.mBoundToMethod = true; 1941 } 1942 1943 final boolean restarting = !initial; 1944 final Binder startInputToken = new Binder(); 1945 final StartInputInfo info = new StartInputInfo(userId, 1946 bindingController.getCurToken(), bindingController.getCurTokenDisplayId(), 1947 bindingController.getCurId(), startInputReason, 1948 restarting, UserHandle.getUserId(userData.mCurClient.mUid), 1949 userData.mCurClient.mSelfReportedDisplayId, 1950 userData.mImeBindingState.mFocusedWindow, userData.mCurEditorInfo, 1951 userData.mImeBindingState.mFocusedWindowSoftInputMode, 1952 bindingController.getSequenceNumber()); 1953 mImeTargetWindowMap.put(startInputToken, userData.mImeBindingState.mFocusedWindow); 1954 mStartInputHistory.addEntry(info); 1955 1956 // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user 1957 // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the 1958 // same-user scenarios. 1959 // That said ignoring cross-user scenario will never affect IMEs that do not have 1960 // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. 1961 if (userId == UserHandle.getUserId(userData.mCurClient.mUid)) { 1962 mPackageManagerInternal.grantImplicitAccess(userId, null /* intent */, 1963 UserHandle.getAppId(bindingController.getCurMethodUid()), 1964 userData.mCurClient.mUid, true /* direct */); 1965 } 1966 1967 @InputMethodNavButtonFlags final int navButtonFlags = 1968 getInputMethodNavButtonFlagsLocked(userData); 1969 final SessionState session = userData.mCurClient.mCurSession; 1970 setEnabledSessionLocked(session, userData); 1971 session.mMethod.startInput(startInputToken, userData.mCurInputConnection, 1972 userData.mCurEditorInfo, restarting, navButtonFlags, userData.mCurImeDispatcher); 1973 if (Flags.refactorInsetsController()) { 1974 if (isShowRequestedForCurrentWindow(userId) 1975 && userData.mImeBindingState.mFocusedWindow != null) { 1976 // Re-use current statsToken, if it exists. 1977 final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken 1978 : createStatsTokenForFocusedClient(true /* show */, 1979 SoftInputShowHideReason.ATTACH_NEW_INPUT, userId); 1980 userData.mCurStatsToken = null; 1981 showCurrentInputInternal(userData.mImeBindingState.mFocusedWindow, statsToken); 1982 } 1983 } else { 1984 if (isShowRequestedForCurrentWindow(userId)) { 1985 ProtoLog.v(IMMS_DEBUG, "Attach new input asks to show input"); 1986 // Re-use current statsToken, if it exists. 1987 final var statsToken = userData.mCurStatsToken != null ? userData.mCurStatsToken 1988 : createStatsTokenForFocusedClient(true /* show */, 1989 SoftInputShowHideReason.ATTACH_NEW_INPUT, userId); 1990 userData.mCurStatsToken = null; 1991 final var visibilityStateComputer = userData.mVisibilityStateComputer; 1992 showCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, statsToken, 1993 visibilityStateComputer.getShowFlags(), MotionEvent.TOOL_TYPE_UNKNOWN, 1994 null /* resultReceiver */, SoftInputShowHideReason.ATTACH_NEW_INPUT, 1995 userId); 1996 } 1997 } 1998 1999 final var curId = bindingController.getCurId(); 2000 final InputMethodInfo curInputMethodInfo = InputMethodSettingsRepository.get(userId) 2001 .getMethodMap().get(curId); 2002 final boolean suppressesSpellChecker = 2003 curInputMethodInfo != null && curInputMethodInfo.suppressesSpellChecker(); 2004 final SparseArray<IAccessibilityInputMethodSession> accessibilityInputMethodSessions = 2005 createAccessibilityInputMethodSessions( 2006 userData.mCurClient.mAccessibilitySessions); 2007 if (bindingController.supportsStylusHandwriting() && hasSupportedStylusLocked()) { 2008 mHwController.setInkWindowInitializer(new InkWindowInitializer()); 2009 } 2010 return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, 2011 session.mSession, accessibilityInputMethodSessions, 2012 (session.mChannel != null ? session.mChannel.dup() : null), 2013 curId, bindingController.getSequenceNumber(), suppressesSpellChecker); 2014 } 2015 2016 @GuardedBy("ImfLock.class") attachNewAccessibilityLocked(@tartInputReason int startInputReason, boolean initial, @UserIdInt int userId)2017 private void attachNewAccessibilityLocked(@StartInputReason int startInputReason, 2018 boolean initial, @UserIdInt int userId) { 2019 final var userData = getUserData(userId); 2020 2021 if (!userData.mBoundToAccessibility) { 2022 AccessibilityManagerInternal.get().bindInput(); 2023 userData.mBoundToAccessibility = true; 2024 } 2025 2026 // TODO(b/187453053): grantImplicitAccess to accessibility services access? if so, need to 2027 // record accessibility services uid. 2028 2029 // We don't start input when session for a11y is created. We start input when 2030 // input method start input, a11y manager service is always on. 2031 if (startInputReason != StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY) { 2032 setEnabledSessionForAccessibilityLocked(userData.mCurClient.mAccessibilitySessions, 2033 userData); 2034 AccessibilityManagerInternal.get().startInput( 2035 userData.mCurRemoteAccessibilityInputConnection, 2036 userData.mCurEditorInfo, !initial /* restarting */); 2037 } 2038 } 2039 2040 @NonNull createAccessibilityInputMethodSessions( @onNull SparseArray<AccessibilitySessionState> accessibilitySessions)2041 private SparseArray<IAccessibilityInputMethodSession> createAccessibilityInputMethodSessions( 2042 @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions) { 2043 final var accessibilityInputMethodSessions = 2044 new SparseArray<IAccessibilityInputMethodSession>(); 2045 for (int i = 0; i < accessibilitySessions.size(); i++) { 2046 accessibilityInputMethodSessions.append(accessibilitySessions.keyAt(i), 2047 accessibilitySessions.valueAt(i).mSession); 2048 } 2049 return accessibilityInputMethodSessions; 2050 } 2051 2052 /** 2053 * Called by {@link #startInputOrWindowGainedFocusInternalLocked} to bind/unbind/attach the 2054 * selected InputMethod to the given focused IME client. 2055 * 2056 * Note that this should be called after validating if the IME client has IME focus. 2057 * 2058 * @see WindowManagerInternal#hasInputMethodClientFocus(IBinder, int, int, int) 2059 */ 2060 @GuardedBy("ImfLock.class") 2061 @NonNull startInputUncheckedLocked(@onNull ClientState cs, IRemoteInputConnection inputConnection, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason, int unverifiedTargetSdkVersion, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull InputMethodBindingController bindingController)2062 private InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, 2063 IRemoteInputConnection inputConnection, 2064 @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, 2065 @NonNull EditorInfo editorInfo, @StartInputFlags int startInputFlags, 2066 @StartInputReason int startInputReason, 2067 int unverifiedTargetSdkVersion, 2068 @NonNull ImeOnBackInvokedDispatcher imeDispatcher, 2069 @NonNull InputMethodBindingController bindingController) { 2070 2071 final int userId = bindingController.getUserId(); 2072 final var userData = getUserData(userId); 2073 final var visibilityStateComputer = userData.mVisibilityStateComputer; 2074 2075 // Compute the final shown display ID with validated cs.selfReportedDisplayId for this 2076 // session & other conditions. 2077 ImeTargetWindowState winState = visibilityStateComputer.getWindowStateOrNull( 2078 userData.mImeBindingState.mFocusedWindow); 2079 if (winState == null) { 2080 return InputBindResult.NOT_IME_TARGET_WINDOW; 2081 } 2082 final int csDisplayId = cs.mSelfReportedDisplayId; 2083 bindingController.setDisplayIdToShowIme( 2084 visibilityStateComputer.computeImeDisplayId(winState, csDisplayId)); 2085 2086 // Potentially override the selected input method if the new display belongs to a virtual 2087 // device with a custom IME. 2088 String selectedMethodId = bindingController.getSelectedMethodId(); 2089 final String deviceMethodId = computeCurrentDeviceMethodIdLocked( 2090 bindingController.getUserId(), selectedMethodId); 2091 if (deviceMethodId == null) { 2092 visibilityStateComputer.getImePolicy().setImeHiddenByDisplayPolicy(true); 2093 } else if (!Objects.equals(deviceMethodId, selectedMethodId)) { 2094 setInputMethodLocked(deviceMethodId, NOT_A_SUBTYPE_INDEX, 2095 bindingController.getDeviceIdToShowIme(), userId); 2096 selectedMethodId = deviceMethodId; 2097 } 2098 2099 if (visibilityStateComputer.getImePolicy().isImeHiddenByDisplayPolicy()) { 2100 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, 2101 SoftInputShowHideReason.HIDE_DISPLAY_IME_POLICY_HIDE, userId); 2102 return InputBindResult.NO_IME; 2103 } 2104 2105 // If no method is currently selected, do nothing. 2106 if (selectedMethodId == null) { 2107 return InputBindResult.NO_IME; 2108 } 2109 2110 if (userData.mCurClient != cs) { 2111 prepareClientSwitchLocked(cs, userId); 2112 } 2113 2114 final boolean connectionWasActive = userData.mCurInputConnection != null; 2115 2116 // Bump up the sequence for this client and attach it. 2117 bindingController.advanceSequenceNumber(); 2118 2119 userData.mCurClient = cs; 2120 userData.mCurInputConnection = inputConnection; 2121 userData.mCurRemoteAccessibilityInputConnection = remoteAccessibilityInputConnection; 2122 userData.mCurImeDispatcher = imeDispatcher; 2123 // Override the locale hints if the app is running on a virtual device. 2124 if (mVdmInternal == null) { 2125 mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); 2126 } 2127 if (mVdmInternal != null && editorInfo.hintLocales == null) { 2128 LocaleList hintsFromVirtualDevice = mVdmInternal.getPreferredLocaleListForUid(cs.mUid); 2129 if (hintsFromVirtualDevice != null) { 2130 editorInfo.hintLocales = hintsFromVirtualDevice; 2131 } 2132 } 2133 userData.mCurEditorInfo = editorInfo; 2134 2135 // Notify input manager if the connection state changes. 2136 final boolean connectionIsActive = userData.mCurInputConnection != null; 2137 if (connectionIsActive != connectionWasActive) { 2138 mInputManagerInternal.notifyInputMethodConnectionActive(connectionIsActive); 2139 } 2140 2141 // If configured, we want to avoid starting up the IME if it is not supposed to be showing 2142 if (shouldPreventImeStartupLocked(selectedMethodId, startInputFlags, 2143 unverifiedTargetSdkVersion, userId)) { 2144 ProtoLog.v(IMMS_DEBUG, "Avoiding IME startup and unbinding current input method."); 2145 bindingController.invalidateAutofillSession(); 2146 bindingController.unbindCurrentMethod(); 2147 return InputBindResult.NO_EDITOR; 2148 } 2149 2150 // Check if the input method is changing. 2151 // We expect the caller has already verified that the client is allowed to access this 2152 // display ID. 2153 final String curId = bindingController.getCurId(); 2154 final int displayIdToShowIme = bindingController.getDisplayIdToShowIme(); 2155 if (curId != null && curId.equals(bindingController.getSelectedMethodId()) 2156 && displayIdToShowIme == bindingController.getCurTokenDisplayId()) { 2157 if (cs.mCurSession != null) { 2158 // Fast case: if we are already connected to the input method, 2159 // then just return it. 2160 // This doesn't mean a11y sessions are there. When a11y service is 2161 // enabled while this client is switched out, this client doesn't have the session. 2162 // A11yManagerService will only request missing sessions (will not request existing 2163 // sessions again). Note when an a11y service is disabled, it will clear its 2164 // session from all clients, so we don't need to worry about disabled a11y services. 2165 cs.mSessionRequestedForAccessibility = false; 2166 requestClientSessionForAccessibilityLocked(cs); 2167 // we can always attach to accessibility because AccessibilityManagerService is 2168 // always on. 2169 attachNewAccessibilityLocked(startInputReason, 2170 (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId); 2171 return attachNewInputLocked(startInputReason, 2172 (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0, userId); 2173 } 2174 2175 InputBindResult bindResult = tryReuseConnectionLocked(bindingController, cs, userId); 2176 if (bindResult != null) { 2177 return bindResult; 2178 } 2179 } 2180 2181 bindingController.unbindCurrentMethod(); 2182 return bindingController.bindCurrentMethod(); 2183 } 2184 2185 /** 2186 * Update the current deviceId and return the relevant imeId for this device. 2187 * 2188 * <p>1. If the device changes to virtual and its custom IME is not available, then disable 2189 * IME.</p> 2190 * <p>2. If the device changes to virtual with valid custom IME, then return the custom IME. If 2191 * the old device was default, then store the current imeId so it can be restored.</p> 2192 * <p>3. If the device changes to default, restore the default device IME.</p> 2193 * <p>4. Otherwise keep the current imeId.</p> 2194 */ 2195 @GuardedBy("ImfLock.class") computeCurrentDeviceMethodIdLocked(@serIdInt int userId, String currentMethodId)2196 private String computeCurrentDeviceMethodIdLocked(@UserIdInt int userId, 2197 String currentMethodId) { 2198 if (mVdmInternal == null) { 2199 mVdmInternal = LocalServices.getService(VirtualDeviceManagerInternal.class); 2200 } 2201 if (mVdmInternal == null) { 2202 return currentMethodId; 2203 } 2204 2205 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 2206 final var bindingController = getInputMethodBindingController(userId); 2207 final int oldDeviceId = bindingController.getDeviceIdToShowIme(); 2208 final int displayIdToShowIme = bindingController.getDisplayIdToShowIme(); 2209 int newDeviceId = mVdmInternal.getDeviceIdForDisplayId(displayIdToShowIme); 2210 if (newDeviceId != DEVICE_ID_DEFAULT) { 2211 // Only show custom IME on trusted displays. 2212 if (mDisplayManagerInternal == null) { 2213 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); 2214 } 2215 int displayFlags = mDisplayManagerInternal.getDisplayInfo(displayIdToShowIme).flags; 2216 if ((displayFlags & Display.FLAG_TRUSTED) != Display.FLAG_TRUSTED) { 2217 // If the display is not trusted, fallback to the default device IME. 2218 newDeviceId = DEVICE_ID_DEFAULT; 2219 } 2220 } 2221 bindingController.setDeviceIdToShowIme(newDeviceId); 2222 if (newDeviceId == DEVICE_ID_DEFAULT) { 2223 if (oldDeviceId == DEVICE_ID_DEFAULT) { 2224 return currentMethodId; 2225 } 2226 final String defaultDeviceMethodId = settings.getSelectedDefaultDeviceInputMethod(); 2227 ProtoLog.v(IMMS_DEBUG, "Restoring default device input method: %s", 2228 defaultDeviceMethodId); 2229 settings.putSelectedDefaultDeviceInputMethod(null); 2230 return defaultDeviceMethodId; 2231 } 2232 2233 final String deviceMethodId = mVirtualDeviceMethodMap.get(newDeviceId, currentMethodId); 2234 if (Objects.equals(deviceMethodId, currentMethodId)) { 2235 return currentMethodId; 2236 } else if (!settings.getMethodMap().containsKey(deviceMethodId)) { 2237 ProtoLog.v(IMMS_DEBUG, 2238 "Disabling IME on virtual device with id %s because its custom input method " 2239 + "is not available: %s", 2240 newDeviceId, deviceMethodId); 2241 return null; 2242 } 2243 2244 if (oldDeviceId == DEVICE_ID_DEFAULT) { 2245 ProtoLog.v(IMMS_DEBUG, "Storing default device input method %s", currentMethodId); 2246 settings.putSelectedDefaultDeviceInputMethod(currentMethodId); 2247 } 2248 ProtoLog.v(IMMS_DEBUG, 2249 "Switching current input method from %s to device-specific one %s because the " 2250 + "current display %s belongs to device with id %s", 2251 currentMethodId, deviceMethodId, displayIdToShowIme, newDeviceId); 2252 return deviceMethodId; 2253 } 2254 2255 @GuardedBy("ImfLock.class") shouldPreventImeStartupLocked( @onNull String selectedMethodId, @StartInputFlags int startInputFlags, int unverifiedTargetSdkVersion, @UserIdInt int userId)2256 private boolean shouldPreventImeStartupLocked( 2257 @NonNull String selectedMethodId, 2258 @StartInputFlags int startInputFlags, 2259 int unverifiedTargetSdkVersion, 2260 @UserIdInt int userId) { 2261 // Fast-path for the majority of cases 2262 if (!mPreventImeStartupUnlessTextEditor) { 2263 return false; 2264 } 2265 if (isShowRequestedForCurrentWindow(userId)) { 2266 return false; 2267 } 2268 if (isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags)) { 2269 return false; 2270 } 2271 final InputMethodInfo imi = InputMethodSettingsRepository.get(userId) 2272 .getMethodMap().get(selectedMethodId); 2273 if (imi == null) { 2274 return false; 2275 } 2276 if (ArrayUtils.contains(mNonPreemptibleInputMethods, imi.getPackageName())) { 2277 return false; 2278 } 2279 return true; 2280 } 2281 2282 @GuardedBy("ImfLock.class") prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId)2283 private void prepareClientSwitchLocked(ClientState cs, @UserIdInt int userId) { 2284 // If the client is changing, we need to switch over to the new 2285 // one. 2286 unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT, userId); 2287 // If the screen is on, inform the new client it is active 2288 if (mIsInteractive) { 2289 cs.mClient.setActive(true /* active */, false /* fullscreen */); 2290 } 2291 } 2292 2293 @GuardedBy("ImfLock.class") 2294 @Nullable tryReuseConnectionLocked( @onNull InputMethodBindingController bindingController, @NonNull ClientState cs, @UserIdInt int userId)2295 private InputBindResult tryReuseConnectionLocked( 2296 @NonNull InputMethodBindingController bindingController, @NonNull ClientState cs, 2297 @UserIdInt int userId) { 2298 if (bindingController.hasMainConnection()) { 2299 if (bindingController.getCurMethod() != null) { 2300 if (!Flags.useZeroJankProxy()) { 2301 // Return to client, and we will get back with it when 2302 // we have had a session made for it. 2303 requestClientSessionLocked(cs, userId); 2304 requestClientSessionForAccessibilityLocked(cs); 2305 } 2306 return new InputBindResult( 2307 InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, 2308 null, null, null, 2309 bindingController.getCurId(), 2310 bindingController.getSequenceNumber(), false); 2311 } else { 2312 final long lastBindTime = bindingController.getLastBindTime(); 2313 long bindingDuration = SystemClock.uptimeMillis() - lastBindTime; 2314 if (bindingDuration < TIME_TO_RECONNECT) { 2315 // In this case we have connected to the service, but 2316 // don't yet have its interface. If it hasn't been too 2317 // long since we did the connection, we'll return to 2318 // the client and wait to get the service interface so 2319 // we can report back. If it has been too long, we want 2320 // to fall through so we can try a disconnect/reconnect 2321 // to see if we can get back in touch with the service. 2322 return new InputBindResult( 2323 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 2324 null, null, null, 2325 bindingController.getCurId(), 2326 bindingController.getSequenceNumber(), false); 2327 } else { 2328 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 2329 bindingController.getSelectedMethodId(), bindingDuration, 0); 2330 } 2331 } 2332 } 2333 return null; 2334 } 2335 2336 @FunctionalInterface 2337 interface ImeDisplayValidator { 2338 @DisplayImePolicy getDisplayImePolicy(int displayId)2339 int getDisplayImePolicy(int displayId); 2340 } 2341 2342 /** 2343 * Find the display where the IME should be shown. 2344 * 2345 * @param displayId the ID of the display where the IME client target is 2346 * @param checker instance of {@link ImeDisplayValidator} which is used for 2347 * checking display config to adjust the final target display 2348 * @return the ID of the display where the IME should be shown or 2349 * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of 2350 * {@link WindowManager#DISPLAY_IME_POLICY_HIDE} 2351 */ computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker)2352 static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { 2353 return computeImeDisplayIdForTargetInner(displayId, checker, FALLBACK_DISPLAY_ID); 2354 } 2355 2356 /** 2357 * Find the display where the IME should be shown for a visible background user. 2358 * 2359 * @param displayId the ID of the display where the IME client target is 2360 * @param userId the ID of the user who own the IME 2361 * @param checker instance of {@link ImeDisplayValidator} which is used for 2362 * checking display config to adjust the final target display 2363 * @return the ID of the display where the IME should be shown or 2364 * {@link android.view.Display#INVALID_DISPLAY} if the display has an ImePolicy of 2365 * {@link WindowManager#DISPLAY_IME_POLICY_HIDE} 2366 */ computeImeDisplayIdForVisibleBackgroundUserOnAutomotive( int displayId, @UserIdInt int userId, @NonNull ImeDisplayValidator checker)2367 int computeImeDisplayIdForVisibleBackgroundUserOnAutomotive( 2368 int displayId, @UserIdInt int userId, @NonNull ImeDisplayValidator checker) { 2369 // Visible background user can be assigned to a secondary display, not the default display. 2370 // The main display assigned to the user will be used as the fallback display. 2371 final int mainDisplayId = mUserManagerInternal.getMainDisplayAssignedToUser(userId); 2372 return computeImeDisplayIdForTargetInner(displayId, checker, mainDisplayId); 2373 } 2374 computeImeDisplayIdForTargetInner( int displayId, @NonNull ImeDisplayValidator checker, int fallbackDisplayId)2375 private static int computeImeDisplayIdForTargetInner( 2376 int displayId, @NonNull ImeDisplayValidator checker, int fallbackDisplayId) { 2377 if (displayId == fallbackDisplayId || displayId == INVALID_DISPLAY) { 2378 return fallbackDisplayId; 2379 } 2380 2381 // Show IME window on fallback display when the display doesn't support system decorations 2382 // or the display is virtual and isn't owned by system for security concern. 2383 final int result = checker.getDisplayImePolicy(displayId); 2384 if (result == DISPLAY_IME_POLICY_LOCAL) { 2385 return displayId; 2386 } else if (result == DISPLAY_IME_POLICY_HIDE) { 2387 return INVALID_DISPLAY; 2388 } 2389 return fallbackDisplayId; 2390 } 2391 2392 @GuardedBy("ImfLock.class") initializeImeLocked(@onNull IInputMethodInvoker inputMethod, @NonNull IBinder token, @NonNull InputMethodBindingController bindingController)2393 void initializeImeLocked(@NonNull IInputMethodInvoker inputMethod, @NonNull IBinder token, 2394 @NonNull InputMethodBindingController bindingController) { 2395 ProtoLog.v(IMMS_DEBUG, "Sending attach of token: %s for display: %s", token, 2396 bindingController.getCurTokenDisplayId()); 2397 final int userId = bindingController.getUserId(); 2398 final var userData = getUserData(userId); 2399 inputMethod.initializeInternal(token, 2400 new InputMethodPrivilegedOperationsImpl(this, token, userData), 2401 getInputMethodNavButtonFlagsLocked(userData)); 2402 } 2403 2404 @AnyThread scheduleResetStylusHandwriting()2405 void scheduleResetStylusHandwriting() { 2406 mHandler.obtainMessage(MSG_RESET_HANDWRITING).sendToTarget(); 2407 } 2408 2409 @AnyThread schedulePrepareStylusHandwritingDelegation(@serIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName)2410 void schedulePrepareStylusHandwritingDelegation(@UserIdInt int userId, 2411 @NonNull String delegatePackageName, @NonNull String delegatorPackageName) { 2412 mHandler.obtainMessage( 2413 MSG_PREPARE_HANDWRITING_DELEGATION, userId, 0 /* unused */, 2414 new Pair<>(delegatePackageName, delegatorPackageName)).sendToTarget(); 2415 } 2416 2417 @AnyThread scheduleRemoveStylusHandwritingWindow()2418 void scheduleRemoveStylusHandwritingWindow() { 2419 mHandler.obtainMessage(MSG_REMOVE_HANDWRITING_WINDOW).sendToTarget(); 2420 } 2421 2422 @AnyThread scheduleNotifyImeUidToAudioService(int uid)2423 void scheduleNotifyImeUidToAudioService(int uid) { 2424 mHandler.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE); 2425 mHandler.obtainMessage(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid, 0 /* unused */) 2426 .sendToTarget(); 2427 } 2428 2429 @BinderThread onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, InputChannel channel, @UserIdInt int userId)2430 void onSessionCreated(IInputMethodInvoker method, IInputMethodSession session, 2431 InputChannel channel, @UserIdInt int userId) { 2432 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.onSessionCreated"); 2433 try { 2434 synchronized (ImfLock.class) { 2435 if (mUserSwitchHandlerTask != null) { 2436 // We have a pending user-switching task so it's better to just ignore this 2437 // session. 2438 channel.dispose(); 2439 return; 2440 } 2441 final var userData = getUserData(userId); 2442 final var bindingController = userData.mBindingController; 2443 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 2444 if (curMethod != null && method != null 2445 && curMethod.asBinder() == method.asBinder()) { 2446 if (userData.mCurClient != null) { 2447 clearClientSessionLocked(userData.mCurClient); 2448 userData.mCurClient.mCurSession = new SessionState( 2449 userData.mCurClient, method, session, channel, userId); 2450 InputBindResult res = attachNewInputLocked( 2451 StartInputReason.SESSION_CREATED_BY_IME, true, userId); 2452 attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_IME, true, 2453 userId); 2454 if (res.method != null) { 2455 userData.mCurClient.mClient.onBindMethod(res); 2456 } 2457 return; 2458 } 2459 } 2460 } 2461 2462 // Session abandoned. Close its associated input channel. 2463 channel.dispose(); 2464 } finally { 2465 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 2466 } 2467 } 2468 2469 @GuardedBy("ImfLock.class") resetSystemUiLocked(InputMethodBindingController bindingController)2470 void resetSystemUiLocked(InputMethodBindingController bindingController) { 2471 // Set IME window status as invisible when unbinding current method. 2472 final int imeWindowVis = 0; 2473 final int backDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; 2474 bindingController.setImeWindowVis(imeWindowVis); 2475 bindingController.setBackDisposition(backDisposition); 2476 updateSystemUiLocked(imeWindowVis, backDisposition, bindingController.getUserId()); 2477 } 2478 2479 @GuardedBy("ImfLock.class") resetCurrentMethodAndClientLocked(@nbindReason int unbindClientReason, @UserIdInt int userId)2480 void resetCurrentMethodAndClientLocked(@UnbindReason int unbindClientReason, 2481 @UserIdInt int userId) { 2482 final var bindingController = getInputMethodBindingController(userId); 2483 bindingController.setSelectedMethodId(null); 2484 2485 // Callback before clean-up binding states. 2486 // TODO(b/338461930): Check if this is still necessary or not. 2487 onUnbindCurrentMethodByReset(userId); 2488 bindingController.unbindCurrentMethod(); 2489 unbindCurrentClientLocked(unbindClientReason, userId); 2490 } 2491 2492 @GuardedBy("ImfLock.class") reRequestCurrentClientSessionLocked(@serIdInt int userId)2493 void reRequestCurrentClientSessionLocked(@UserIdInt int userId) { 2494 final var userData = getUserData(userId); 2495 if (userData.mCurClient != null) { 2496 clearClientSessionLocked(userData.mCurClient); 2497 clearClientSessionForAccessibilityLocked(userData.mCurClient); 2498 requestClientSessionLocked(userData.mCurClient, userId); 2499 requestClientSessionForAccessibilityLocked(userData.mCurClient); 2500 } 2501 } 2502 2503 @GuardedBy("ImfLock.class") requestClientSessionLocked(ClientState cs, @UserIdInt int userId)2504 void requestClientSessionLocked(ClientState cs, @UserIdInt int userId) { 2505 if (!cs.mSessionRequested) { 2506 ProtoLog.v(IMMS_DEBUG, "Creating new session for client %s", cs); 2507 final InputChannel serverChannel; 2508 final InputChannel clientChannel; 2509 { 2510 final InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); 2511 serverChannel = channels[0]; 2512 clientChannel = channels[1]; 2513 } 2514 2515 cs.mSessionRequested = true; 2516 2517 final var bindingController = getInputMethodBindingController(userId); 2518 final IInputMethodInvoker curMethod = bindingController.getCurMethod(); 2519 final IInputMethodSessionCallback.Stub callback = 2520 new IInputMethodSessionCallback.Stub() { 2521 @Override 2522 public void sessionCreated(IInputMethodSession session) { 2523 final long ident = Binder.clearCallingIdentity(); 2524 try { 2525 onSessionCreated(curMethod, session, serverChannel, userId); 2526 } finally { 2527 Binder.restoreCallingIdentity(ident); 2528 } 2529 } 2530 }; 2531 2532 try { 2533 curMethod.createSession(clientChannel, callback); 2534 } finally { 2535 // Dispose the channel because the remote proxy will get its own copy when 2536 // unparceled. 2537 if (clientChannel != null) { 2538 clientChannel.dispose(); 2539 } 2540 } 2541 } 2542 } 2543 2544 @GuardedBy("ImfLock.class") requestClientSessionForAccessibilityLocked(ClientState cs)2545 void requestClientSessionForAccessibilityLocked(ClientState cs) { 2546 if (!cs.mSessionRequestedForAccessibility) { 2547 ProtoLog.v(IMMS_DEBUG, "Creating new accessibility sessions for client %s", cs); 2548 cs.mSessionRequestedForAccessibility = true; 2549 ArraySet<Integer> ignoreSet = new ArraySet<>(); 2550 for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) { 2551 ignoreSet.add(cs.mAccessibilitySessions.keyAt(i)); 2552 } 2553 AccessibilityManagerInternal.get().createImeSession(ignoreSet); 2554 } 2555 } 2556 2557 @GuardedBy("ImfLock.class") clearClientSessionLocked(@onNull ClientState cs)2558 void clearClientSessionLocked(@NonNull ClientState cs) { 2559 finishSessionLocked(cs.mCurSession); 2560 cs.mCurSession = null; 2561 cs.mSessionRequested = false; 2562 } 2563 2564 @GuardedBy("ImfLock.class") clearClientSessionForAccessibilityLocked(@onNull ClientState cs)2565 void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs) { 2566 for (int i = 0; i < cs.mAccessibilitySessions.size(); i++) { 2567 finishSessionForAccessibilityLocked(cs.mAccessibilitySessions.valueAt(i)); 2568 } 2569 cs.mAccessibilitySessions.clear(); 2570 cs.mSessionRequestedForAccessibility = false; 2571 } 2572 2573 @GuardedBy("ImfLock.class") clearClientSessionForAccessibilityLocked(@onNull ClientState cs, int id)2574 void clearClientSessionForAccessibilityLocked(@NonNull ClientState cs, int id) { 2575 AccessibilitySessionState session = cs.mAccessibilitySessions.get(id); 2576 if (session != null) { 2577 finishSessionForAccessibilityLocked(session); 2578 cs.mAccessibilitySessions.remove(id); 2579 } 2580 } 2581 2582 @GuardedBy("ImfLock.class") finishSessionLocked(@ullable SessionState sessionState)2583 private void finishSessionLocked(@Nullable SessionState sessionState) { 2584 if (sessionState != null) { 2585 if (sessionState.mSession != null) { 2586 try { 2587 sessionState.mSession.finishSession(); 2588 } catch (RemoteException e) { 2589 Slog.w(TAG, "Session failed to close due to remote exception", e); 2590 final int userId = sessionState.mUserId; 2591 final var bindingController = getInputMethodBindingController(userId); 2592 updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(), 2593 userId); 2594 } 2595 sessionState.mSession = null; 2596 } 2597 if (sessionState.mChannel != null) { 2598 sessionState.mChannel.dispose(); 2599 sessionState.mChannel = null; 2600 } 2601 } 2602 } 2603 2604 @GuardedBy("ImfLock.class") finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState)2605 private void finishSessionForAccessibilityLocked(AccessibilitySessionState sessionState) { 2606 if (sessionState != null) { 2607 if (sessionState.mSession != null) { 2608 try { 2609 sessionState.mSession.finishSession(); 2610 } catch (RemoteException e) { 2611 Slog.w(TAG, "Session failed to close due to remote exception", e); 2612 } 2613 sessionState.mSession = null; 2614 } 2615 } 2616 } 2617 2618 @GuardedBy("ImfLock.class") clearClientSessionsLocked(@onNull InputMethodBindingController bindingController)2619 void clearClientSessionsLocked(@NonNull InputMethodBindingController bindingController) { 2620 final int userId = bindingController.getUserId(); 2621 final var userData = getUserData(userId); 2622 if (bindingController.getCurMethod() != null) { 2623 // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. 2624 @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> { 2625 // TODO(b/305849394): Figure out what we should do for single user IME mode. 2626 final boolean shouldClearClientSession = 2627 !mConcurrentMultiUserModeEnabled 2628 || UserHandle.getUserId(c.mUid) == userId; 2629 if (shouldClearClientSession) { 2630 clearClientSessionLocked(c); 2631 clearClientSessionForAccessibilityLocked(c); 2632 } 2633 }; 2634 mClientController.forAllClients(clearClientSession); 2635 2636 finishSessionLocked(userData.mEnabledSession); 2637 for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) { 2638 finishSessionForAccessibilityLocked( 2639 userData.mEnabledAccessibilitySessions.valueAt(i)); 2640 } 2641 userData.mEnabledSession = null; 2642 userData.mEnabledAccessibilitySessions.clear(); 2643 scheduleNotifyImeUidToAudioService(Process.INVALID_UID); 2644 } 2645 hideStatusBarIconLocked(userId); 2646 getUserData(userId).mInFullscreenMode = false; 2647 mWindowManagerInternal.setDismissImeOnBackKeyPressed(false); 2648 scheduleResetStylusHandwriting(); 2649 } 2650 2651 @BinderThread 2652 @GuardedBy("ImfLock.class") updateStatusIconLocked(String packageName, @DrawableRes int iconId, @NonNull UserData userData)2653 private void updateStatusIconLocked(String packageName, @DrawableRes int iconId, 2654 @NonNull UserData userData) { 2655 final int userId = userData.mUserId; 2656 // To minimize app compat risk, ignore background users' request for single-user mode. 2657 // TODO(b/357178609): generalize the logic and remove this special rule. 2658 if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { 2659 return; 2660 } 2661 if (iconId == 0) { 2662 ProtoLog.v(IMMS_DEBUG, "hide the small icon for the input method"); 2663 hideStatusBarIconLocked(userId); 2664 } else if (packageName != null) { 2665 ProtoLog.v(IMMS_DEBUG, "show a small icon for the input method"); 2666 final PackageManager userAwarePackageManager = 2667 getPackageManagerForUser(mContext, userId); 2668 ApplicationInfo applicationInfo = null; 2669 try { 2670 applicationInfo = userAwarePackageManager.getApplicationInfo(packageName, 2671 PackageManager.ApplicationInfoFlags.of(0)); 2672 } catch (PackageManager.NameNotFoundException e) { 2673 } 2674 final CharSequence contentDescription = applicationInfo != null 2675 ? userAwarePackageManager.getApplicationLabel(applicationInfo) 2676 : null; 2677 if (mStatusBarManagerInternal != null) { 2678 mStatusBarManagerInternal.setIcon(mSlotIme, packageName, iconId, 0, 2679 contentDescription != null 2680 ? contentDescription.toString() : null); 2681 mStatusBarManagerInternal.setIconVisibility(mSlotIme, true); 2682 } 2683 } 2684 } 2685 2686 @GuardedBy("ImfLock.class") hideStatusBarIconLocked(@serIdInt int userId)2687 private void hideStatusBarIconLocked(@UserIdInt int userId) { 2688 // To minimize app compat risk, ignore background users' request for single-user mode. 2689 // TODO(b/357178609): generalize the logic and remove this special rule. 2690 if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { 2691 return; 2692 } 2693 if (mStatusBarManagerInternal != null) { 2694 mStatusBarManagerInternal.setIconVisibility(mSlotIme, false); 2695 } 2696 } 2697 2698 @GuardedBy("ImfLock.class") 2699 @InputMethodNavButtonFlags getInputMethodNavButtonFlagsLocked(@onNull UserData userData)2700 private int getInputMethodNavButtonFlagsLocked(@NonNull UserData userData) { 2701 final int userId = userData.mUserId; 2702 final var bindingController = userData.mBindingController; 2703 // Whether the current display has a navigation bar. When this is false (e.g. emulator), 2704 // the IME should not draw the IME navigation bar. 2705 final int tokenDisplayId = bindingController.getCurTokenDisplayId(); 2706 final boolean hasNavigationBar = mWindowManagerInternal 2707 .hasNavigationBar(tokenDisplayId != INVALID_DISPLAY 2708 ? tokenDisplayId : DEFAULT_DISPLAY); 2709 final boolean canImeDrawsImeNavBar = userData.mImeDrawsNavBar.get() && hasNavigationBar; 2710 final boolean shouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherLocked( 2711 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId); 2712 return (canImeDrawsImeNavBar ? InputMethodNavButtonFlags.IME_DRAWS_IME_NAV_BAR : 0) 2713 | (shouldShowImeSwitcherWhenImeIsShown 2714 ? InputMethodNavButtonFlags.SHOW_IME_SWITCHER_WHEN_IME_IS_SHOWN : 0); 2715 } 2716 2717 @GuardedBy("ImfLock.class") shouldShowImeSwitcherLocked(@meWindowVisibility int visibility, @UserIdInt int userId)2718 private boolean shouldShowImeSwitcherLocked(@ImeWindowVisibility int visibility, 2719 @UserIdInt int userId) { 2720 if (!mShowOngoingImeSwitcherForPhones) return false; 2721 // When the IME switcher dialog is shown, the IME switcher button should be hidden. 2722 // TODO(b/305849394): Make mMenuController multi-user aware. 2723 final boolean switcherMenuShowing = Flags.imeSwitcherRevamp() 2724 ? mMenuControllerNew.isShowing() 2725 : mMenuController.getSwitchingDialogLocked() != null; 2726 if (switcherMenuShowing) { 2727 return false; 2728 } 2729 // When we are switching IMEs, the IME switcher button should be hidden. 2730 final var bindingController = getInputMethodBindingController(userId); 2731 if (!Objects.equals(bindingController.getCurId(), 2732 bindingController.getSelectedMethodId())) { 2733 return false; 2734 } 2735 if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() 2736 && mWindowManagerInternal.isKeyguardSecure(userId)) { 2737 return false; 2738 } 2739 if ((visibility & InputMethodService.IME_ACTIVE) == 0) { 2740 return false; 2741 } 2742 if (mWindowManagerInternal.isHardKeyboardAvailable() && !Flags.imeSwitcherRevamp()) { 2743 // When physical keyboard is attached, we show the ime switcher (or notification if 2744 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently 2745 // exists in the IME switcher dialog. Might be OK to remove this condition once 2746 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live. 2747 return true; 2748 } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) { 2749 return false; 2750 } 2751 2752 return hasMultipleSubtypesForSwitcher(false /* nonAuxOnly */, userId); 2753 } 2754 2755 /** 2756 * Checks whether there at least two subtypes that should be shown for the IME switcher menu, 2757 * across all enabled IMEs for the given user. 2758 * 2759 * @param nonAuxOnly whether to check only for non auxiliary subtypes. 2760 * @param userId the id of the user for which to check the number of subtypes. 2761 */ hasMultipleSubtypesForSwitcher(boolean nonAuxOnly, @UserIdInt int userId)2762 private static boolean hasMultipleSubtypesForSwitcher(boolean nonAuxOnly, 2763 @UserIdInt int userId) { 2764 final var settings = InputMethodSettingsRepository.get(userId); 2765 List<InputMethodInfo> imes = settings.getEnabledInputMethodListWithFilter( 2766 InputMethodInfo::shouldShowInInputMethodPicker); 2767 final int numImes = imes.size(); 2768 if (numImes > 2) return true; 2769 if (numImes < 1) return false; 2770 int nonAuxCount = 0; 2771 int auxCount = 0; 2772 InputMethodSubtype nonAuxSubtype = null; 2773 InputMethodSubtype auxSubtype = null; 2774 for (int i = 0; i < numImes; ++i) { 2775 final InputMethodInfo imi = imes.get(i); 2776 final List<InputMethodSubtype> subtypes = 2777 settings.getEnabledInputMethodSubtypeList(imi, true); 2778 final int subtypeCount = subtypes.size(); 2779 if (subtypeCount == 0) { 2780 ++nonAuxCount; 2781 } else { 2782 for (int j = 0; j < subtypeCount; ++j) { 2783 final InputMethodSubtype subtype = subtypes.get(j); 2784 if (!subtype.isAuxiliary()) { 2785 ++nonAuxCount; 2786 nonAuxSubtype = subtype; 2787 } else { 2788 ++auxCount; 2789 auxSubtype = subtype; 2790 } 2791 } 2792 } 2793 } 2794 if (Flags.imeSwitcherRevamp() && nonAuxOnly) { 2795 return nonAuxCount > 1; 2796 } else if (nonAuxCount > 1 || auxCount > 1) { 2797 return true; 2798 } else if (nonAuxCount == 1 && auxCount == 1) { 2799 if (nonAuxSubtype != null && auxSubtype != null 2800 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale()) 2801 || auxSubtype.overridesImplicitlyEnabledSubtype() 2802 || nonAuxSubtype.overridesImplicitlyEnabledSubtype()) 2803 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) { 2804 return false; 2805 } 2806 return true; 2807 } 2808 return false; 2809 } 2810 2811 @BinderThread 2812 @GuardedBy("ImfLock.class") 2813 @SuppressWarnings("deprecation") setImeWindowStatusLocked(@meWindowVisibility int vis, @BackDispositionMode int backDisposition, @NonNull UserData userData)2814 private void setImeWindowStatusLocked(@ImeWindowVisibility int vis, 2815 @BackDispositionMode int backDisposition, @NonNull UserData userData) { 2816 final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); 2817 2818 final int userId = userData.mUserId; 2819 final var bindingController = userData.mBindingController; 2820 // Skip update IME status when current token display is not same as focused display. 2821 // Note that we still need to update IME status when focusing external display 2822 // that does not support system decoration and fallback to show IME on default 2823 // display since it is intentional behavior. 2824 final int tokenDisplayId = bindingController.getCurTokenDisplayId(); 2825 if (tokenDisplayId != topFocusedDisplayId && tokenDisplayId != FALLBACK_DISPLAY_ID) { 2826 return; 2827 } 2828 bindingController.setImeWindowVis(vis); 2829 bindingController.setBackDisposition(backDisposition); 2830 updateSystemUiLocked(vis, backDisposition, userId); 2831 2832 final boolean dismissImeOnBackKeyPressed; 2833 switch (backDisposition) { 2834 case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: 2835 dismissImeOnBackKeyPressed = true; 2836 break; 2837 case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: 2838 dismissImeOnBackKeyPressed = false; 2839 break; 2840 default: 2841 case InputMethodService.BACK_DISPOSITION_DEFAULT: 2842 dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0); 2843 break; 2844 } 2845 mWindowManagerInternal.setDismissImeOnBackKeyPressed(dismissImeOnBackKeyPressed); 2846 } 2847 2848 @BinderThread 2849 @GuardedBy("ImfLock.class") reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData)2850 private void reportStartInputLocked(IBinder startInputToken, @NonNull UserData userData) { 2851 final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); 2852 if (targetWindow != null) { 2853 mWindowManagerInternal.updateInputMethodTargetWindow(targetWindow); 2854 } 2855 final var visibilityStateComputer = userData.mVisibilityStateComputer; 2856 visibilityStateComputer.setLastImeTargetWindow(targetWindow); 2857 } 2858 2859 @GuardedBy("ImfLock.class") updateImeWindowStatusLocked(boolean disableImeIcon, int displayId)2860 private void updateImeWindowStatusLocked(boolean disableImeIcon, int displayId) { 2861 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); 2862 if (disableImeIcon) { 2863 final var bindingController = getInputMethodBindingController(userId); 2864 updateSystemUiLocked(0 /* vis */, bindingController.getBackDisposition(), userId); 2865 } else { 2866 updateSystemUiLocked(userId); 2867 } 2868 } 2869 2870 // Caution! This method is called in this class. Handle multi-user carefully 2871 @GuardedBy("ImfLock.class") updateSystemUiLocked(@serIdInt int userId)2872 void updateSystemUiLocked(@UserIdInt int userId) { 2873 final var bindingController = getInputMethodBindingController(userId); 2874 updateSystemUiLocked(bindingController.getImeWindowVis(), 2875 bindingController.getBackDisposition(), userId); 2876 } 2877 2878 @GuardedBy("ImfLock.class") updateSystemUiLocked(@meWindowVisibility int vis, @BackDispositionMode int backDisposition, @UserIdInt int userId)2879 private void updateSystemUiLocked(@ImeWindowVisibility int vis, 2880 @BackDispositionMode int backDisposition, @UserIdInt int userId) { 2881 // To minimize app compat risk, ignore background users' request for single-user mode. 2882 // TODO(b/357178609): generalize the logic and remove this special rule. 2883 if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { 2884 return; 2885 } 2886 final var userData = getUserData(userId); 2887 final var bindingController = userData.mBindingController; 2888 final var curToken = bindingController.getCurToken(); 2889 if (curToken == null) { 2890 return; 2891 } 2892 final int curTokenDisplayId = bindingController.getCurTokenDisplayId(); 2893 ProtoLog.v(IMMS_DEBUG, "IME window vis: %s active: %s visible: %s displayId: %s", vis, 2894 (vis & InputMethodService.IME_ACTIVE), (vis & InputMethodService.IME_VISIBLE), 2895 curTokenDisplayId); 2896 final IBinder focusedWindowToken = userData.mImeBindingState.mFocusedWindow; 2897 final Boolean windowPerceptible = focusedWindowToken != null 2898 ? mFocusedWindowPerceptible.get(focusedWindowToken) : null; 2899 2900 // TODO: Move this clearing calling identity block to setImeWindowStatusLocked after making 2901 // sure all updateSystemUi happens on system privilege. 2902 final long ident = Binder.clearCallingIdentity(); 2903 try { 2904 if (windowPerceptible != null && !windowPerceptible) { 2905 vis &= ~InputMethodService.IME_VISIBLE; 2906 } 2907 final var curId = bindingController.getCurId(); 2908 // TODO(b/305849394): Make mMenuController multi-user aware. 2909 final boolean switcherMenuShowing = Flags.imeSwitcherRevamp() 2910 ? mMenuControllerNew.isShowing() 2911 : mMenuController.getSwitchingDialogLocked() != null; 2912 if (switcherMenuShowing 2913 || !Objects.equals(curId, bindingController.getSelectedMethodId())) { 2914 // When the IME switcher dialog is shown, or we are switching IMEs, 2915 // the back button should be in the default state (as if the IME is not shown). 2916 backDisposition = InputMethodService.BACK_DISPOSITION_ADJUST_NOTHING; 2917 } 2918 final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis, userId); 2919 if (mStatusBarManagerInternal != null) { 2920 mStatusBarManagerInternal.setImeWindowStatus(curTokenDisplayId, vis, 2921 backDisposition, needsToShowImeSwitcher); 2922 } 2923 } finally { 2924 Binder.restoreCallingIdentity(ident); 2925 } 2926 } 2927 2928 @GuardedBy("ImfLock.class") updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId)2929 void updateFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { 2930 updateInputMethodsFromSettingsLocked(enabledMayChange, userId); 2931 if (!Flags.imeSwitcherRevamp()) { 2932 mMenuController.updateKeyboardFromSettingsLocked(userId); 2933 } 2934 } 2935 2936 @GuardedBy("ImfLock.class") updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId)2937 void updateInputMethodsFromSettingsLocked(boolean enabledMayChange, @UserIdInt int userId) { 2938 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 2939 if (enabledMayChange) { 2940 final PackageManager userAwarePackageManager = getPackageManagerForUser(mContext, 2941 userId); 2942 2943 List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); 2944 for (int i = 0; i < enabled.size(); i++) { 2945 // We allow the user to select "disabled until used" apps, so if they 2946 // are enabling one of those here we now need to make it enabled. 2947 InputMethodInfo imm = enabled.get(i); 2948 ApplicationInfo ai = null; 2949 try { 2950 ai = userAwarePackageManager.getApplicationInfo(imm.getPackageName(), 2951 PackageManager.ApplicationInfoFlags.of( 2952 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS)); 2953 } catch (PackageManager.NameNotFoundException ignored) { 2954 } 2955 if (ai != null && ai.enabledSetting 2956 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 2957 ProtoLog.v(IMMS_DEBUG, "Update state(%s): DISABLED_UNTIL_USED -> DEFAULT", 2958 imm.getId()); 2959 userAwarePackageManager.setApplicationEnabledSetting(imm.getPackageName(), 2960 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 2961 PackageManager.DONT_KILL_APP); 2962 } 2963 } 2964 } 2965 2966 final var userData = getUserData(userId); 2967 final var bindingController = userData.mBindingController; 2968 if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) { 2969 String ime = SecureSettingsWrapper.getString( 2970 Settings.Secure.DEFAULT_INPUT_METHOD, null, userId); 2971 String defaultDeviceIme = SecureSettingsWrapper.getString( 2972 Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, userId); 2973 if (defaultDeviceIme != null && !Objects.equals(ime, defaultDeviceIme)) { 2974 ProtoLog.v(IMMS_DEBUG, 2975 "Current input method %s differs from the stored default device input " 2976 + "method for user %s - restoring %s", 2977 ime, userId, defaultDeviceIme); 2978 SecureSettingsWrapper.putString( 2979 Settings.Secure.DEFAULT_INPUT_METHOD, defaultDeviceIme, userId); 2980 SecureSettingsWrapper.putString( 2981 Settings.Secure.DEFAULT_DEVICE_INPUT_METHOD, null, userId); 2982 } 2983 } 2984 2985 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 2986 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 2987 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 2988 // enabled. 2989 String id = settings.getSelectedInputMethod(); 2990 // There is no input method selected, try to choose new applicable input method. 2991 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked(userId)) { 2992 id = settings.getSelectedInputMethod(); 2993 } 2994 if (!TextUtils.isEmpty(id)) { 2995 try { 2996 setInputMethodLocked(id, settings.getSelectedInputMethodSubtypeIndex(id), userId); 2997 } catch (IllegalArgumentException e) { 2998 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 2999 resetCurrentMethodAndClientLocked(UnbindReason.SWITCH_IME_FAILED, userId); 3000 } 3001 } else { 3002 // There is no longer an input method set, so stop any current one. 3003 resetCurrentMethodAndClientLocked(UnbindReason.NO_IME, userId); 3004 } 3005 3006 userData.mSwitchingController.resetCircularListLocked(mContext, settings); 3007 userData.mHardwareKeyboardShortcutController.update(settings); 3008 sendOnNavButtonFlagsChangedLocked(userData); 3009 } 3010 3011 @GuardedBy("ImfLock.class") notifyInputMethodSubtypeChangedLocked(@serIdInt int userId, @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype)3012 private void notifyInputMethodSubtypeChangedLocked(@UserIdInt int userId, 3013 @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) { 3014 final InputMethodSubtype normalizedSubtype = 3015 subtype != null && subtype.isSuitableForPhysicalKeyboardLayoutMapping() 3016 ? subtype : null; 3017 final InputMethodSubtypeHandle newSubtypeHandle = normalizedSubtype != null 3018 ? InputMethodSubtypeHandle.of(imi, normalizedSubtype) : null; 3019 3020 final var userData = getUserData(userId); 3021 3022 // A workaround for b/356879517. KeyboardLayoutManager has relied on an implementation 3023 // detail that IMMS triggers this callback only for the current IME user. 3024 // TODO(b/357663774): Figure out how to better handle this scenario. 3025 userData.mSubtypeForKeyboardLayoutMapping = 3026 Pair.create(newSubtypeHandle, normalizedSubtype); 3027 if (userId != mCurrentImeUserId) { 3028 return; 3029 } 3030 mInputManagerInternal.onInputMethodSubtypeChangedForKeyboardLayoutMapping( 3031 userId, newSubtypeHandle, normalizedSubtype); 3032 } 3033 3034 @GuardedBy("ImfLock.class") setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId)3035 void setInputMethodLocked(String id, int subtypeIndex, @UserIdInt int userId) { 3036 setInputMethodLocked(id, subtypeIndex, DEVICE_ID_DEFAULT, userId); 3037 } 3038 3039 @GuardedBy("ImfLock.class") setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId)3040 void setInputMethodLocked(String id, int subtypeIndex, int deviceId, @UserIdInt int userId) { 3041 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 3042 InputMethodInfo info = settings.getMethodMap().get(id); 3043 if (info == null) { 3044 throw getExceptionForUnknownImeId(id); 3045 } 3046 3047 final var bindingController = getInputMethodBindingController(userId); 3048 // See if we need to notify a subtype change within the same IME. 3049 if (id.equals(bindingController.getSelectedMethodId())) { 3050 final int subtypeCount = info.getSubtypeCount(); 3051 if (subtypeCount <= 0) { 3052 notifyInputMethodSubtypeChangedLocked(userId, info, null); 3053 return; 3054 } 3055 final InputMethodSubtype oldSubtype = bindingController.getCurrentSubtype(); 3056 final InputMethodSubtype newSubtype; 3057 if (subtypeIndex >= 0 && subtypeIndex < subtypeCount) { 3058 newSubtype = info.getSubtypeAt(subtypeIndex); 3059 } else { 3060 // If subtype is null, try to find the most applicable one from 3061 // getCurrentInputMethodSubtype. 3062 subtypeIndex = NOT_A_SUBTYPE_INDEX; 3063 // TODO(b/347083680): The method below has questionable behaviors. 3064 newSubtype = bindingController.getCurrentInputMethodSubtype(); 3065 if (newSubtype != null) { 3066 for (int i = 0; i < subtypeCount; ++i) { 3067 if (Objects.equals(newSubtype, info.getSubtypeAt(i))) { 3068 subtypeIndex = i; 3069 break; 3070 } 3071 } 3072 } 3073 } 3074 if (!Objects.equals(newSubtype, oldSubtype)) { 3075 setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, true, userId); 3076 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3077 if (curMethod != null) { 3078 updateSystemUiLocked(bindingController.getImeWindowVis(), 3079 bindingController.getBackDisposition(), userId); 3080 curMethod.changeInputMethodSubtype(newSubtype); 3081 } 3082 } 3083 return; 3084 } 3085 3086 // Changing to a different IME. 3087 if (bindingController.getDeviceIdToShowIme() != DEVICE_ID_DEFAULT 3088 && deviceId == DEVICE_ID_DEFAULT) { 3089 // This change should only be applicable to the default device but the current input 3090 // method is a custom one specific to a virtual device. So only update the settings 3091 // entry used to restore the default device input method once we want to show the IME 3092 // back on the default device. 3093 settings.putSelectedDefaultDeviceInputMethod(id); 3094 return; 3095 } 3096 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3097 if (curMethod != null) { 3098 curMethod.removeStylusHandwritingWindow(); 3099 } 3100 final long ident = Binder.clearCallingIdentity(); 3101 try { 3102 setSelectedInputMethodAndSubtypeLocked(info, subtypeIndex, false, userId); 3103 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 3104 // because mCurMethodId is stored as a history in 3105 // setSelectedInputMethodAndSubtypeLocked(). 3106 bindingController.setSelectedMethodId(id); 3107 3108 if (mActivityManagerInternal.isSystemReady()) { 3109 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 3110 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 3111 intent.putExtra("input_method_id", id); 3112 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 3113 } 3114 bindingController.unbindCurrentMethod(); 3115 unbindCurrentClientLocked(UnbindReason.SWITCH_IME, userId); 3116 } finally { 3117 Binder.restoreCallingIdentity(ident); 3118 } 3119 } 3120 3121 @GuardedBy("ImfLock.class") sendResultReceiverFailureLocked(@ullable ResultReceiver resultReceiver, @UserIdInt int userId)3122 private void sendResultReceiverFailureLocked(@Nullable ResultReceiver resultReceiver, 3123 @UserIdInt int userId) { 3124 if (resultReceiver == null) { 3125 return; 3126 } 3127 final var userData = getUserData(userId); 3128 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3129 final boolean isInputShown = visibilityStateComputer.isInputShown(); 3130 resultReceiver.send(isInputShown 3131 ? InputMethodManager.RESULT_UNCHANGED_SHOWN 3132 : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); 3133 } 3134 3135 @Override showSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async)3136 public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, 3137 @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, 3138 int lastClickToolType, ResultReceiver resultReceiver, 3139 @SoftInputShowHideReason int reason, boolean async) { 3140 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showSoftInput"); 3141 ImeTracing.getInstance().triggerManagerServiceDump( 3142 "InputMethodManagerService#showSoftInput", mDumper); 3143 synchronized (ImfLock.class) { 3144 final int uid = Binder.getCallingUid(); 3145 final int callingUserId = UserHandle.getUserId(uid); 3146 final int userId = resolveImeUserIdLocked(callingUserId, client); 3147 final boolean result = showSoftInputLocked(client, windowToken, statsToken, flags, 3148 lastClickToolType, resultReceiver, reason, uid, userId); 3149 // When ZeroJankProxy is enabled, the app has already received "true" as the return 3150 // value, and expect "resultReceiver" to be notified later. See b/327751155. 3151 if (!result && Flags.useZeroJankProxy()) { 3152 sendResultReceiverFailureLocked(resultReceiver, userId); 3153 } 3154 return result; // ignored when ZeroJankProxy is enabled. 3155 } 3156 } 3157 3158 @GuardedBy("ImfLock.class") showSoftInputLocked(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, int lastClickToolType, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId)3159 private boolean showSoftInputLocked(IInputMethodClient client, IBinder windowToken, 3160 @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, 3161 int lastClickToolType, ResultReceiver resultReceiver, 3162 @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId) { 3163 if (!canInteractWithImeLocked(uid, client, "showSoftInput", statsToken, 3164 userId)) { 3165 ImeTracker.forLogging().onFailed( 3166 statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); 3167 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3168 return false; 3169 } 3170 final var userData = getUserData(userId); 3171 final long ident = Binder.clearCallingIdentity(); 3172 try { 3173 ProtoLog.v(IMMS_DEBUG, "Client requesting input be shown"); 3174 if (Flags.refactorInsetsController()) { 3175 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3176 boolean wasVisible = visibilityStateComputer.isInputShown(); 3177 if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) { 3178 if (resultReceiver != null) { 3179 resultReceiver.send( 3180 wasVisible ? InputMethodManager.RESULT_UNCHANGED_SHOWN 3181 : InputMethodManager.RESULT_SHOWN, null); 3182 } 3183 return true; 3184 } 3185 return false; 3186 } else { 3187 return showCurrentInputLocked(windowToken, statsToken, flags, lastClickToolType, 3188 resultReceiver, reason, userId); 3189 } 3190 } finally { 3191 Binder.restoreCallingIdentity(ident); 3192 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3193 } 3194 } 3195 3196 // TODO(b/353463205) check callers to see if we can make statsToken @NonNull showCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken)3197 boolean showCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { 3198 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showCurrentInputInternal"); 3199 ImeTracing.getInstance().triggerManagerServiceDump( 3200 "InputMethodManagerService#showSoftInput", mDumper); 3201 synchronized (ImfLock.class) { 3202 final int userId = resolveImeUserIdFromWindowLocked(windowToken); 3203 final long ident = Binder.clearCallingIdentity(); 3204 try { 3205 ProtoLog.v(IMMS_DEBUG, "Client requesting input be shown"); 3206 return showCurrentInputLocked(windowToken, statsToken, 0 /* flags */, 3207 0 /* lastClickTooType */, null /* resultReceiver */, 3208 SoftInputShowHideReason.SHOW_SOFT_INPUT, userId); 3209 } finally { 3210 Binder.restoreCallingIdentity(ident); 3211 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3212 } 3213 } 3214 } 3215 3216 // TODO(b/353463205) check callers to see if we can make statsToken @NonNull hideCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken)3217 boolean hideCurrentInputInternal(IBinder windowToken, @NonNull ImeTracker.Token statsToken) { 3218 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideCurrentInputInternal"); 3219 ImeTracing.getInstance().triggerManagerServiceDump( 3220 "InputMethodManagerService#hideSoftInput", mDumper); 3221 synchronized (ImfLock.class) { 3222 final int userId = resolveImeUserIdFromWindowLocked(windowToken); 3223 final long ident = Binder.clearCallingIdentity(); 3224 try { 3225 ProtoLog.v(IMMS_DEBUG, "Client requesting input be hidden"); 3226 return hideCurrentInputLocked(windowToken, statsToken, 0 /* flags */, 3227 null /* resultReceiver */, SoftInputShowHideReason.HIDE_SOFT_INPUT, 3228 userId); 3229 } finally { 3230 Binder.restoreCallingIdentity(ident); 3231 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3232 } 3233 } 3234 } 3235 3236 @BinderThread 3237 @Override startStylusHandwriting(IInputMethodClient client)3238 public void startStylusHandwriting(IInputMethodClient client) { 3239 startStylusHandwriting(client, false /* usesDelegation */); 3240 } 3241 3242 @BinderThread 3243 @Override startConnectionlessStylusHandwriting(IInputMethodClient client, int userId, @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName, @Nullable String delegatorPackageName, @NonNull IConnectionlessHandwritingCallback callback)3244 public void startConnectionlessStylusHandwriting(IInputMethodClient client, int userId, 3245 @Nullable CursorAnchorInfo cursorAnchorInfo, @Nullable String delegatePackageName, 3246 @Nullable String delegatorPackageName, 3247 @NonNull IConnectionlessHandwritingCallback callback) { 3248 synchronized (ImfLock.class) { 3249 final var bindingController = getInputMethodBindingController(userId); 3250 if (!bindingController.supportsConnectionlessStylusHandwriting()) { 3251 Slog.w(TAG, "Connectionless stylus handwriting mode unsupported by IME."); 3252 try { 3253 callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED); 3254 } catch (RemoteException e) { 3255 Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_UNSUPPORTED", e); 3256 e.rethrowAsRuntimeException(); 3257 } 3258 return; 3259 } 3260 } 3261 3262 IConnectionlessHandwritingCallback immsCallback = callback; 3263 boolean isForDelegation = delegatePackageName != null && delegatorPackageName != null; 3264 if (isForDelegation) { 3265 synchronized (ImfLock.class) { 3266 if (!mClientController.verifyClientAndPackageMatch(client, delegatorPackageName)) { 3267 Slog.w(TAG, "startConnectionlessStylusHandwriting() fail"); 3268 try { 3269 callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER); 3270 } catch (RemoteException e) { 3271 Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e); 3272 e.rethrowAsRuntimeException(); 3273 } 3274 throw new IllegalArgumentException("Delegator doesn't match UID"); 3275 } 3276 } 3277 immsCallback = new IConnectionlessHandwritingCallback.Stub() { 3278 @Override 3279 public void onResult(CharSequence text) throws RemoteException { 3280 synchronized (ImfLock.class) { 3281 mHwController.prepareStylusHandwritingDelegation( 3282 userId, delegatePackageName, delegatorPackageName, 3283 /* connectionless= */ true); 3284 } 3285 callback.onResult(text); 3286 } 3287 3288 @Override 3289 public void onError(int errorCode) throws RemoteException { 3290 callback.onError(errorCode); 3291 } 3292 }; 3293 } 3294 3295 if (!startStylusHandwriting( 3296 client, false, immsCallback, cursorAnchorInfo, isForDelegation)) { 3297 try { 3298 callback.onError(CONNECTIONLESS_HANDWRITING_ERROR_OTHER); 3299 } catch (RemoteException e) { 3300 Slog.e(TAG, "Failed to report CONNECTIONLESS_HANDWRITING_ERROR_OTHER", e); 3301 e.rethrowAsRuntimeException(); 3302 } 3303 } 3304 } 3305 startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation)3306 private void startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation) { 3307 startStylusHandwriting(client, acceptingDelegation, null, null, false); 3308 } 3309 startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation, IConnectionlessHandwritingCallback connectionlessCallback, CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation)3310 private boolean startStylusHandwriting(IInputMethodClient client, boolean acceptingDelegation, 3311 IConnectionlessHandwritingCallback connectionlessCallback, 3312 CursorAnchorInfo cursorAnchorInfo, boolean isConnectionlessForDelegation) { 3313 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting"); 3314 try { 3315 ImeTracing.getInstance().triggerManagerServiceDump( 3316 "InputMethodManagerService#startStylusHandwriting", mDumper); 3317 final int uid = Binder.getCallingUid(); 3318 final int callingUserId = UserHandle.getUserId(uid); 3319 synchronized (ImfLock.class) { 3320 final int userId = resolveImeUserIdLocked(callingUserId); 3321 if (!acceptingDelegation) { 3322 mHwController.clearPendingHandwritingDelegation(); 3323 } 3324 if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting", 3325 null /* statsToken */, userId)) { 3326 return false; 3327 } 3328 if (!hasSupportedStylusLocked()) { 3329 Slog.w(TAG, "No supported Stylus hardware found on device. Ignoring" 3330 + " startStylusHandwriting()"); 3331 return false; 3332 } 3333 final long ident = Binder.clearCallingIdentity(); 3334 try { 3335 final var bindingController = getInputMethodBindingController(userId); 3336 if (!bindingController.supportsStylusHandwriting()) { 3337 Slog.w(TAG, 3338 "Stylus HW unsupported by IME. Ignoring startStylusHandwriting()"); 3339 return false; 3340 } 3341 3342 final OptionalInt requestId = mHwController.getCurrentRequestId(); 3343 if (!requestId.isPresent()) { 3344 Slog.e(TAG, "Stylus handwriting was not initialized."); 3345 return false; 3346 } 3347 if (!mHwController.isStylusGestureOngoing()) { 3348 Slog.e(TAG, 3349 "There is no ongoing stylus gesture to start stylus handwriting."); 3350 return false; 3351 } 3352 if (mHwController.hasOngoingStylusHandwritingSession()) { 3353 // prevent duplicate calls to startStylusHandwriting(). 3354 Slog.e(TAG, 3355 "Stylus handwriting session is already ongoing." 3356 + " Ignoring startStylusHandwriting()."); 3357 return false; 3358 } 3359 ProtoLog.v(IMMS_DEBUG, "Client requesting Stylus Handwriting to be started"); 3360 final IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3361 if (curMethod != null) { 3362 curMethod.canStartStylusHandwriting(requestId.getAsInt(), 3363 connectionlessCallback, cursorAnchorInfo, 3364 isConnectionlessForDelegation); 3365 return true; 3366 } 3367 } finally { 3368 Binder.restoreCallingIdentity(ident); 3369 } 3370 } 3371 } finally { 3372 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3373 } 3374 return false; 3375 } 3376 3377 @Override prepareStylusHandwritingDelegation( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName)3378 public void prepareStylusHandwritingDelegation( 3379 @NonNull IInputMethodClient client, 3380 @UserIdInt int userId, 3381 @NonNull String delegatePackageName, 3382 @NonNull String delegatorPackageName) { 3383 if (!isStylusHandwritingEnabled(mContext, userId)) { 3384 Slog.w(TAG, "Can not prepare stylus handwriting delegation. Stylus handwriting" 3385 + " pref is disabled for user: " + userId); 3386 return; 3387 } 3388 synchronized (ImfLock.class) { 3389 if (!mClientController.verifyClientAndPackageMatch(client, 3390 delegatorPackageName)) { 3391 Slog.w(TAG, "prepareStylusHandwritingDelegation() fail"); 3392 throw new IllegalArgumentException("Delegator doesn't match Uid"); 3393 } 3394 } 3395 schedulePrepareStylusHandwritingDelegation( 3396 userId, delegatePackageName, delegatorPackageName); 3397 } 3398 3399 @Override acceptStylusHandwritingDelegationAsync( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback)3400 public void acceptStylusHandwritingDelegationAsync( 3401 @NonNull IInputMethodClient client, 3402 @UserIdInt int userId, 3403 @NonNull String delegatePackageName, 3404 @NonNull String delegatorPackageName, 3405 @InputMethodManager.HandwritingDelegateFlags int flags, IBooleanListener callback) { 3406 boolean result = acceptStylusHandwritingDelegation( 3407 client, userId, delegatePackageName, delegatorPackageName, flags); 3408 try { 3409 callback.onResult(result); 3410 } catch (RemoteException e) { 3411 Slog.e(TAG, "Failed to report result=" + result, e); 3412 e.rethrowAsRuntimeException(); 3413 } 3414 } 3415 3416 @Override acceptStylusHandwritingDelegation( @onNull IInputMethodClient client, @UserIdInt int userId, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags)3417 public boolean acceptStylusHandwritingDelegation( 3418 @NonNull IInputMethodClient client, 3419 @UserIdInt int userId, 3420 @NonNull String delegatePackageName, 3421 @NonNull String delegatorPackageName, 3422 @InputMethodManager.HandwritingDelegateFlags int flags) { 3423 if (!isStylusHandwritingEnabled(mContext, userId)) { 3424 Slog.w(TAG, "Can not accept stylus handwriting delegation. Stylus handwriting" 3425 + " pref is disabled for user: " + userId); 3426 return false; 3427 } 3428 if (!verifyDelegator(client, delegatePackageName, delegatorPackageName, flags)) { 3429 return false; 3430 } 3431 synchronized (ImfLock.class) { 3432 final var bindingController = getInputMethodBindingController(userId); 3433 if (mHwController.isDelegationUsingConnectionlessFlow()) { 3434 final IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3435 if (curMethod == null) { 3436 return false; 3437 } 3438 curMethod.commitHandwritingDelegationTextIfAvailable(); 3439 mHwController.clearPendingHandwritingDelegation(); 3440 } else { 3441 startStylusHandwriting(client, true /* acceptingDelegation */); 3442 } 3443 } 3444 return true; 3445 } 3446 verifyDelegator( @onNull IInputMethodClient client, @NonNull String delegatePackageName, @NonNull String delegatorPackageName, @InputMethodManager.HandwritingDelegateFlags int flags)3447 private boolean verifyDelegator( 3448 @NonNull IInputMethodClient client, 3449 @NonNull String delegatePackageName, 3450 @NonNull String delegatorPackageName, 3451 @InputMethodManager.HandwritingDelegateFlags int flags) { 3452 synchronized (ImfLock.class) { 3453 if (!mClientController.verifyClientAndPackageMatch(client, delegatePackageName)) { 3454 Slog.w(TAG, "Delegate package does not belong to the same user. Ignoring" 3455 + " startStylusHandwriting"); 3456 return false; 3457 } 3458 boolean homeDelegatorAllowed = 3459 (flags & InputMethodManager.HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED) 3460 != 0; 3461 if (!delegatorPackageName.equals(mHwController.getDelegatorPackageName()) 3462 && !(homeDelegatorAllowed 3463 && mHwController.isDelegatorFromDefaultHomePackage())) { 3464 Slog.w(TAG, 3465 "Delegator package does not match. Ignoring startStylusHandwriting"); 3466 return false; 3467 } 3468 if (!delegatePackageName.equals(mHwController.getDelegatePackageName())) { 3469 Slog.w(TAG, 3470 "Delegate package does not match. Ignoring startStylusHandwriting"); 3471 return false; 3472 } 3473 } 3474 return true; 3475 } 3476 3477 @BinderThread 3478 @Override reportPerceptibleAsync(@onNull IBinder windowToken, boolean perceptible)3479 public void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) { 3480 Binder.withCleanCallingIdentity(() -> { 3481 Objects.requireNonNull(windowToken, "windowToken must not be null"); 3482 synchronized (ImfLock.class) { 3483 Boolean windowPerceptible = mFocusedWindowPerceptible.get(windowToken); 3484 final int userId = resolveImeUserIdFromWindowLocked(windowToken); 3485 final var userData = getUserData(userId); 3486 if (userData.mImeBindingState.mFocusedWindow != windowToken 3487 || (windowPerceptible != null && windowPerceptible == perceptible)) { 3488 return; 3489 } 3490 mFocusedWindowPerceptible.put(windowToken, perceptible); 3491 updateSystemUiLocked(userId); 3492 } 3493 }); 3494 } 3495 3496 @GuardedBy("ImfLock.class") showCurrentInputLocked(IBinder windowToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, @UserIdInt int userId)3497 private boolean showCurrentInputLocked(IBinder windowToken, 3498 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, 3499 @UserIdInt int userId) { 3500 final var statsToken = createStatsTokenForFocusedClient(true /* show */, reason, userId); 3501 return showCurrentInputLocked(windowToken, statsToken, flags, 3502 MotionEvent.TOOL_TYPE_UNKNOWN, null /* resultReceiver */, reason, userId); 3503 } 3504 3505 @GuardedBy("ImfLock.class") showCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)3506 boolean showCurrentInputLocked(IBinder windowToken, 3507 @NonNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, 3508 @MotionEvent.ToolType int lastClickToolType, @Nullable ResultReceiver resultReceiver, 3509 @SoftInputShowHideReason int reason, @UserIdInt int userId) { 3510 final var userData = getUserData(userId); 3511 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3512 if (!visibilityStateComputer.onImeShowFlags(statsToken, flags)) { 3513 return false; 3514 } 3515 3516 if (!mSystemReady) { 3517 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY); 3518 return false; 3519 } 3520 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SYSTEM_READY); 3521 3522 visibilityStateComputer.requestImeVisibility(windowToken, true); 3523 3524 // Ensure binding the connection when IME is going to show. 3525 final var bindingController = userData.mBindingController; 3526 bindingController.setCurrentMethodVisible(); 3527 final IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3528 ImeTracker.forLogging().onCancelled(userData.mCurStatsToken, 3529 ImeTracker.PHASE_SERVER_WAIT_IME); 3530 final boolean readyToDispatchToIme; 3531 if (Flags.deferShowSoftInputUntilSessionCreation()) { 3532 readyToDispatchToIme = 3533 curMethod != null && userData.mCurClient != null 3534 && userData.mCurClient.mCurSession != null; 3535 } else { 3536 readyToDispatchToIme = curMethod != null; 3537 } 3538 if (readyToDispatchToIme) { 3539 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME); 3540 userData.mCurStatsToken = null; 3541 3542 if (Flags.useHandwritingListenerForTooltype()) { 3543 maybeReportToolType(userId); 3544 } else if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) { 3545 onUpdateEditorToolTypeLocked(lastClickToolType, userId); 3546 } 3547 mVisibilityApplier.performShowIme(windowToken, statsToken, 3548 visibilityStateComputer.getShowFlagsForInputMethodServiceOnly(), 3549 resultReceiver, reason, userId); 3550 visibilityStateComputer.setInputShown(true); 3551 return true; 3552 } else { 3553 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_WAIT_IME); 3554 userData.mCurStatsToken = statsToken; 3555 } 3556 return false; 3557 } 3558 3559 @GuardedBy("ImfLock.class") maybeReportToolType(@serIdInt int userId)3560 private void maybeReportToolType(@UserIdInt int userId) { 3561 // TODO(b/356638981): This needs to be compatible with visible background users. 3562 int lastDeviceId = mInputManagerInternal.getLastUsedInputDeviceId(); 3563 final InputManager im = mContext.getSystemService(InputManager.class); 3564 if (im == null) { 3565 return; 3566 } 3567 InputDevice device = im.getInputDevice(lastDeviceId); 3568 if (device == null) { 3569 return; 3570 } 3571 int toolType; 3572 if (isStylusDevice(device)) { 3573 toolType = MotionEvent.TOOL_TYPE_STYLUS; 3574 } else if (isFingerDevice(device)) { 3575 toolType = MotionEvent.TOOL_TYPE_FINGER; 3576 } else { 3577 // other toolTypes are irrelevant and reported as unknown. 3578 toolType = MotionEvent.TOOL_TYPE_UNKNOWN; 3579 } 3580 onUpdateEditorToolTypeLocked(toolType, userId); 3581 } 3582 3583 @Override hideSoftInput(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async)3584 public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, 3585 @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, 3586 ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, boolean async) { 3587 ImeTracing.getInstance().triggerManagerServiceDump( 3588 "InputMethodManagerService#hideSoftInput", mDumper); 3589 synchronized (ImfLock.class) { 3590 final int uid = Binder.getCallingUid(); 3591 final int callingUserId = UserHandle.getUserId(uid); 3592 final int userId = resolveImeUserIdLocked(callingUserId, client); 3593 final boolean result = hideSoftInputLocked(client, windowToken, statsToken, flags, 3594 resultReceiver, reason, uid, userId); 3595 // When ZeroJankProxy is enabled, the app has already received "true" as the return 3596 // value, and expect "resultReceiver" to be notified later. See b/327751155. 3597 if (!result && Flags.useZeroJankProxy()) { 3598 sendResultReceiverFailureLocked(resultReceiver, userId); 3599 } 3600 return result; // ignored when ZeroJankProxy is enabled. 3601 } 3602 } 3603 3604 @GuardedBy("ImfLock.class") hideSoftInputLocked(IInputMethodClient client, IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, int uid, @UserIdInt int userId)3605 private boolean hideSoftInputLocked(IInputMethodClient client, IBinder windowToken, 3606 @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, 3607 ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, 3608 int uid, @UserIdInt int userId) { 3609 final var userData = getUserData(userId); 3610 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3611 if (!canInteractWithImeLocked(uid, client, "hideSoftInput", statsToken, userId)) { 3612 if (visibilityStateComputer.isInputShown()) { 3613 ImeTracker.forLogging().onFailed( 3614 statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); 3615 } else { 3616 ImeTracker.forLogging().onCancelled(statsToken, 3617 ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); 3618 } 3619 return false; 3620 } 3621 final long ident = Binder.clearCallingIdentity(); 3622 try { 3623 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideSoftInput"); 3624 ProtoLog.v(IMMS_DEBUG, "Client requesting input be hidden"); 3625 if (Flags.refactorInsetsController()) { 3626 boolean wasVisible = visibilityStateComputer.isInputShown(); 3627 // TODO add windowToken to interface 3628 if (setImeVisibilityOnFocusedWindowClient(false, userData, statsToken)) { 3629 if (resultReceiver != null) { 3630 resultReceiver.send(wasVisible ? InputMethodManager.RESULT_HIDDEN 3631 : InputMethodManager.RESULT_UNCHANGED_HIDDEN, null); 3632 } 3633 return true; 3634 } 3635 return false; 3636 } else { 3637 return InputMethodManagerService.this.hideCurrentInputLocked( 3638 windowToken, statsToken, flags, resultReceiver, reason, userId); 3639 } 3640 } finally { 3641 Binder.restoreCallingIdentity(ident); 3642 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3643 } 3644 } 3645 3646 @Override 3647 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) hideSoftInputFromServerForTest()3648 public void hideSoftInputFromServerForTest() { 3649 final int callingUserId = UserHandle.getCallingUserId(); 3650 synchronized (ImfLock.class) { 3651 final int userId = resolveImeUserIdLocked(callingUserId); 3652 final var userData = getUserData(userId); 3653 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 0 /* flags */, 3654 SoftInputShowHideReason.HIDE_SOFT_INPUT, userId); 3655 } 3656 } 3657 3658 @GuardedBy("ImfLock.class") hideCurrentInputLocked(IBinder windowToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, @UserIdInt int userId)3659 private boolean hideCurrentInputLocked(IBinder windowToken, 3660 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, 3661 @UserIdInt int userId) { 3662 final var statsToken = createStatsTokenForFocusedClient(false /* show */, reason, userId); 3663 return hideCurrentInputLocked(windowToken, statsToken, flags, null /* resultReceiver */, 3664 reason, userId); 3665 } 3666 3667 @GuardedBy("ImfLock.class") hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, @SoftInputShowHideReason int reason, @UserIdInt int userId)3668 boolean hideCurrentInputLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, 3669 @InputMethodManager.HideFlags int flags, @Nullable ResultReceiver resultReceiver, 3670 @SoftInputShowHideReason int reason, @UserIdInt int userId) { 3671 final var userData = getUserData(userId); 3672 final var bindingController = userData.mBindingController; 3673 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3674 if (!visibilityStateComputer.canHideIme(statsToken, flags)) { 3675 return false; 3676 } 3677 3678 // There is a chance that IMM#hideSoftInput() is called in a transient state where 3679 // IMMS#InputShown is already updated to be true whereas the user's ImeWindowVis is still 3680 // waiting to be updated with the new value sent from IME process. Even in such a transient 3681 // state historically we have accepted an incoming call of IMM#hideSoftInput() from the 3682 // application process as a valid request, and have even promised such a behavior with CTS 3683 // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only 3684 // IMMS#InputShown indicates that the software keyboard is shown. 3685 // TODO(b/246309664): Clean up IMMS#mImeWindowVis 3686 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 3687 final boolean shouldHideSoftInput = curMethod != null 3688 && (visibilityStateComputer.isInputShown() 3689 || (bindingController.getImeWindowVis() & InputMethodService.IME_ACTIVE) != 0); 3690 3691 visibilityStateComputer.requestImeVisibility(windowToken, false); 3692 if (shouldHideSoftInput) { 3693 // The IME will report its visible state again after the following message finally 3694 // delivered to the IME process as an IPC. Hence the inconsistency between 3695 // IMMS#mInputShown and the user's ImeWindowVis should be resolved spontaneously in 3696 // the final state. 3697 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); 3698 mVisibilityApplier.performHideIme(windowToken, statsToken, resultReceiver, reason, 3699 userId); 3700 } else { 3701 ImeTracker.forLogging().onCancelled(statsToken, ImeTracker.PHASE_SERVER_SHOULD_HIDE); 3702 } 3703 bindingController.setCurrentMethodNotVisible(); 3704 visibilityStateComputer.clearImeShowFlags(); 3705 // Cancel existing statsToken for show IME as we got a hide request. 3706 ImeTracker.forLogging().onCancelled(userData.mCurStatsToken, 3707 ImeTracker.PHASE_SERVER_WAIT_IME); 3708 userData.mCurStatsToken = null; 3709 return shouldHideSoftInput; 3710 } 3711 isImeClientFocused(IBinder windowToken, ClientState cs)3712 private boolean isImeClientFocused(IBinder windowToken, ClientState cs) { 3713 final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( 3714 windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId); 3715 return imeClientFocus == WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS; 3716 } 3717 3718 //TODO(b/293640003): merge with startInputOrWindowGainedFocus once Flags.useZeroJankProxy() 3719 // is enabled. 3720 @Override startInputOrWindowGainedFocusAsync( @tartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible, int startInputSeq, boolean useAsyncShowHideMethod)3721 public void startInputOrWindowGainedFocusAsync( 3722 @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, 3723 @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, 3724 int windowFlags, @Nullable EditorInfo editorInfo, 3725 IRemoteInputConnection inputConnection, 3726 IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, 3727 int unverifiedTargetSdkVersion, @UserIdInt int userId, 3728 @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible, 3729 int startInputSeq, boolean useAsyncShowHideMethod) { 3730 // implemented by ZeroJankProxy 3731 } 3732 3733 @NonNull 3734 @Override startInputOrWindowGainedFocus( @tartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo editorInfo, IRemoteInputConnection inputConnection, IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @UserIdInt int userId, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible)3735 public InputBindResult startInputOrWindowGainedFocus( 3736 @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, 3737 @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, 3738 int windowFlags, @Nullable EditorInfo editorInfo, 3739 IRemoteInputConnection inputConnection, 3740 IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, 3741 int unverifiedTargetSdkVersion, @UserIdInt int userId, 3742 @NonNull ImeOnBackInvokedDispatcher imeDispatcher, boolean imeRequestedVisible) { 3743 if (UserHandle.getCallingUserId() != userId) { 3744 mContext.enforceCallingOrSelfPermission( 3745 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 3746 3747 if (editorInfo == null || editorInfo.targetInputMethodUser == null 3748 || editorInfo.targetInputMethodUser.getIdentifier() != userId) { 3749 throw new InvalidParameterException("EditorInfo#targetInputMethodUser must also be " 3750 + "specified for cross-user startInputOrWindowGainedFocus()"); 3751 } 3752 } 3753 if (windowToken == null) { 3754 Slog.e(TAG, "windowToken cannot be null."); 3755 return InputBindResult.NULL; 3756 } 3757 // The user represented by userId, must be running. 3758 if (!mUserManagerInternal.isUserRunning(userId)) { 3759 // There is a chance that we hit here because of race condition. Let's just 3760 // return an error code instead of crashing the caller process, which at 3761 // least has INTERACT_ACROSS_USERS_FULL permission thus is likely to be an 3762 // important process. 3763 Slog.w(TAG, "User #" + userId + " is not running."); 3764 return InputBindResult.INVALID_USER; 3765 } 3766 final var userData = getUserData(userId); 3767 try { 3768 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, 3769 "IMMS.startInputOrWindowGainedFocus"); 3770 ImeTracing.getInstance().triggerManagerServiceDump( 3771 "InputMethodManagerService#startInputOrWindowGainedFocus", mDumper); 3772 final InputBindResult result; 3773 synchronized (ImfLock.class) { 3774 final var bindingController = userData.mBindingController; 3775 // If the system is not yet ready, we shouldn't be running third party code. 3776 if (!mSystemReady) { 3777 return new InputBindResult( 3778 InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, 3779 null /* method */, null /* accessibilitySessions */, null /* channel */, 3780 bindingController.getSelectedMethodId(), 3781 bindingController.getSequenceNumber(), 3782 false /* isInputMethodSuppressingSpellChecker */); 3783 } 3784 final ClientState cs = mClientController.getClient(client.asBinder()); 3785 if (cs == null) { 3786 throw new IllegalArgumentException("Unknown client " + client.asBinder()); 3787 } 3788 final long ident = Binder.clearCallingIdentity(); 3789 try { 3790 // Verify if IMMS is in the process of switching user. 3791 if (!mConcurrentMultiUserModeEnabled && mUserSwitchHandlerTask != null) { 3792 // There is already an on-going pending user switch task. 3793 final int nextUserId = mUserSwitchHandlerTask.mToUserId; 3794 if (userId == nextUserId) { 3795 scheduleSwitchUserTaskLocked(userId, cs.mClient); 3796 return InputBindResult.USER_SWITCHING; 3797 } 3798 final int[] profileIdsWithDisabled = mUserManagerInternal.getProfileIds( 3799 mCurrentImeUserId, false /* enabledOnly */); 3800 for (int profileId : profileIdsWithDisabled) { 3801 if (profileId == userId) { 3802 scheduleSwitchUserTaskLocked(userId, cs.mClient); 3803 return InputBindResult.USER_SWITCHING; 3804 } 3805 } 3806 return InputBindResult.INVALID_USER; 3807 } 3808 3809 // Ensure that caller's focused window and display parameters are allowd to 3810 // display input method. 3811 final int imeClientFocus = mWindowManagerInternal.hasInputMethodClientFocus( 3812 windowToken, cs.mUid, cs.mPid, cs.mSelfReportedDisplayId); 3813 switch (imeClientFocus) { 3814 case WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH: 3815 Slog.e(TAG, 3816 "startInputOrWindowGainedFocusInternal: display ID mismatch."); 3817 return InputBindResult.DISPLAY_ID_MISMATCH; 3818 case WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW: 3819 // Check with the window manager to make sure this client actually 3820 // has a window with focus. If not, reject. This is thread safe 3821 // because if the focus changes some time before or after, the 3822 // next client receiving focus that has any interest in input will 3823 // be calling through here after that change happens. 3824 ProtoLog.v(IMMS_DEBUG, 3825 "Focus gain on non-focused client %s (uid=%s pid=%s)", 3826 cs.mClient, cs.mUid, cs.mPid); 3827 return InputBindResult.NOT_IME_TARGET_WINDOW; 3828 case WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID: 3829 return InputBindResult.INVALID_DISPLAY_ID; 3830 } 3831 3832 // In case mShowForced flag affects the next client to keep IME visible, when 3833 // the current client is leaving due to the next focused client, we clear 3834 // mShowForced flag when the next client's targetSdkVersion is T or higher. 3835 final boolean shouldClearFlag = 3836 mImePlatformCompatUtils.shouldClearShowForcedFlag(cs.mUid); 3837 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3838 final boolean showForced = visibilityStateComputer.mShowForced; 3839 if (userData.mImeBindingState.mFocusedWindow != windowToken 3840 && showForced && shouldClearFlag) { 3841 visibilityStateComputer.mShowForced = false; 3842 } 3843 3844 // Verify if caller is a background user. 3845 if (!mConcurrentMultiUserModeEnabled && userId != mCurrentImeUserId) { 3846 if (ArrayUtils.contains( 3847 mUserManagerInternal.getProfileIds(mCurrentImeUserId, false), 3848 userId)) { 3849 // cross-profile access is always allowed here to allow 3850 // profile-switching. 3851 scheduleSwitchUserTaskLocked(userId, cs.mClient); 3852 return InputBindResult.USER_SWITCHING; 3853 } 3854 Slog.w(TAG, "A background user is requesting window. Hiding IME."); 3855 Slog.w(TAG, "If you need to impersonate a foreground user/profile from" 3856 + " a background user, use EditorInfo.targetInputMethodUser with" 3857 + " INTERACT_ACROSS_USERS_FULL permission."); 3858 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 3859 0 /* flags */, SoftInputShowHideReason.HIDE_INVALID_USER, userId); 3860 return InputBindResult.INVALID_USER; 3861 } 3862 3863 if (editorInfo != null && !InputMethodUtils.checkIfPackageBelongsToUid( 3864 mPackageManagerInternal, cs.mUid, editorInfo.packageName)) { 3865 Slog.e(TAG, "Rejecting this client as it reported an invalid package name." 3866 + " uid=" + cs.mUid + " package=" + editorInfo.packageName); 3867 return InputBindResult.INVALID_PACKAGE_NAME; 3868 } 3869 3870 result = startInputOrWindowGainedFocusInternalLocked(startInputReason, 3871 client, windowToken, startInputFlags, softInputMode, windowFlags, 3872 editorInfo, inputConnection, remoteAccessibilityInputConnection, 3873 unverifiedTargetSdkVersion, bindingController, imeDispatcher, cs, 3874 imeRequestedVisible); 3875 } finally { 3876 Binder.restoreCallingIdentity(ident); 3877 } 3878 } 3879 if (result == null) { 3880 // This must never happen, but just in case. 3881 Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason=" 3882 + InputMethodDebug.startInputReasonToString(startInputReason) 3883 + " windowFlags=#" + Integer.toHexString(windowFlags) 3884 + " editorInfo=" + editorInfo); 3885 return InputBindResult.NULL; 3886 } 3887 3888 return result; 3889 } finally { 3890 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 3891 } 3892 } 3893 3894 @GuardedBy("ImfLock.class") 3895 @NonNull startInputOrWindowGainedFocusInternalLocked( @tartInputReason int startInputReason, IInputMethodClient client, @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, IRemoteInputConnection inputContext, @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController, @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs, boolean imeRequestedVisible)3896 private InputBindResult startInputOrWindowGainedFocusInternalLocked( 3897 @StartInputReason int startInputReason, IInputMethodClient client, 3898 @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, 3899 @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo editorInfo, 3900 IRemoteInputConnection inputContext, 3901 @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection, 3902 int unverifiedTargetSdkVersion, @NonNull InputMethodBindingController bindingController, 3903 @NonNull ImeOnBackInvokedDispatcher imeDispatcher, @NonNull ClientState cs, 3904 boolean imeRequestedVisible) { 3905 ProtoLog.v(IMMS_DEBUG, "startInputOrWindowGainedFocusInternalLocked: reason=%s" 3906 + " client=%s" 3907 + " inputContext=%s" 3908 + " editorInfo=%s" 3909 + " startInputFlags=%s" 3910 + " softInputMode=%s" 3911 + " windowFlags=#%s" 3912 + " unverifiedTargetSdkVersion=%s" 3913 + " bindingController=%s" 3914 + " imeDispatcher=%s" 3915 + " cs=%s" 3916 + " imeRequestedVisible=%s", 3917 InputMethodDebug.startInputReasonToString(startInputReason), client.asBinder(), 3918 inputContext, editorInfo, InputMethodDebug.startInputFlagsToString(startInputFlags), 3919 InputMethodDebug.softInputModeToString(softInputMode), 3920 Integer.toHexString(windowFlags), unverifiedTargetSdkVersion, bindingController, 3921 imeDispatcher, cs, imeRequestedVisible); 3922 3923 final int userId = bindingController.getUserId(); 3924 final var userData = getUserData(userId); 3925 final boolean sameWindowFocused = userData.mImeBindingState.mFocusedWindow == windowToken; 3926 final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; 3927 final boolean startInputByWinGainedFocus = 3928 (startInputFlags & StartInputFlags.WINDOW_GAINED_FOCUS) != 0; 3929 final int toolType = editorInfo != null 3930 ? editorInfo.getInitialToolType() : MotionEvent.TOOL_TYPE_UNKNOWN; 3931 3932 // Init the focused window state (e.g. whether the editor has focused or IME focus has 3933 // changed from another window). 3934 final ImeTargetWindowState windowState = new ImeTargetWindowState( 3935 softInputMode, windowFlags, !sameWindowFocused, isTextEditor, 3936 startInputByWinGainedFocus, toolType); 3937 final var visibilityStateComputer = userData.mVisibilityStateComputer; 3938 visibilityStateComputer.setWindowState(windowToken, windowState); 3939 3940 if (sameWindowFocused && isTextEditor) { 3941 ProtoLog.v(IMMS_DEBUG, 3942 "Window already focused, ignoring focus gain of: %s editorInfo=%s, token=%s, " 3943 + "startInputReason=%s", 3944 client, editorInfo, windowToken, 3945 InputMethodDebug.startInputReasonToString(startInputReason)); 3946 if (editorInfo != null) { 3947 return startInputUncheckedLocked(cs, inputContext, 3948 remoteAccessibilityInputConnection, editorInfo, startInputFlags, 3949 startInputReason, unverifiedTargetSdkVersion, imeDispatcher, 3950 bindingController); 3951 } 3952 return new InputBindResult( 3953 InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, 3954 null, null, null, null, -1, false); 3955 } 3956 3957 userData.mImeBindingState = new ImeBindingState(bindingController.getUserId(), windowToken, 3958 softInputMode, cs, editorInfo); 3959 mFocusedWindowPerceptible.put(windowToken, true); 3960 3961 // We want to start input before showing the IME, but after closing 3962 // it. We want to do this after closing it to help the IME disappear 3963 // more quickly (not get stuck behind it initializing itself for the 3964 // new focused input, even if its window wants to hide the IME). 3965 boolean didStart = false; 3966 InputBindResult res = null; 3967 3968 final ImeVisibilityResult imeVisRes = visibilityStateComputer.computeState(windowState, 3969 isSoftInputModeStateVisibleAllowed(unverifiedTargetSdkVersion, startInputFlags), 3970 imeRequestedVisible); 3971 if (imeVisRes != null) { 3972 boolean isShow = false; 3973 switch (imeVisRes.getReason()) { 3974 case SoftInputShowHideReason.SHOW_RESTORE_IME_VISIBILITY: 3975 case SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV: 3976 case SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV: 3977 case SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE: 3978 isShow = true; 3979 3980 if (editorInfo != null) { 3981 res = startInputUncheckedLocked(cs, inputContext, 3982 remoteAccessibilityInputConnection, editorInfo, startInputFlags, 3983 startInputReason, unverifiedTargetSdkVersion, 3984 imeDispatcher, bindingController); 3985 didStart = true; 3986 } 3987 break; 3988 } 3989 final var statsToken = createStatsTokenForFocusedClient(isShow, imeVisRes.getReason(), 3990 userId); 3991 mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, 3992 statsToken, imeVisRes.getState(), imeVisRes.getReason(), userId); 3993 if (imeVisRes.getReason() == SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW) { 3994 // If focused display changed, we should unbind current method 3995 // to make app window in previous display relayout after Ime 3996 // window token removed. 3997 // Note that we can trust client's display ID as long as it matches 3998 // to the display ID obtained from the window. 3999 if (cs.mSelfReportedDisplayId != bindingController.getCurTokenDisplayId()) { 4000 bindingController.unbindCurrentMethod(); 4001 } 4002 } 4003 } 4004 if (!didStart) { 4005 if (editorInfo != null) { 4006 res = startInputUncheckedLocked(cs, inputContext, 4007 remoteAccessibilityInputConnection, editorInfo, startInputFlags, 4008 startInputReason, unverifiedTargetSdkVersion, 4009 imeDispatcher, bindingController); 4010 } else { 4011 res = InputBindResult.NULL_EDITOR_INFO; 4012 } 4013 } 4014 return res; 4015 } 4016 4017 @GuardedBy("ImfLock.class") canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, @Nullable ImeTracker.Token statsToken, @UserIdInt int userId)4018 private boolean canInteractWithImeLocked(int uid, IInputMethodClient client, String methodName, 4019 @Nullable ImeTracker.Token statsToken, @UserIdInt int userId) { 4020 final var userData = getUserData(userId); 4021 if (userData.mCurClient == null || client == null 4022 || userData.mCurClient.mClient.asBinder() != client.asBinder()) { 4023 // We need to check if this is the current client with 4024 // focus in the window manager, to allow this call to 4025 // be made before input is started in it. 4026 final ClientState cs = mClientController.getClient(client.asBinder()); 4027 if (cs == null) { 4028 ImeTracker.forLogging().onFailed(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); 4029 throw new IllegalArgumentException("unknown client " + client.asBinder()); 4030 } 4031 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_KNOWN); 4032 if (!isImeClientFocused(userData.mImeBindingState.mFocusedWindow, cs)) { 4033 Slog.w(TAG, String.format("Ignoring %s of uid %d : %s", methodName, uid, client)); 4034 return false; 4035 } 4036 } 4037 ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_CLIENT_FOCUSED); 4038 return true; 4039 } 4040 4041 @GuardedBy("ImfLock.class") canShowInputMethodPickerLocked(IInputMethodClient client, @UserIdInt int userId)4042 private boolean canShowInputMethodPickerLocked(IInputMethodClient client, 4043 @UserIdInt int userId) { 4044 final int uid = Binder.getCallingUid(); 4045 final var userData = getUserData(userId); 4046 if (userData.mImeBindingState.mFocusedWindowClient != null && client != null 4047 && userData.mImeBindingState.mFocusedWindowClient.mClient.asBinder() 4048 == client.asBinder()) { 4049 return true; 4050 } 4051 if (userId != UserHandle.getUserId(uid)) { 4052 return false; 4053 } 4054 final var curIntent = getInputMethodBindingController(userId).getCurIntent(); 4055 if (curIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( 4056 mPackageManagerInternal, uid, curIntent.getComponent().getPackageName())) { 4057 return true; 4058 } 4059 return false; 4060 } 4061 4062 @Override showInputMethodPickerFromClient(IInputMethodClient client, int auxiliarySubtypeMode)4063 public void showInputMethodPickerFromClient(IInputMethodClient client, 4064 int auxiliarySubtypeMode) { 4065 if (mConcurrentMultiUserModeEnabled) { 4066 Slog.w(TAG, "showInputMethodPickerFromClient is not enabled on automotive"); 4067 return; 4068 } 4069 final int callingUserId = UserHandle.getCallingUserId(); 4070 synchronized (ImfLock.class) { 4071 final int userId = resolveImeUserIdLocked(callingUserId); 4072 if (!canShowInputMethodPickerLocked(client, userId)) { 4073 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 4074 + Binder.getCallingUid() + ": " + client); 4075 return; 4076 } 4077 final var userData = getUserData(userId); 4078 // Always call subtype picker, because subtype picker is a superset of input method 4079 // picker. 4080 final int displayId = (userData.mCurClient != null) 4081 ? userData.mCurClient.mSelfReportedDisplayId : DEFAULT_DISPLAY; 4082 mHandler.post(() -> { 4083 synchronized (ImfLock.class) { 4084 showInputMethodPickerLocked(auxiliarySubtypeMode, displayId, userId); 4085 } 4086 }); 4087 } 4088 } 4089 4090 @IInputMethodManagerImpl.PermissionVerified(allOf = { 4091 Manifest.permission.INTERACT_ACROSS_USERS_FULL, 4092 Manifest.permission.WRITE_SECURE_SETTINGS}) 4093 @Override showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId)4094 public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) { 4095 // Always call subtype picker, because subtype picker is a superset of input method 4096 // picker. 4097 mHandler.post(() -> { 4098 synchronized (ImfLock.class) { 4099 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); 4100 showInputMethodPickerLocked(auxiliarySubtypeMode, displayId, userId); 4101 } 4102 }); 4103 } 4104 4105 /** 4106 * A test API for CTS to make sure that the input method menu is showing. 4107 */ 4108 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) isInputMethodPickerShownForTest()4109 public boolean isInputMethodPickerShownForTest() { 4110 synchronized (ImfLock.class) { 4111 return Flags.imeSwitcherRevamp() 4112 ? mMenuControllerNew.isShowing() 4113 : mMenuController.isisInputMethodPickerShownForTestLocked(); 4114 } 4115 } 4116 4117 @IInputMethodManagerImpl.PermissionVerified(allOf = { 4118 Manifest.permission.INTERACT_ACROSS_USERS_FULL, 4119 Manifest.permission.WRITE_SECURE_SETTINGS}) 4120 @Override onImeSwitchButtonClickFromSystem(int displayId)4121 public void onImeSwitchButtonClickFromSystem(int displayId) { 4122 synchronized (ImfLock.class) { 4123 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); 4124 final var userData = getUserData(userId); 4125 4126 onImeSwitchButtonClickLocked(displayId, userData); 4127 } 4128 } 4129 4130 /** 4131 * Handles a click on the IME switch button. Depending on the number of enabled IME subtypes, 4132 * this will either switch to the next IME/subtype, or show the input method picker dialog. 4133 * 4134 * @param displayId The ID of the display where the input method picker dialog should be shown. 4135 * @param userData The data of the user for which to switch IMEs or show the picker dialog. 4136 */ 4137 @BinderThread 4138 @GuardedBy("ImfLock.class") onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData)4139 private void onImeSwitchButtonClickLocked(int displayId, @NonNull UserData userData) { 4140 final int userId = userData.mUserId; 4141 if (hasMultipleSubtypesForSwitcher(true /* nonAuxOnly */, userId)) { 4142 switchToNextInputMethodLocked(false /* onlyCurrentIme */, userData); 4143 } else { 4144 showInputMethodPickerFromSystem( 4145 InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, displayId); 4146 } 4147 } 4148 4149 /** 4150 * A test API for CTS to check whether the IME Switcher button should be shown when the IME 4151 * is shown. 4152 */ 4153 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) shouldShowImeSwitcherButtonForTest()4154 public boolean shouldShowImeSwitcherButtonForTest() { 4155 final int callingUserId = UserHandle.getCallingUserId(); 4156 synchronized (ImfLock.class) { 4157 final int userId = resolveImeUserIdLocked(callingUserId); 4158 return shouldShowImeSwitcherLocked( 4159 InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE, userId); 4160 } 4161 } 4162 4163 @NonNull getExceptionForUnknownImeId( @ullable String imeId)4164 private static IllegalArgumentException getExceptionForUnknownImeId( 4165 @Nullable String imeId) { 4166 return new IllegalArgumentException("Unknown id: " + imeId); 4167 } 4168 4169 @BinderThread 4170 @GuardedBy("ImfLock.class") setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype, @NonNull UserData userData)4171 private void setInputMethodAndSubtypeLocked(String id, @Nullable InputMethodSubtype subtype, 4172 @NonNull UserData userData) { 4173 final int callingUid = Binder.getCallingUid(); 4174 final int userId = userData.mUserId; 4175 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4176 final InputMethodInfo imi = settings.getMethodMap().get(id); 4177 if (imi == null || !canCallerAccessInputMethod( 4178 imi.getPackageName(), callingUid, userId, settings)) { 4179 throw getExceptionForUnknownImeId(id); 4180 } 4181 final int subtypeIndex = subtype != null 4182 ? SubtypeUtils.getSubtypeIndexFromHashCode(imi, subtype.hashCode()) 4183 : NOT_A_SUBTYPE_INDEX; 4184 setInputMethodWithSubtypeIndexLocked(id, subtypeIndex, userId); 4185 } 4186 4187 @BinderThread 4188 @GuardedBy("ImfLock.class") switchToPreviousInputMethodLocked(@onNull UserData userData)4189 private boolean switchToPreviousInputMethodLocked(@NonNull UserData userData) { 4190 final int userId = userData.mUserId; 4191 final var bindingController = userData.mBindingController; 4192 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4193 final Pair<String, String> lastIme = settings.getLastInputMethodAndSubtype(); 4194 final InputMethodInfo lastImi; 4195 if (lastIme != null) { 4196 lastImi = settings.getMethodMap().get(lastIme.first); 4197 } else { 4198 lastImi = null; 4199 } 4200 final var currentSubtype = bindingController.getCurrentSubtype(); 4201 String targetLastImiId = null; 4202 int subtypeIndex = NOT_A_SUBTYPE_INDEX; 4203 if (lastIme != null && lastImi != null) { 4204 final boolean imiIdIsSame = lastImi.getId().equals( 4205 bindingController.getSelectedMethodId()); 4206 final int lastSubtypeHash = Integer.parseInt(lastIme.second); 4207 final int currentSubtypeHash = currentSubtype == null ? NOT_A_SUBTYPE_INDEX 4208 : currentSubtype.hashCode(); 4209 // If the last IME is the same as the current IME and the last subtype is not 4210 // defined, there is no need to switch to the last IME. 4211 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { 4212 targetLastImiId = lastIme.first; 4213 subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(lastImi, lastSubtypeHash); 4214 } 4215 } 4216 4217 if (TextUtils.isEmpty(targetLastImiId) 4218 && !InputMethodUtils.canAddToLastInputMethod(currentSubtype)) { 4219 // This is a safety net. If the currentSubtype can't be added to the history 4220 // and the framework couldn't find the last ime, we will make the last ime be 4221 // the most applicable enabled keyboard subtype of the system imes. 4222 final List<InputMethodInfo> enabled = settings.getEnabledInputMethodList(); 4223 final int enabledCount = enabled.size(); 4224 final String locale; 4225 if (currentSubtype != null 4226 && !TextUtils.isEmpty(currentSubtype.getLocale())) { 4227 locale = currentSubtype.getLocale(); 4228 } else { 4229 locale = SystemLocaleWrapper.get(userId).get(0).toString(); 4230 } 4231 for (int i = 0; i < enabledCount; ++i) { 4232 final InputMethodInfo imi = enabled.get(i); 4233 if (imi.getSubtypeCount() > 0 && imi.isSystem()) { 4234 InputMethodSubtype keyboardSubtype = 4235 SubtypeUtils.findLastResortApplicableSubtype( 4236 SubtypeUtils.getSubtypes(imi), 4237 SubtypeUtils.SUBTYPE_MODE_KEYBOARD, locale, true); 4238 if (keyboardSubtype != null) { 4239 targetLastImiId = imi.getId(); 4240 subtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi, 4241 keyboardSubtype.hashCode()); 4242 if (keyboardSubtype.getLocale().equals(locale)) { 4243 break; 4244 } 4245 } 4246 } 4247 } 4248 } 4249 4250 if (!TextUtils.isEmpty(targetLastImiId)) { 4251 ProtoLog.v(IMMS_DEBUG, "Switch to: %s, %s, from: %s, %s", lastImi.getId(), 4252 lastIme.second, bindingController.getSelectedMethodId(), subtypeIndex); 4253 setInputMethodWithSubtypeIndexLocked(targetLastImiId, subtypeIndex, userId); 4254 return true; 4255 } else { 4256 return false; 4257 } 4258 } 4259 4260 @BinderThread 4261 @GuardedBy("ImfLock.class") switchToNextInputMethodLocked(boolean onlyCurrentIme, @NonNull UserData userData)4262 private boolean switchToNextInputMethodLocked(boolean onlyCurrentIme, 4263 @NonNull UserData userData) { 4264 final var bindingController = userData.mBindingController; 4265 final var currentImi = bindingController.getSelectedMethod(); 4266 if (currentImi == null) { 4267 return false; 4268 } 4269 final ImeSubtypeListItem nextSubtype = userData.mSwitchingController 4270 .getNextInputMethodLocked(onlyCurrentIme, currentImi, 4271 bindingController.getCurrentSubtype(), 4272 MODE_AUTO, true /* forward */); 4273 if (nextSubtype == null) { 4274 return false; 4275 } 4276 setInputMethodWithSubtypeIndexLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeIndex, 4277 userData.mUserId); 4278 return true; 4279 } 4280 4281 @BinderThread 4282 @GuardedBy("ImfLock.class") shouldOfferSwitchingToNextInputMethodLocked(@onNull UserData userData)4283 private boolean shouldOfferSwitchingToNextInputMethodLocked(@NonNull UserData userData) { 4284 final var bindingController = userData.mBindingController; 4285 final var currentImi = bindingController.getSelectedMethod(); 4286 if (currentImi == null) { 4287 return false; 4288 } 4289 final ImeSubtypeListItem nextSubtype = userData.mSwitchingController 4290 .getNextInputMethodLocked(false /* onlyCurrentIme */, currentImi, 4291 bindingController.getCurrentSubtype(), 4292 MODE_AUTO, true /* forward */); 4293 return nextSubtype != null; 4294 } 4295 4296 @Override getLastInputMethodSubtype(@serIdInt int userId)4297 public InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) { 4298 if (UserHandle.getCallingUserId() != userId) { 4299 mContext.enforceCallingOrSelfPermission( 4300 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 4301 } 4302 synchronized (ImfLock.class) { 4303 return InputMethodSettingsRepository.get(userId).getLastInputMethodSubtype(); 4304 } 4305 } 4306 4307 @Override setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, @UserIdInt int userId)4308 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes, 4309 @UserIdInt int userId) { 4310 if (UserHandle.getCallingUserId() != userId) { 4311 mContext.enforceCallingOrSelfPermission( 4312 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 4313 } 4314 final int callingUid = Binder.getCallingUid(); 4315 4316 // By this IPC call, only a process which shares the same uid with the IME can add 4317 // additional input method subtypes to the IME. 4318 if (TextUtils.isEmpty(imiId) || subtypes == null) return; 4319 final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>(); 4320 for (InputMethodSubtype subtype : subtypes) { 4321 if (!toBeAdded.contains(subtype)) { 4322 toBeAdded.add(subtype); 4323 } else { 4324 Slog.w(TAG, "Duplicated subtype definition found: " 4325 + subtype.getLocale() + ", " + subtype.getMode()); 4326 } 4327 } 4328 final var userData = getUserData(userId); 4329 synchronized (ImfLock.class) { 4330 if (!mSystemReady) { 4331 return; 4332 } 4333 4334 final var additionalSubtypeMap = AdditionalSubtypeMapRepository.get(userId); 4335 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4336 final var newAdditionalSubtypeMap = settings.getNewAdditionalSubtypeMap( 4337 imiId, toBeAdded, additionalSubtypeMap, mPackageManagerInternal, callingUid); 4338 if (additionalSubtypeMap != newAdditionalSubtypeMap) { 4339 AdditionalSubtypeMapRepository.putAndSave(userId, newAdditionalSubtypeMap, 4340 settings.getMethodMap()); 4341 final long ident = Binder.clearCallingIdentity(); 4342 try { 4343 final var methodMap = userData.mRawInputMethodMap.get().toInputMethodMap( 4344 AdditionalSubtypeMapRepository.get(userId), DirectBootAwareness.AUTO, 4345 userData.mIsUnlockingOrUnlocked.get()); 4346 final var newSettings = InputMethodSettings.create(methodMap, userId); 4347 InputMethodSettingsRepository.put(userId, newSettings); 4348 postInputMethodSettingUpdatedLocked(false /* resetDefaultEnabledIme */, userId); 4349 } finally { 4350 Binder.restoreCallingIdentity(ident); 4351 } 4352 } 4353 } 4354 } 4355 4356 @Override setExplicitlyEnabledInputMethodSubtypes(String imeId, @NonNull int[] subtypeHashCodes, @UserIdInt int userId)4357 public void setExplicitlyEnabledInputMethodSubtypes(String imeId, 4358 @NonNull int[] subtypeHashCodes, @UserIdInt int userId) { 4359 if (UserHandle.getCallingUserId() != userId) { 4360 mContext.enforceCallingOrSelfPermission( 4361 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 4362 } 4363 final int callingUid = Binder.getCallingUid(); 4364 final ComponentName imeComponentName = 4365 imeId != null ? ComponentName.unflattenFromString(imeId) : null; 4366 if (imeComponentName == null || !InputMethodUtils.checkIfPackageBelongsToUid( 4367 mPackageManagerInternal, callingUid, imeComponentName.getPackageName())) { 4368 throw new SecurityException("Calling UID=" + callingUid + " does not belong to imeId=" 4369 + imeId); 4370 } 4371 Objects.requireNonNull(subtypeHashCodes, "subtypeHashCodes must not be null"); 4372 4373 final long ident = Binder.clearCallingIdentity(); 4374 try { 4375 synchronized (ImfLock.class) { 4376 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4377 if (!settings.setEnabledInputMethodSubtypes(imeId, subtypeHashCodes)) { 4378 return; 4379 } 4380 // To avoid unnecessary "updateInputMethodsFromSettingsLocked" from happening. 4381 final var userData = getUserData(userId); 4382 userData.mLastEnabledInputMethodsStr = settings.getEnabledInputMethodsStr(); 4383 updateInputMethodsFromSettingsLocked(false /* enabledChanged */, userId); 4384 } 4385 } finally { 4386 Binder.restoreCallingIdentity(ident); 4387 } 4388 } 4389 4390 /** 4391 * This is kept due to {@code @UnsupportedAppUsage} in 4392 * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in 4393 * {@link InputMethodService#onCreate()}. 4394 * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)} 4395 * 4396 * @deprecated TODO(b/113914148): Check if we can remove this 4397 */ 4398 @Override 4399 @Deprecated getInputMethodWindowVisibleHeight(@onNull IInputMethodClient client)4400 public int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) { 4401 final int callingUid = Binder.getCallingUid(); 4402 final int callingUserId = UserHandle.getCallingUserId(); 4403 return Binder.withCleanCallingIdentity(() -> { 4404 final int curTokenDisplayId; 4405 synchronized (ImfLock.class) { 4406 final int userId = resolveImeUserIdLocked(callingUserId); 4407 if (!canInteractWithImeLocked(callingUid, client, 4408 "getInputMethodWindowVisibleHeight", null /* statsToken */, userId)) { 4409 return 0; 4410 } 4411 final var bindingController = getInputMethodBindingController(userId); 4412 // This should probably use the caller's display id, but because this is unsupported 4413 // and maintained only for compatibility, there's no point in fixing it. 4414 curTokenDisplayId = bindingController.getCurTokenDisplayId(); 4415 } 4416 return mWindowManagerInternal.getInputMethodWindowVisibleHeight(curTokenDisplayId); 4417 }); 4418 } 4419 4420 @IInputMethodManagerImpl.PermissionVerified(allOf = { 4421 Manifest.permission.INTERACT_ACROSS_USERS_FULL, 4422 Manifest.permission.INTERNAL_SYSTEM_WINDOW}) 4423 @Override removeImeSurface(int displayId)4424 public void removeImeSurface(int displayId) { 4425 mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget(); 4426 } 4427 4428 @Override removeImeSurfaceFromWindowAsync(IBinder windowToken)4429 public void removeImeSurfaceFromWindowAsync(IBinder windowToken) { 4430 // No permission check, because we'll only execute the request if the calling window is 4431 // also the current IME client. 4432 mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); 4433 } 4434 registerDeviceListenerAndCheckStylusSupport()4435 private void registerDeviceListenerAndCheckStylusSupport() { 4436 final InputManager im = mContext.getSystemService(InputManager.class); 4437 final IntArray stylusIds = getStylusInputDeviceIds(im); 4438 if (stylusIds.size() > 0) { 4439 synchronized (ImfLock.class) { 4440 mStylusIds = new IntArray(); 4441 mStylusIds.addAll(stylusIds); 4442 } 4443 } 4444 im.registerInputDeviceListener(new InputManager.InputDeviceListener() { 4445 @Override 4446 public void onInputDeviceAdded(int deviceId) { 4447 InputDevice device = im.getInputDevice(deviceId); 4448 if (device != null && isStylusDevice(device)) { 4449 add(deviceId); 4450 } 4451 } 4452 4453 @Override 4454 public void onInputDeviceRemoved(int deviceId) { 4455 remove(deviceId); 4456 } 4457 4458 @Override 4459 public void onInputDeviceChanged(int deviceId) { 4460 InputDevice device = im.getInputDevice(deviceId); 4461 if (device == null) { 4462 return; 4463 } 4464 if (isStylusDevice(device)) { 4465 add(deviceId); 4466 } else { 4467 remove(deviceId); 4468 } 4469 } 4470 4471 private void add(int deviceId) { 4472 synchronized (ImfLock.class) { 4473 addStylusDeviceIdLocked(deviceId); 4474 } 4475 } 4476 4477 private void remove(int deviceId) { 4478 synchronized (ImfLock.class) { 4479 removeStylusDeviceIdLocked(deviceId); 4480 } 4481 } 4482 }, mHandler); 4483 } 4484 4485 @GuardedBy("ImfLock.class") addStylusDeviceIdLocked(int deviceId)4486 private void addStylusDeviceIdLocked(int deviceId) { 4487 if (mStylusIds == null) { 4488 mStylusIds = new IntArray(); 4489 } else if (mStylusIds.indexOf(deviceId) != -1) { 4490 return; 4491 } 4492 Slog.d(TAG, "New Stylus deviceId" + deviceId + " added."); 4493 mStylusIds.add(deviceId); 4494 // a new Stylus is detected. If IME supports handwriting, and we don't have 4495 // handwriting initialized, lets do it now. 4496 final var bindingController = getInputMethodBindingController(mCurrentImeUserId); 4497 if (!mHwController.getCurrentRequestId().isPresent() 4498 && bindingController.supportsStylusHandwriting()) { 4499 scheduleResetStylusHandwriting(); 4500 } 4501 } 4502 removeStylusDeviceIdLocked(int deviceId)4503 private void removeStylusDeviceIdLocked(int deviceId) { 4504 if (mStylusIds == null || mStylusIds.size() == 0) { 4505 return; 4506 } 4507 int index; 4508 if ((index = mStylusIds.indexOf(deviceId)) != -1) { 4509 mStylusIds.remove(index); 4510 Slog.d(TAG, "Stylus deviceId: " + deviceId + " removed."); 4511 } 4512 if (mStylusIds.size() == 0) { 4513 // no more supported stylus(es) in system. 4514 mHwController.reset(); 4515 scheduleRemoveStylusHandwritingWindow(); 4516 } 4517 } 4518 isStylusDevice(InputDevice inputDevice)4519 private static boolean isStylusDevice(InputDevice inputDevice) { 4520 return inputDevice.supportsSource(InputDevice.SOURCE_STYLUS) 4521 || inputDevice.supportsSource(InputDevice.SOURCE_BLUETOOTH_STYLUS); 4522 } 4523 isFingerDevice(InputDevice inputDevice)4524 private static boolean isFingerDevice(InputDevice inputDevice) { 4525 return inputDevice.supportsSource(InputDevice.SOURCE_TOUCHSCREEN); 4526 } 4527 4528 @GuardedBy("ImfLock.class") hasSupportedStylusLocked()4529 private boolean hasSupportedStylusLocked() { 4530 return mStylusIds != null && mStylusIds.size() != 0; 4531 } 4532 4533 /** 4534 * Helper method that adds a virtual stylus id for next handwriting session test if 4535 * a stylus deviceId is not already registered on device. 4536 */ 4537 @BinderThread 4538 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) 4539 @Override addVirtualStylusIdForTestSession(IInputMethodClient client)4540 public void addVirtualStylusIdForTestSession(IInputMethodClient client) { 4541 final int uid = Binder.getCallingUid(); 4542 final int callingUserId = UserHandle.getUserId(uid); 4543 synchronized (ImfLock.class) { 4544 final int userId = resolveImeUserIdLocked(callingUserId); 4545 if (!canInteractWithImeLocked(uid, client, "addVirtualStylusIdForTestSession", 4546 null /* statsToken */, userId)) { 4547 return; 4548 } 4549 final long ident = Binder.clearCallingIdentity(); 4550 try { 4551 ProtoLog.v(IMMS_DEBUG, "Adding virtual stylus id for session"); 4552 addStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST); 4553 } finally { 4554 Binder.restoreCallingIdentity(ident); 4555 } 4556 } 4557 } 4558 4559 /** 4560 * Helper method to set a stylus idle-timeout after which handwriting {@code InkWindow} 4561 * will be removed. 4562 * 4563 * @param timeout to set in milliseconds. To reset to default, use a value <= zero 4564 */ 4565 @BinderThread 4566 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.TEST_INPUT_METHOD) 4567 @Override setStylusWindowIdleTimeoutForTest( IInputMethodClient client, @DurationMillisLong long timeout)4568 public void setStylusWindowIdleTimeoutForTest( 4569 IInputMethodClient client, @DurationMillisLong long timeout) { 4570 final int uid = Binder.getCallingUid(); 4571 final int callingUserId = UserHandle.getUserId(uid); 4572 synchronized (ImfLock.class) { 4573 final int userId = resolveImeUserIdLocked(callingUserId); 4574 if (!canInteractWithImeLocked(uid, client, "setStylusWindowIdleTimeoutForTest", 4575 null /* statsToken */, userId)) { 4576 return; 4577 } 4578 final long ident = Binder.clearCallingIdentity(); 4579 try { 4580 ProtoLog.v(IMMS_DEBUG, "Setting stylus window idle timeout"); 4581 getCurMethodLocked().setStylusWindowIdleTimeoutForTest(timeout); 4582 } finally { 4583 Binder.restoreCallingIdentity(ident); 4584 } 4585 } 4586 } 4587 4588 @GuardedBy("ImfLock.class") removeVirtualStylusIdForTestSessionLocked()4589 private void removeVirtualStylusIdForTestSessionLocked() { 4590 removeStylusDeviceIdLocked(VIRTUAL_STYLUS_ID_FOR_TEST); 4591 } 4592 getStylusInputDeviceIds(InputManager im)4593 private static IntArray getStylusInputDeviceIds(InputManager im) { 4594 IntArray stylusIds = new IntArray(); 4595 for (int id : im.getInputDeviceIds()) { 4596 InputDevice device = im.getInputDevice(id); 4597 if (device != null && device.isEnabled() && isStylusDevice(device)) { 4598 stylusIds.add(id); 4599 } 4600 } 4601 4602 return stylusIds; 4603 } 4604 4605 /** 4606 * Starting point for dumping the IME tracing information in proto format. 4607 * 4608 * @param protoDump dump information from the IME client side 4609 */ 4610 @BinderThread 4611 @Override startProtoDump(byte[] protoDump, int source, String where)4612 public void startProtoDump(byte[] protoDump, int source, String where) { 4613 if (protoDump == null && source != ImeTracing.IME_TRACING_FROM_IMMS) { 4614 // Dump not triggered from IMMS, but no proto information provided. 4615 return; 4616 } 4617 ImeTracing tracingInstance = ImeTracing.getInstance(); 4618 if (!tracingInstance.isAvailable() || !tracingInstance.isEnabled()) { 4619 return; 4620 } 4621 4622 ProtoOutputStream proto = new ProtoOutputStream(); 4623 switch (source) { 4624 case ImeTracing.IME_TRACING_FROM_CLIENT: 4625 final long client_token = proto.start(InputMethodClientsTraceFileProto.ENTRY); 4626 proto.write(InputMethodClientsTraceProto.ELAPSED_REALTIME_NANOS, 4627 SystemClock.elapsedRealtimeNanos()); 4628 proto.write(InputMethodClientsTraceProto.WHERE, where); 4629 proto.write(InputMethodClientsTraceProto.CLIENT, protoDump); 4630 proto.end(client_token); 4631 break; 4632 case ImeTracing.IME_TRACING_FROM_IMS: 4633 final long service_token = proto.start(InputMethodServiceTraceFileProto.ENTRY); 4634 proto.write(InputMethodServiceTraceProto.ELAPSED_REALTIME_NANOS, 4635 SystemClock.elapsedRealtimeNanos()); 4636 proto.write(InputMethodServiceTraceProto.WHERE, where); 4637 proto.write(InputMethodServiceTraceProto.INPUT_METHOD_SERVICE, protoDump); 4638 proto.end(service_token); 4639 break; 4640 case ImeTracing.IME_TRACING_FROM_IMMS: 4641 final long managerservice_token = 4642 proto.start(InputMethodManagerServiceTraceFileProto.ENTRY); 4643 proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS, 4644 SystemClock.elapsedRealtimeNanos()); 4645 proto.write(InputMethodManagerServiceTraceProto.WHERE, where); 4646 dumpDebug(proto, 4647 InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE); 4648 proto.end(managerservice_token); 4649 break; 4650 default: 4651 // Dump triggered by a source not recognised. 4652 return; 4653 } 4654 tracingInstance.addToBuffer(proto, source); 4655 } 4656 4657 @BinderThread 4658 @Override isImeTraceEnabled()4659 public boolean isImeTraceEnabled() { 4660 return ImeTracing.getInstance().isEnabled(); 4661 } 4662 4663 @BinderThread 4664 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING) 4665 @Override startImeTrace()4666 public void startImeTrace() { 4667 ImeTracing.getInstance().startTrace(null /* printwriter */); 4668 synchronized (ImfLock.class) { 4669 mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(true /* enabled */)); 4670 } 4671 } 4672 4673 @BinderThread 4674 @IInputMethodManagerImpl.PermissionVerified(Manifest.permission.CONTROL_UI_TRACING) 4675 @Override stopImeTrace()4676 public void stopImeTrace() { 4677 ImeTracing.getInstance().stopTrace(null /* printwriter */); 4678 synchronized (ImfLock.class) { 4679 mClientController.forAllClients(c -> c.mClient.setImeTraceEnabled(false /* enabled */)); 4680 } 4681 } 4682 4683 // TODO(b/356239178): Make dump proto multi-user aware. dumpDebug(ProtoOutputStream proto, long fieldId)4684 private void dumpDebug(ProtoOutputStream proto, long fieldId) { 4685 synchronized (ImfLock.class) { 4686 final int userId = mCurrentImeUserId; 4687 final var userData = getUserData(userId); 4688 final var bindingController = userData.mBindingController; 4689 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4690 final long token = proto.start(fieldId); 4691 proto.write(CUR_METHOD_ID, bindingController.getSelectedMethodId()); 4692 proto.write(CUR_SEQ, bindingController.getSequenceNumber()); 4693 proto.write(CUR_CLIENT, Objects.toString(userData.mCurClient)); 4694 userData.mImeBindingState.dumpDebug(proto, mWindowManagerInternal); 4695 proto.write(LAST_IME_TARGET_WINDOW_NAME, mWindowManagerInternal.getWindowName( 4696 visibilityStateComputer.getLastImeTargetWindow())); 4697 proto.write(CUR_FOCUSED_WINDOW_SOFT_INPUT_MODE, InputMethodDebug.softInputModeToString( 4698 userData.mImeBindingState.mFocusedWindowSoftInputMode)); 4699 if (userData.mCurEditorInfo != null) { 4700 userData.mCurEditorInfo.dumpDebug(proto, CUR_ATTRIBUTE); 4701 } 4702 proto.write(CUR_ID, bindingController.getCurId()); 4703 visibilityStateComputer.dumpDebug(proto, fieldId); 4704 proto.write(IN_FULLSCREEN_MODE, userData.mInFullscreenMode); 4705 proto.write(CUR_TOKEN, Objects.toString(bindingController.getCurToken())); 4706 proto.write(CUR_TOKEN_DISPLAY_ID, bindingController.getCurTokenDisplayId()); 4707 proto.write(SYSTEM_READY, mSystemReady); 4708 proto.write(HAVE_CONNECTION, bindingController.hasMainConnection()); 4709 proto.write(BOUND_TO_METHOD, userData.mBoundToMethod); 4710 proto.write(IS_INTERACTIVE, mIsInteractive); 4711 proto.write(BACK_DISPOSITION, bindingController.getBackDisposition()); 4712 proto.write(IME_WINDOW_VISIBILITY, bindingController.getImeWindowVis()); 4713 if (!Flags.imeSwitcherRevamp()) { 4714 proto.write(SHOW_IME_WITH_HARD_KEYBOARD, 4715 mMenuController.getShowImeWithHardKeyboard()); 4716 } 4717 proto.write(CONCURRENT_MULTI_USER_MODE_ENABLED, mConcurrentMultiUserModeEnabled); 4718 proto.end(token); 4719 } 4720 } 4721 4722 @BinderThread 4723 @GuardedBy("ImfLock.class") notifyUserActionLocked(@onNull UserData userData)4724 private void notifyUserActionLocked(@NonNull UserData userData) { 4725 ProtoLog.v(IMMS_DEBUG, "Got the notification of a user action."); 4726 final var bindingController = userData.mBindingController; 4727 final InputMethodInfo imi = bindingController.getSelectedMethod(); 4728 if (imi != null) { 4729 userData.mSwitchingController.onUserActionLocked(imi, 4730 bindingController.getCurrentSubtype()); 4731 } 4732 } 4733 4734 @BinderThread 4735 @GuardedBy("ImfLock.class") applyImeVisibilityLocked(IBinder windowToken, boolean setVisible, @NonNull ImeTracker.Token statsToken, @NonNull UserData userData)4736 private void applyImeVisibilityLocked(IBinder windowToken, boolean setVisible, 4737 @NonNull ImeTracker.Token statsToken, @NonNull UserData userData) { 4738 try { 4739 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.applyImeVisibilityLocked"); 4740 final int userId = userData.mUserId; 4741 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4742 final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom( 4743 windowToken, userId); 4744 mVisibilityApplier.applyImeVisibility(requestToken, statsToken, 4745 setVisible ? STATE_SHOW_IME : STATE_HIDE_IME, 4746 SoftInputShowHideReason.NOT_SET /* ignore reason */, userId); 4747 } finally { 4748 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 4749 } 4750 } 4751 4752 @BinderThread 4753 @GuardedBy("ImfLock.class") resetStylusHandwritingLocked(int requestId)4754 private void resetStylusHandwritingLocked(int requestId) { 4755 final OptionalInt curRequest = mHwController.getCurrentRequestId(); 4756 if (!curRequest.isPresent() || curRequest.getAsInt() != requestId) { 4757 Slog.w(TAG, "IME requested to finish handwriting with a mismatched requestId: " 4758 + requestId); 4759 } 4760 removeVirtualStylusIdForTestSessionLocked(); 4761 scheduleResetStylusHandwriting(); 4762 } 4763 4764 @GuardedBy("ImfLock.class") setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex, @UserIdInt int userId)4765 private void setInputMethodWithSubtypeIndexLocked(String id, int subtypeIndex, 4766 @UserIdInt int userId) { 4767 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4768 if (settings.getMethodMap().get(id) != null 4769 && settings.getEnabledInputMethodListWithFilter( 4770 (info) -> info.getId().equals(id)).isEmpty()) { 4771 throw new IllegalStateException("Requested IME is not enabled: " + id); 4772 } 4773 final long ident = Binder.clearCallingIdentity(); 4774 try { 4775 setInputMethodLocked(id, subtypeIndex, userId); 4776 } finally { 4777 Binder.restoreCallingIdentity(ident); 4778 } 4779 } 4780 4781 /** 4782 * Called right after {@link IInputMethod#showSoftInput} or {@link IInputMethod#hideSoftInput}. 4783 */ 4784 @GuardedBy("ImfLock.class") onShowHideSoftInputRequested(boolean show, IBinder requestImeToken, @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken, @UserIdInt int userId)4785 void onShowHideSoftInputRequested(boolean show, IBinder requestImeToken, 4786 @SoftInputShowHideReason int reason, @Nullable ImeTracker.Token statsToken, 4787 @UserIdInt int userId) { 4788 final var userData = getUserData(userId); 4789 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4790 final IBinder requestToken = visibilityStateComputer.getWindowTokenFrom(requestImeToken, 4791 userId); 4792 final var bindingController = userData.mBindingController; 4793 final WindowManagerInternal.ImeTargetInfo info = 4794 mWindowManagerInternal.onToggleImeRequested( 4795 show, userData.mImeBindingState.mFocusedWindow, requestToken, 4796 bindingController.getCurTokenDisplayId()); 4797 mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( 4798 userData.mImeBindingState.mFocusedWindowClient, 4799 userData.mImeBindingState.mFocusedWindowEditorInfo, 4800 info.focusedWindowName, userData.mImeBindingState.mFocusedWindowSoftInputMode, 4801 reason, userData.mInFullscreenMode, info.requestWindowName, 4802 info.imeControlTargetName, info.imeLayerTargetName, info.imeSurfaceParentName, 4803 userId)); 4804 4805 if (statsToken != null) { 4806 mImeTrackerService.onImmsUpdate(statsToken, info.requestWindowName); 4807 } 4808 } 4809 4810 @BinderThread 4811 @GuardedBy("ImfLock.class") hideMySoftInputLocked(@onNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, @NonNull UserData userData)4812 private void hideMySoftInputLocked(@NonNull ImeTracker.Token statsToken, 4813 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, 4814 @NonNull UserData userData) { 4815 final int userId = userData.mUserId; 4816 if (Flags.refactorInsetsController()) { 4817 userData.mCurClient.mClient.setImeVisibility(false, statsToken); 4818 // TODO we will loose the flags here 4819 setImeVisibilityOnFocusedWindowClient(false, userData, statsToken); 4820 } else { 4821 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4822 hideCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(), 4823 statsToken, flags, null /* resultReceiver */, reason, userId); 4824 } 4825 } 4826 4827 @BinderThread 4828 @GuardedBy("ImfLock.class") showMySoftInputLocked(@onNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, @NonNull UserData userData)4829 private void showMySoftInputLocked(@NonNull ImeTracker.Token statsToken, 4830 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, 4831 @NonNull UserData userData) { 4832 final int userId = userData.mUserId; 4833 if (Flags.refactorInsetsController()) { 4834 userData.mCurClient.mClient.setImeVisibility(true, statsToken); 4835 setImeVisibilityOnFocusedWindowClient(true, userData, statsToken); 4836 } else { 4837 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4838 showCurrentInputLocked(visibilityStateComputer.getLastImeTargetWindow(), 4839 statsToken, flags, MotionEvent.TOOL_TYPE_UNKNOWN, 4840 null /* resultReceiver */, reason, userId); 4841 } 4842 } 4843 4844 @GuardedBy("ImfLock.class") 4845 @VisibleForTesting getVisibilityApplierLocked()4846 DefaultImeVisibilityApplier getVisibilityApplierLocked() { 4847 return mVisibilityApplier; 4848 } 4849 4850 @GuardedBy("ImfLock.class") onApplyImeVisibilityFromComputerLocked(IBinder windowToken, @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result, @UserIdInt int userId)4851 void onApplyImeVisibilityFromComputerLocked(IBinder windowToken, 4852 @NonNull ImeTracker.Token statsToken, @NonNull ImeVisibilityResult result, 4853 @UserIdInt int userId) { 4854 mVisibilityApplier.applyImeVisibility(windowToken, statsToken, result.getState(), 4855 result.getReason(), userId); 4856 } 4857 4858 @GuardedBy("ImfLock.class") setEnabledSessionLocked(SessionState session, @NonNull UserData userData)4859 void setEnabledSessionLocked(SessionState session, @NonNull UserData userData) { 4860 if (userData.mEnabledSession != session) { 4861 if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) { 4862 ProtoLog.v(IMMS_DEBUG, "Disabling: " + userData.mEnabledSession); 4863 userData.mEnabledSession.mMethod.setSessionEnabled( 4864 userData.mEnabledSession.mSession, false); 4865 } 4866 userData.mEnabledSession = session; 4867 if (userData.mEnabledSession != null && userData.mEnabledSession.mSession != null) { 4868 ProtoLog.v(IMMS_DEBUG, "Enabling: " + userData.mEnabledSession); 4869 userData.mEnabledSession.mMethod.setSessionEnabled( 4870 userData.mEnabledSession.mSession, true); 4871 } 4872 } 4873 } 4874 4875 @GuardedBy("ImfLock.class") setEnabledSessionForAccessibilityLocked( @onNull SparseArray<AccessibilitySessionState> accessibilitySessions, @NonNull UserData userData)4876 void setEnabledSessionForAccessibilityLocked( 4877 @NonNull SparseArray<AccessibilitySessionState> accessibilitySessions, 4878 @NonNull UserData userData) { 4879 // mEnabledAccessibilitySessions could the same object as accessibilitySessions. 4880 SparseArray<IAccessibilityInputMethodSession> disabledSessions = new SparseArray<>(); 4881 for (int i = 0; i < userData.mEnabledAccessibilitySessions.size(); i++) { 4882 if (!accessibilitySessions.contains(userData.mEnabledAccessibilitySessions.keyAt(i))) { 4883 AccessibilitySessionState sessionState = 4884 userData.mEnabledAccessibilitySessions.valueAt(i); 4885 if (sessionState != null) { 4886 disabledSessions.append(userData.mEnabledAccessibilitySessions.keyAt(i), 4887 sessionState.mSession); 4888 } 4889 } 4890 } 4891 if (disabledSessions.size() > 0) { 4892 AccessibilityManagerInternal.get().setImeSessionEnabled(disabledSessions, 4893 false); 4894 } 4895 SparseArray<IAccessibilityInputMethodSession> enabledSessions = new SparseArray<>(); 4896 for (int i = 0; i < accessibilitySessions.size(); i++) { 4897 if (!userData.mEnabledAccessibilitySessions.contains(accessibilitySessions.keyAt(i))) { 4898 AccessibilitySessionState sessionState = accessibilitySessions.valueAt(i); 4899 if (sessionState != null) { 4900 enabledSessions.append(accessibilitySessions.keyAt(i), sessionState.mSession); 4901 } 4902 } 4903 } 4904 if (enabledSessions.size() > 0) { 4905 AccessibilityManagerInternal.get().setImeSessionEnabled(enabledSessions, 4906 true); 4907 } 4908 userData.mEnabledAccessibilitySessions = accessibilitySessions; 4909 } 4910 4911 @GuardedBy("ImfLock.class") showInputMethodPickerLocked(int auxiliarySubtypeMode, int displayId, @UserIdInt int userId)4912 private void showInputMethodPickerLocked(int auxiliarySubtypeMode, int displayId, 4913 @UserIdInt int userId) { 4914 final boolean showAuxSubtypes; 4915 switch (auxiliarySubtypeMode) { 4916 // This is undocumented so far, but IMM#showInputMethodPicker() has been 4917 // implemented so that auxiliary subtypes will be excluded when the soft 4918 // keyboard is invisible. 4919 case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO -> { 4920 final var userData = getUserData(userId); 4921 final var visibilityStateComputer = userData.mVisibilityStateComputer; 4922 showAuxSubtypes = visibilityStateComputer.isInputShown(); 4923 } 4924 case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES -> 4925 showAuxSubtypes = true; 4926 case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES -> 4927 showAuxSubtypes = false; 4928 default -> { 4929 Slog.e(TAG, "Unknown subtype picker mode=" + auxiliarySubtypeMode); 4930 return; 4931 } 4932 } 4933 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 4934 final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked() 4935 && mWindowManagerInternal.isKeyguardSecure(userId); 4936 final String lastInputMethodId = settings.getSelectedInputMethod(); 4937 final int lastInputMethodSubtypeIndex = 4938 settings.getSelectedInputMethodSubtypeIndex(lastInputMethodId); 4939 4940 final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController 4941 .getSortedInputMethodAndSubtypeList( 4942 showAuxSubtypes, isScreenLocked, true /* forImeMenu */, 4943 mContext, settings); 4944 if (imList.isEmpty()) { 4945 Slog.w(TAG, "Show switching menu failed, imList is empty," 4946 + " showAuxSubtypes: " + showAuxSubtypes 4947 + " isScreenLocked: " + isScreenLocked 4948 + " userId: " + userId); 4949 return; 4950 } 4951 4952 if (Flags.imeSwitcherRevamp()) { 4953 ProtoLog.v(IMMS_DEBUG, "Show IME switcher menu," 4954 + " showAuxSubtypes=%s" 4955 + " displayId=%s" 4956 + " preferredInputMethodId=%s" 4957 + " preferredInputMethodSubtypeIndex=%s", 4958 showAuxSubtypes, displayId, lastInputMethodId, lastInputMethodSubtypeIndex); 4959 4960 int selectedSubtypeIndex = lastInputMethodSubtypeIndex; 4961 if (selectedSubtypeIndex == NOT_A_SUBTYPE_INDEX) { 4962 // TODO(b/351124299): Check if this fallback logic is still necessary. 4963 final var bindingController = getInputMethodBindingController(userId); 4964 final var curSubtype = bindingController.getCurrentInputMethodSubtype(); 4965 if (curSubtype != null) { 4966 final var curMethodId = bindingController.getSelectedMethodId(); 4967 final var curImi = settings.getMethodMap().get(curMethodId); 4968 selectedSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode( 4969 curImi, curSubtype.hashCode()); 4970 } 4971 } 4972 4973 mMenuControllerNew.show(imList, lastInputMethodId, selectedSubtypeIndex, isScreenLocked, 4974 displayId, userId); 4975 } else { 4976 mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId, 4977 lastInputMethodId, lastInputMethodSubtypeIndex, imList, userId); 4978 } 4979 } 4980 4981 @SuppressWarnings("unchecked") 4982 @UiThread 4983 @Override handleMessage(Message msg)4984 public boolean handleMessage(Message msg) { 4985 switch (msg.what) { 4986 case MSG_HIDE_INPUT_METHOD: { 4987 @SoftInputShowHideReason final int reason = msg.arg1; 4988 final int originatingDisplayId = msg.arg2; 4989 synchronized (ImfLock.class) { 4990 final int userId = resolveImeUserIdFromDisplayIdLocked(originatingDisplayId); 4991 final var userData = getUserData(userId); 4992 if (Flags.refactorInsetsController()) { 4993 setImeVisibilityOnFocusedWindowClient(false, userData, 4994 null /* TODO(b/353463205) check statsToken */); 4995 } else { 4996 4997 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 4998 0 /* flags */, reason, userId); 4999 } 5000 } 5001 return true; 5002 } 5003 case MSG_REMOVE_IME_SURFACE: { 5004 synchronized (ImfLock.class) { 5005 // TODO(b/305849394): Needs to figure out what to do where for background users. 5006 final int userId = mCurrentImeUserId; 5007 final var userData = getUserData(userId); 5008 try { 5009 if (userData.mEnabledSession != null 5010 && userData.mEnabledSession.mSession != null 5011 && !isShowRequestedForCurrentWindow(userId)) { 5012 userData.mEnabledSession.mSession.removeImeSurface(); 5013 } 5014 } catch (RemoteException e) { 5015 } 5016 } 5017 return true; 5018 } 5019 case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: { 5020 IBinder windowToken = (IBinder) msg.obj; 5021 synchronized (ImfLock.class) { 5022 final int userId = resolveImeUserIdFromWindowLocked(windowToken); 5023 final var userData = getUserData(userId); 5024 try { 5025 if (windowToken == userData.mImeBindingState.mFocusedWindow 5026 && userData.mEnabledSession != null 5027 && userData.mEnabledSession.mSession != null) { 5028 userData.mEnabledSession.mSession.removeImeSurface(); 5029 } 5030 } catch (RemoteException e) { 5031 } 5032 } 5033 return true; 5034 } 5035 5036 // --------------------------------------------------------- 5037 5038 case MSG_SET_INTERACTIVE: 5039 handleSetInteractive(msg.arg1 != 0); 5040 return true; 5041 5042 // -------------------------------------------------------------- 5043 case MSG_HARD_KEYBOARD_SWITCH_CHANGED: 5044 if (!Flags.imeSwitcherRevamp()) { 5045 mMenuController.handleHardKeyboardStatusChange(msg.arg1 == 1); 5046 } 5047 synchronized (ImfLock.class) { 5048 sendOnNavButtonFlagsChangedToAllImesLocked(); 5049 } 5050 return true; 5051 case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: { 5052 final int userId = msg.arg1; 5053 final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj; 5054 mInputMethodListListeners.forEach( 5055 listener -> listener.onInputMethodListUpdated(imes, userId)); 5056 return true; 5057 } 5058 5059 // --------------------------------------------------------------- 5060 case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: { 5061 if (mAudioManagerInternal == null) { 5062 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); 5063 } 5064 if (mAudioManagerInternal != null) { 5065 mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */); 5066 } 5067 return true; 5068 } 5069 5070 case MSG_RESET_HANDWRITING: { 5071 synchronized (ImfLock.class) { 5072 final var bindingController = 5073 getInputMethodBindingController(mCurrentImeUserId); 5074 if (bindingController.supportsStylusHandwriting() 5075 && bindingController.getCurMethod() != null 5076 && hasSupportedStylusLocked()) { 5077 Slog.d(TAG, "Initializing Handwriting Spy"); 5078 mHwController.initializeHandwritingSpy( 5079 bindingController.getCurTokenDisplayId()); 5080 } else { 5081 mHwController.reset(); 5082 } 5083 } 5084 return true; 5085 } 5086 case MSG_PREPARE_HANDWRITING_DELEGATION: 5087 synchronized (ImfLock.class) { 5088 int userId = msg.arg1; 5089 String delegate = (String) ((Pair) msg.obj).first; 5090 String delegator = (String) ((Pair) msg.obj).second; 5091 mHwController.prepareStylusHandwritingDelegation( 5092 userId, delegate, delegator, /* connectionless= */ false); 5093 } 5094 return true; 5095 case MSG_START_HANDWRITING: 5096 final var handwritingRequest = (HandwritingRequest) msg.obj; 5097 synchronized (ImfLock.class) { 5098 final var userData = handwritingRequest.userData; 5099 final var bindingController = userData.mBindingController; 5100 IInputMethodInvoker curMethod = bindingController.getCurMethod(); 5101 if (curMethod == null || userData.mImeBindingState.mFocusedWindow == null) { 5102 return true; 5103 } 5104 final HandwritingModeController.HandwritingSession session = 5105 mHwController.startHandwritingSession( 5106 handwritingRequest.requestId, 5107 handwritingRequest.pid, 5108 bindingController.getCurMethodUid(), 5109 userData.mImeBindingState.mFocusedWindow); 5110 if (session == null) { 5111 Slog.e(TAG, 5112 "Failed to start handwriting session for requestId: " + msg.arg1); 5113 return true; 5114 } 5115 5116 if (!curMethod.startStylusHandwriting(session.getRequestId(), 5117 session.getHandwritingChannel(), session.getRecordedEvents())) { 5118 // When failed to issue IPCs, re-initialize handwriting state. 5119 Slog.w(TAG, "Resetting handwriting mode."); 5120 scheduleResetStylusHandwriting(); 5121 } 5122 } 5123 return true; 5124 case MSG_FINISH_HANDWRITING: 5125 synchronized (ImfLock.class) { 5126 IInputMethodInvoker curMethod = getCurMethodLocked(); 5127 if (curMethod != null && mHwController.getCurrentRequestId().isPresent()) { 5128 curMethod.finishStylusHandwriting(); 5129 } 5130 } 5131 return true; 5132 case MSG_REMOVE_HANDWRITING_WINDOW: 5133 synchronized (ImfLock.class) { 5134 IInputMethodInvoker curMethod = getCurMethodLocked(); 5135 if (curMethod != null) { 5136 curMethod.removeStylusHandwritingWindow(); 5137 } 5138 } 5139 return true; 5140 } 5141 return false; 5142 } 5143 HandwritingRequest(int requestId, int pid, @NonNull UserData userData)5144 private record HandwritingRequest(int requestId, int pid, @NonNull UserData userData) { } 5145 5146 @BinderThread 5147 @GuardedBy("ImfLock.class") onStylusHandwritingReadyLocked(int requestId, int pid, @NonNull UserData userData)5148 private void onStylusHandwritingReadyLocked(int requestId, int pid, 5149 @NonNull UserData userData) { 5150 mHandler.obtainMessage(MSG_START_HANDWRITING, 5151 new HandwritingRequest(requestId, pid, userData)).sendToTarget(); 5152 } 5153 handleSetInteractive(final boolean interactive)5154 private void handleSetInteractive(final boolean interactive) { 5155 synchronized (ImfLock.class) { 5156 // TODO(b/305849394): Support multiple IMEs. 5157 final int userId = mCurrentImeUserId; 5158 final var userData = getUserData(userId); 5159 final var bindingController = userData.mBindingController; 5160 mIsInteractive = interactive; 5161 updateSystemUiLocked( 5162 interactive ? bindingController.getImeWindowVis() : 0, 5163 bindingController.getBackDisposition(), userId); 5164 // Inform the current client of the change in active status 5165 if (userData.mCurClient == null) { 5166 return; 5167 } 5168 if (mImePlatformCompatUtils.shouldUseSetInteractiveProtocol( 5169 bindingController.getCurMethodUid())) { 5170 // Handle IME visibility when interactive changed before finishing the input to 5171 // ensure we preserve the last state as possible. 5172 final var visibilityStateComputer = userData.mVisibilityStateComputer; 5173 final ImeVisibilityResult imeVisRes = visibilityStateComputer.onInteractiveChanged( 5174 userData.mImeBindingState.mFocusedWindow, interactive); 5175 if (imeVisRes != null) { 5176 // Pass in a null statsToken as the IME snapshot is not tracked by ImeTracker. 5177 mVisibilityApplier.applyImeVisibility(userData.mImeBindingState.mFocusedWindow, 5178 null /* statsToken */, imeVisRes.getState(), imeVisRes.getReason(), 5179 userId); 5180 } 5181 // Eligible IME processes use new "setInteractive" protocol. 5182 userData.mCurClient.mClient.setInteractive(mIsInteractive, 5183 userData.mInFullscreenMode); 5184 } else { 5185 // Legacy IME processes continue using legacy "setActive" protocol. 5186 userData.mCurClient.mClient.setActive(mIsInteractive, userData.mInFullscreenMode); 5187 } 5188 } 5189 } 5190 5191 @GuardedBy("ImfLock.class") chooseNewDefaultIMELocked(@serIdInt int userId)5192 private boolean chooseNewDefaultIMELocked(@UserIdInt int userId) { 5193 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5194 final InputMethodInfo imi = InputMethodInfoUtils.getMostApplicableDefaultIME( 5195 settings.getEnabledInputMethodList()); 5196 if (imi != null) { 5197 ProtoLog.v(IMMS_DEBUG, "New default IME was selected: %s", imi.getId()); 5198 resetSelectedInputMethodAndSubtypeLocked(imi.getId(), userId); 5199 return true; 5200 } 5201 5202 return false; 5203 } 5204 5205 @NonNull queryRawInputMethodServiceMap(Context context, @UserIdInt int userId)5206 static RawInputMethodMap queryRawInputMethodServiceMap(Context context, @UserIdInt int userId) { 5207 final Context userAwareContext = context.getUserId() == userId 5208 ? context 5209 : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); 5210 5211 final int flags = PackageManager.GET_META_DATA 5212 | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS 5213 | PackageManager.MATCH_DIRECT_BOOT_AWARE 5214 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE; 5215 5216 // Beware that package visibility filtering will be enforced based on the effective calling 5217 // identity (Binder.getCallingUid()), but our use case always expect Binder.getCallingUid() 5218 // to return Process.SYSTEM_UID here. The actual filtering is implemented separately with 5219 // canCallerAccessInputMethod(). 5220 // TODO(b/343108534): Use PackageManagerInternal#queryIntentServices() to pass SYSTEM_UID. 5221 final List<ResolveInfo> services = userAwareContext.getPackageManager().queryIntentServices( 5222 new Intent(InputMethod.SERVICE_INTERFACE), 5223 PackageManager.ResolveInfoFlags.of(flags)); 5224 5225 // Note: This is a temporary solution for Bug 261723412. 5226 // TODO(b/339761278): Remove this workaround after switching to InputMethodInfoSafeList. 5227 final List<String> enabledInputMethodList = 5228 InputMethodUtils.getEnabledInputMethodIdsForFiltering(context, userId); 5229 5230 return filterInputMethodServices(enabledInputMethodList, userAwareContext, services); 5231 } 5232 5233 @NonNull filterInputMethodServices( List<String> enabledInputMethodList, Context userAwareContext, List<ResolveInfo> services)5234 static RawInputMethodMap filterInputMethodServices( 5235 List<String> enabledInputMethodList, Context userAwareContext, 5236 List<ResolveInfo> services) { 5237 final ArrayMap<String, Integer> imiPackageCount = new ArrayMap<>(); 5238 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(services.size()); 5239 5240 for (int i = 0; i < services.size(); ++i) { 5241 ResolveInfo ri = services.get(i); 5242 ServiceInfo si = ri.serviceInfo; 5243 final String imeId = InputMethodInfo.computeId(ri); 5244 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) { 5245 Slog.w(TAG, "Skipping input method " + imeId 5246 + ": it does not require the permission " 5247 + android.Manifest.permission.BIND_INPUT_METHOD); 5248 continue; 5249 } 5250 5251 ProtoLog.v(IMMS_DEBUG, "Checking %s", imeId); 5252 5253 try { 5254 final InputMethodInfo imi = new InputMethodInfo(userAwareContext, ri, 5255 Collections.emptyList()); 5256 if (imi.isVrOnly()) { 5257 continue; // Skip VR-only IME, which isn't supported for now. 5258 } 5259 final String packageName = si.packageName; 5260 // only include IMEs which are from the system, enabled, or below the threshold 5261 if (si.applicationInfo.isSystemApp() || enabledInputMethodList.contains(imi.getId()) 5262 || imiPackageCount.getOrDefault(packageName, 0) 5263 < InputMethodInfo.MAX_IMES_PER_PACKAGE) { 5264 imiPackageCount.put(packageName, 5265 1 + imiPackageCount.getOrDefault(packageName, 0)); 5266 5267 methodMap.put(imi.getId(), imi); 5268 ProtoLog.v(IMMS_DEBUG, "Found an input method %s", imi); 5269 } else { 5270 ProtoLog.v(IMMS_DEBUG, "Found an input method, but ignored due threshold: %s", 5271 imi); 5272 } 5273 } catch (Exception e) { 5274 Slog.wtf(TAG, "Unable to load input method " + imeId, e); 5275 } 5276 } 5277 return RawInputMethodMap.of(methodMap); 5278 } 5279 5280 @GuardedBy("ImfLock.class") postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme, @UserIdInt int userId)5281 void postInputMethodSettingUpdatedLocked(boolean resetDefaultEnabledIme, 5282 @UserIdInt int userId) { 5283 ProtoLog.v(IMMS_DEBUG, "--- re-buildInputMethodList reset = %s" 5284 + " \n ------ caller=%s", resetDefaultEnabledIme, Debug.getCallers(10)); 5285 if (!mSystemReady) { 5286 Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready"); 5287 return; 5288 } 5289 5290 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5291 5292 boolean reenableMinimumNonAuxSystemImes = false; 5293 // TODO: The following code should find better place to live. 5294 if (!resetDefaultEnabledIme) { 5295 boolean enabledImeFound = false; 5296 boolean enabledNonAuxImeFound = false; 5297 final List<InputMethodInfo> enabledImes = settings.getEnabledInputMethodList(); 5298 final int numImes = enabledImes.size(); 5299 for (int i = 0; i < numImes; ++i) { 5300 final InputMethodInfo imi = enabledImes.get(i); 5301 if (settings.getMethodMap().containsKey(imi.getId())) { 5302 enabledImeFound = true; 5303 if (!imi.isAuxiliaryIme()) { 5304 enabledNonAuxImeFound = true; 5305 break; 5306 } 5307 } 5308 } 5309 if (!enabledImeFound) { 5310 ProtoLog.v(IMMS_DEBUG, 5311 "All the enabled IMEs are gone. Reset default enabled IMEs."); 5312 resetDefaultEnabledIme = true; 5313 resetSelectedInputMethodAndSubtypeLocked("", userId); 5314 } else if (!enabledNonAuxImeFound) { 5315 ProtoLog.v(IMMS_DEBUG, "All the enabled non-Aux IMEs are gone. Do partial reset."); 5316 reenableMinimumNonAuxSystemImes = true; 5317 } 5318 } 5319 5320 if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { 5321 final ArrayList<InputMethodInfo> defaultEnabledIme = 5322 InputMethodInfoUtils.getDefaultEnabledImes(mContext, settings.getMethodList(), 5323 reenableMinimumNonAuxSystemImes); 5324 final int numImes = defaultEnabledIme.size(); 5325 for (int i = 0; i < numImes; ++i) { 5326 final InputMethodInfo imi = defaultEnabledIme.get(i); 5327 ProtoLog.v(IMMS_DEBUG, "--- enable ime = %s", imi); 5328 setInputMethodEnabledLocked(imi.getId(), true, userId); 5329 } 5330 } 5331 5332 final String defaultImiId = settings.getSelectedInputMethod(); 5333 if (!TextUtils.isEmpty(defaultImiId)) { 5334 if (!settings.getMethodMap().containsKey(defaultImiId)) { 5335 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); 5336 if (chooseNewDefaultIMELocked(userId)) { 5337 updateInputMethodsFromSettingsLocked(true, userId); 5338 } 5339 } else { 5340 // Double check that the default IME is certainly enabled. 5341 setInputMethodEnabledLocked(defaultImiId, true, userId); 5342 } 5343 } 5344 5345 updateDefaultVoiceImeIfNeededLocked(userId); 5346 5347 final var userData = getUserData(userId); 5348 userData.mSwitchingController.resetCircularListLocked(mContext, settings); 5349 userData.mHardwareKeyboardShortcutController.update(settings); 5350 5351 sendOnNavButtonFlagsChangedLocked(userData); 5352 5353 // Notify InputMethodListListeners of the new installed InputMethods. 5354 final List<InputMethodInfo> inputMethodList = settings.getMethodList(); 5355 mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, 5356 userId, 0 /* unused */, inputMethodList).sendToTarget(); 5357 } 5358 5359 @GuardedBy("ImfLock.class") sendOnNavButtonFlagsChangedToAllImesLocked()5360 void sendOnNavButtonFlagsChangedToAllImesLocked() { 5361 for (int userId : mUserManagerInternal.getUserIds()) { 5362 sendOnNavButtonFlagsChangedLocked(getUserData(userId)); 5363 } 5364 } 5365 5366 @GuardedBy("ImfLock.class") sendOnNavButtonFlagsChangedLocked(@onNull UserData userData)5367 void sendOnNavButtonFlagsChangedLocked(@NonNull UserData userData) { 5368 final var bindingController = userData.mBindingController; 5369 final IInputMethodInvoker curMethod = bindingController.getCurMethod(); 5370 if (curMethod == null) { 5371 // No need to send the data if the IME is not yet bound. 5372 return; 5373 } 5374 curMethod.onNavButtonFlagsChanged(getInputMethodNavButtonFlagsLocked(userData)); 5375 } 5376 5377 @WorkerThread onUpdateResourceOverlay(@serIdInt int userId)5378 private void onUpdateResourceOverlay(@UserIdInt int userId) { 5379 final int profileParentId = mUserManagerInternal.getProfileParentId(userId); 5380 final boolean value = 5381 InputMethodDrawsNavBarResourceMonitor.evaluate(mContext, profileParentId); 5382 final var profileUserIds = mUserManagerInternal.getProfileIds(profileParentId, false); 5383 final ArrayList<UserData> updatedUsers = new ArrayList<>(); 5384 for (int profileUserId : profileUserIds) { 5385 final var userData = getUserData(profileUserId); 5386 userData.mImeDrawsNavBar.set(value); 5387 updatedUsers.add(userData); 5388 } 5389 synchronized (ImfLock.class) { 5390 updatedUsers.forEach(this::sendOnNavButtonFlagsChangedLocked); 5391 } 5392 } 5393 5394 @GuardedBy("ImfLock.class") updateDefaultVoiceImeIfNeededLocked(@serIdInt int userId)5395 private void updateDefaultVoiceImeIfNeededLocked(@UserIdInt int userId) { 5396 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5397 final String systemSpeechRecognizer = 5398 mContext.getString(com.android.internal.R.string.config_systemSpeechRecognizer); 5399 final String currentDefaultVoiceImeId = settings.getDefaultVoiceInputMethod(); 5400 final InputMethodInfo newSystemVoiceIme = InputMethodInfoUtils.chooseSystemVoiceIme( 5401 settings.getMethodMap(), systemSpeechRecognizer, currentDefaultVoiceImeId); 5402 if (newSystemVoiceIme == null) { 5403 ProtoLog.v(IMMS_DEBUG, "Found no valid default Voice IME. If the user is still locked," 5404 + " this may be expected."); 5405 // Clear DEFAULT_VOICE_INPUT_METHOD when necessary. Note that InputMethodSettings 5406 // does not update the actual Secure Settings until the user is unlocked. 5407 if (!TextUtils.isEmpty(currentDefaultVoiceImeId)) { 5408 settings.putDefaultVoiceInputMethod(""); 5409 // We don't support disabling the voice ime when a package is removed from the 5410 // config. 5411 } 5412 return; 5413 } 5414 if (TextUtils.equals(currentDefaultVoiceImeId, newSystemVoiceIme.getId())) { 5415 return; 5416 } 5417 ProtoLog.v(IMMS_DEBUG, "Enabling the default Voice IME: %s userId: %s", newSystemVoiceIme, 5418 userId); 5419 setInputMethodEnabledLocked(newSystemVoiceIme.getId(), true, userId); 5420 settings.putDefaultVoiceInputMethod(newSystemVoiceIme.getId()); 5421 } 5422 5423 // ---------------------------------------------------------------------- 5424 5425 /** 5426 * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}. 5427 * 5428 * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently 5429 * not recognized by the system 5430 * @param enabled {@code true} if {@code id} needs to be enabled 5431 * @param userId the user ID to be updated 5432 * @return {@code true} if the IME was previously enabled 5433 */ 5434 @GuardedBy("ImfLock.class") setInputMethodEnabledLocked(String id, boolean enabled, @UserIdInt int userId)5435 private boolean setInputMethodEnabledLocked(String id, boolean enabled, @UserIdInt int userId) { 5436 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5437 if (enabled) { 5438 final String enabledImeIdsStr = settings.getEnabledInputMethodsStr(); 5439 final String newEnabledImeIdsStr = InputMethodUtils.concatEnabledImeIds( 5440 enabledImeIdsStr, id); 5441 if (TextUtils.equals(enabledImeIdsStr, newEnabledImeIdsStr)) { 5442 // We are enabling this input method, but it is already enabled. 5443 // Nothing to do. The previous state was enabled. 5444 return true; 5445 } 5446 settings.putEnabledInputMethodsStr(newEnabledImeIdsStr); 5447 // Previous state was disabled. 5448 return false; 5449 } else { 5450 final List<Pair<String, ArrayList<String>>> enabledInputMethodsList = settings 5451 .getEnabledInputMethodsAndSubtypeList(); 5452 StringBuilder builder = new StringBuilder(); 5453 if (settings.buildAndPutEnabledInputMethodsStrRemovingId( 5454 builder, enabledInputMethodsList, id)) { 5455 final var bindingController = getInputMethodBindingController(userId); 5456 if (bindingController.getDeviceIdToShowIme() == DEVICE_ID_DEFAULT) { 5457 // Disabled input method is currently selected, switch to another one. 5458 final String selId = settings.getSelectedInputMethod(); 5459 if (id.equals(selId) && !chooseNewDefaultIMELocked(userId)) { 5460 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 5461 resetSelectedInputMethodAndSubtypeLocked("", userId); 5462 } 5463 } else if (id.equals(settings.getSelectedDefaultDeviceInputMethod())) { 5464 // Disabled default device IME while using a virtual device one, choose a 5465 // new default one but only update the settings. 5466 InputMethodInfo newDefaultIme = 5467 InputMethodInfoUtils.getMostApplicableDefaultIME( 5468 settings.getEnabledInputMethodList()); 5469 settings.putSelectedDefaultDeviceInputMethod( 5470 newDefaultIme == null ? null : newDefaultIme.getId()); 5471 } 5472 // Previous state was enabled. 5473 return true; 5474 } else { 5475 // We are disabling the input method but it is already disabled. 5476 // Nothing to do. The previous state was disabled. 5477 return false; 5478 } 5479 } 5480 } 5481 5482 @GuardedBy("ImfLock.class") setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex, boolean setSubtypeOnly, @UserIdInt int userId)5483 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeIndex, 5484 boolean setSubtypeOnly, @UserIdInt int userId) { 5485 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5486 final var bindingController = getInputMethodBindingController(userId); 5487 settings.saveCurrentInputMethodAndSubtypeToHistory(bindingController.getSelectedMethodId(), 5488 bindingController.getCurrentSubtype()); 5489 5490 // Set Subtype here 5491 final int newSubtypeHashcode; 5492 final InputMethodSubtype newSubtype; 5493 if (imi == null || subtypeIndex < 0) { 5494 newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE; 5495 newSubtype = null; 5496 } else { 5497 if (subtypeIndex < imi.getSubtypeCount()) { 5498 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeIndex); 5499 newSubtypeHashcode = subtype.hashCode(); 5500 newSubtype = subtype; 5501 } else { 5502 // TODO(b/347093491): Probably this should be determined from the new subtype. 5503 newSubtypeHashcode = INVALID_SUBTYPE_HASHCODE; 5504 // If the subtype is not specified, choose the most applicable one 5505 // TODO(b/347083680): The method below has questionable behaviors. 5506 newSubtype = bindingController.getCurrentInputMethodSubtype(); 5507 } 5508 } 5509 settings.putSelectedSubtype(newSubtypeHashcode); 5510 bindingController.setCurrentSubtype(newSubtype); 5511 notifyInputMethodSubtypeChangedLocked(settings.getUserId(), imi, newSubtype); 5512 5513 if (!setSubtypeOnly) { 5514 // Set InputMethod here 5515 settings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 5516 } 5517 5518 if (Flags.imeSwitcherRevamp()) { 5519 getUserData(userId).mSwitchingController.onInputMethodSubtypeChanged(); 5520 } 5521 } 5522 5523 @GuardedBy("ImfLock.class") resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme, @UserIdInt int userId)5524 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme, 5525 @UserIdInt int userId) { 5526 final var bindingController = getInputMethodBindingController(userId); 5527 bindingController.setDisplayIdToShowIme(INVALID_DISPLAY); 5528 bindingController.setDeviceIdToShowIme(DEVICE_ID_DEFAULT); 5529 5530 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5531 settings.putSelectedDefaultDeviceInputMethod(null); 5532 5533 InputMethodInfo imi = settings.getMethodMap().get(newDefaultIme); 5534 int lastSubtypeIndex = NOT_A_SUBTYPE_INDEX; 5535 // newDefaultIme is empty when there is no candidate for the selected IME. 5536 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 5537 String subtypeHashCode = settings.getLastSubtypeForInputMethod(newDefaultIme); 5538 if (subtypeHashCode != null) { 5539 try { 5540 lastSubtypeIndex = SubtypeUtils.getSubtypeIndexFromHashCode(imi, 5541 Integer.parseInt(subtypeHashCode)); 5542 } catch (NumberFormatException e) { 5543 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 5544 } 5545 } 5546 } 5547 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeIndex, false, userId); 5548 } 5549 5550 /** 5551 * Gets the current subtype of this input method. 5552 * 5553 * @param userId User ID to be queried about 5554 * @return the current {@link InputMethodSubtype} for the specified user 5555 */ 5556 @Nullable 5557 @Override getCurrentInputMethodSubtype(@serIdInt int userId)5558 public InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) { 5559 if (UserHandle.getCallingUserId() != userId) { 5560 mContext.enforceCallingOrSelfPermission( 5561 Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 5562 } 5563 synchronized (ImfLock.class) { 5564 final var bindingController = getInputMethodBindingController(userId); 5565 // TODO(b/347083680): The method below has questionable behaviors. 5566 return bindingController.getCurrentInputMethodSubtype(); 5567 } 5568 } 5569 5570 @GuardedBy("ImfLock.class") switchToInputMethodLocked(@onNull String imeId, int subtypeIndex, @UserIdInt int userId)5571 private boolean switchToInputMethodLocked(@NonNull String imeId, int subtypeIndex, 5572 @UserIdInt int userId) { 5573 final var settings = InputMethodSettingsRepository.get(userId); 5574 final var enabledImes = settings.getEnabledInputMethodList(); 5575 if (!CollectionUtils.any(enabledImes, imi -> imi.getId().equals(imeId))) { 5576 return false; // IME is not found or not enabled. 5577 } 5578 setInputMethodLocked(imeId, subtypeIndex, userId); 5579 return true; 5580 } 5581 5582 /** 5583 * Filter the access to the input method by rules of the package visibility. Return {@code true} 5584 * if the given input method is the currently selected one or visible to the caller. 5585 * 5586 * @param targetPkgName the package name of input method to check 5587 * @param callingUid the caller that is going to access the input method 5588 * @param userId the user ID where the input method resides 5589 * @param settings the input method settings under the given user ID 5590 * @return {@code true} if caller is able to access the input method 5591 */ canCallerAccessInputMethod(@onNull String targetPkgName, int callingUid, @UserIdInt int userId, @NonNull InputMethodSettings settings)5592 private boolean canCallerAccessInputMethod(@NonNull String targetPkgName, int callingUid, 5593 @UserIdInt int userId, @NonNull InputMethodSettings settings) { 5594 final String methodId = settings.getSelectedInputMethod(); 5595 final ComponentName selectedInputMethod = methodId != null 5596 ? InputMethodUtils.convertIdToComponentName(methodId) : null; 5597 if (selectedInputMethod != null 5598 && selectedInputMethod.getPackageName().equals(targetPkgName)) { 5599 return true; 5600 } 5601 final boolean canAccess = !mPackageManagerInternal.filterAppAccess( 5602 targetPkgName, callingUid, userId); 5603 if (DEBUG && !canAccess) { 5604 Slog.d(TAG, "Input method " + targetPkgName 5605 + " is not visible to the caller " + callingUid); 5606 } 5607 return canAccess; 5608 } 5609 5610 @GuardedBy("ImfLock.class") switchKeyboardLayoutLocked(int direction, @NonNull UserData userData)5611 private void switchKeyboardLayoutLocked(int direction, @NonNull UserData userData) { 5612 final int userId = userData.mUserId; 5613 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5614 5615 final var bindingController = userData.mBindingController; 5616 final InputMethodInfo currentImi = settings.getMethodMap().get( 5617 bindingController.getSelectedMethodId()); 5618 if (currentImi == null) { 5619 return; 5620 } 5621 final var currentSubtype = bindingController.getCurrentSubtype(); 5622 final InputMethodSubtypeHandle nextSubtypeHandle; 5623 if (Flags.imeSwitcherRevamp()) { 5624 final var nextItem = userData.mSwitchingController 5625 .getNextInputMethodForHardware( 5626 false /* onlyCurrentIme */, currentImi, currentSubtype, MODE_AUTO, 5627 direction > 0 /* forward */); 5628 if (nextItem == null) { 5629 Slog.i(TAG, "Hardware keyboard switching shortcut," 5630 + " next input method and subtype not found"); 5631 return; 5632 } 5633 5634 final var nextSubtype = nextItem.mSubtypeIndex > NOT_A_SUBTYPE_INDEX 5635 ? nextItem.mImi.getSubtypeAt(nextItem.mSubtypeIndex) : null; 5636 nextSubtypeHandle = InputMethodSubtypeHandle.of(nextItem.mImi, nextSubtype); 5637 } else { 5638 final InputMethodSubtypeHandle currentSubtypeHandle = 5639 InputMethodSubtypeHandle.of(currentImi, currentSubtype); 5640 nextSubtypeHandle = userData.mHardwareKeyboardShortcutController.onSubtypeSwitch( 5641 currentSubtypeHandle, direction > 0); 5642 } 5643 if (nextSubtypeHandle == null) { 5644 return; 5645 } 5646 final InputMethodInfo nextImi = settings.getMethodMap().get(nextSubtypeHandle.getImeId()); 5647 if (nextImi == null) { 5648 return; 5649 } 5650 5651 final int subtypeCount = nextImi.getSubtypeCount(); 5652 if (subtypeCount == 0) { 5653 if (nextSubtypeHandle.equals(InputMethodSubtypeHandle.of(nextImi, null))) { 5654 setInputMethodLocked(nextImi.getId(), NOT_A_SUBTYPE_INDEX, userId); 5655 } 5656 return; 5657 } 5658 5659 for (int i = 0; i < subtypeCount; ++i) { 5660 if (nextSubtypeHandle.equals( 5661 InputMethodSubtypeHandle.of(nextImi, nextImi.getSubtypeAt(i)))) { 5662 setInputMethodLocked(nextImi.getId(), i, userId); 5663 return; 5664 } 5665 } 5666 } 5667 publishLocalService()5668 private void publishLocalService() { 5669 LocalServices.addService(InputMethodManagerInternal.class, mInputMethodManagerInternal); 5670 } 5671 5672 private final class LocalServiceImpl extends InputMethodManagerInternal { 5673 5674 @ImfLockFree 5675 @Override setInteractive(boolean interactive)5676 public void setInteractive(boolean interactive) { 5677 // Do everything in handler so as not to block the caller. 5678 mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0).sendToTarget(); 5679 } 5680 5681 @ImfLockFree 5682 @Override hideInputMethod(@oftInputShowHideReason int reason, int originatingDisplayId)5683 public void hideInputMethod(@SoftInputShowHideReason int reason, 5684 int originatingDisplayId) { 5685 mHandler.removeMessages(MSG_HIDE_INPUT_METHOD); 5686 mHandler.obtainMessage(MSG_HIDE_INPUT_METHOD, reason, originatingDisplayId) 5687 .sendToTarget(); 5688 } 5689 5690 @ImfLockFree 5691 @NonNull 5692 @Override getInputMethodListAsUser(@serIdInt int userId)5693 public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { 5694 return getInputMethodListInternal(userId, DirectBootAwareness.AUTO, Process.SYSTEM_UID); 5695 } 5696 5697 @ImfLockFree 5698 @NonNull 5699 @Override getEnabledInputMethodListAsUser(@serIdInt int userId)5700 public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { 5701 return getEnabledInputMethodListInternal(userId, Process.SYSTEM_UID); 5702 } 5703 5704 @ImfLockFree 5705 @NonNull 5706 @Override getEnabledInputMethodSubtypeListAsUser( String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId)5707 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListAsUser( 5708 String imiId, boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) { 5709 return getEnabledInputMethodSubtypeListInternal(imiId, allowsImplicitlyEnabledSubtypes, 5710 userId, Process.SYSTEM_UID); 5711 } 5712 5713 @Override onCreateInlineSuggestionsRequest(@serIdInt int userId, InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb)5714 public void onCreateInlineSuggestionsRequest(@UserIdInt int userId, 5715 InlineSuggestionsRequestInfo requestInfo, InlineSuggestionsRequestCallback cb) { 5716 // Get the device global touch exploration state before lock to avoid deadlock. 5717 final boolean touchExplorationEnabled = AccessibilityManagerInternal.get() 5718 .isTouchExplorationEnabled(userId); 5719 5720 synchronized (ImfLock.class) { 5721 getInputMethodBindingController(userId).onCreateInlineSuggestionsRequest( 5722 requestInfo, cb, touchExplorationEnabled); 5723 } 5724 } 5725 5726 @Override switchToInputMethod(@onNull String imeId, int subtypeIndex, @UserIdInt int userId)5727 public boolean switchToInputMethod(@NonNull String imeId, int subtypeIndex, 5728 @UserIdInt int userId) { 5729 synchronized (ImfLock.class) { 5730 return switchToInputMethodLocked(imeId, subtypeIndex, userId); 5731 } 5732 } 5733 5734 @Override setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId)5735 public boolean setInputMethodEnabled(String imeId, boolean enabled, @UserIdInt int userId) { 5736 synchronized (ImfLock.class) { 5737 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 5738 if (!settings.getMethodMap().containsKey(imeId)) { 5739 return false; // IME is not found. 5740 } 5741 setInputMethodEnabledLocked(imeId, enabled, userId); 5742 return true; 5743 } 5744 } 5745 5746 @Override setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId)5747 public void setVirtualDeviceInputMethodForAllUsers(int deviceId, @Nullable String imeId) { 5748 Preconditions.checkArgument(deviceId != DEVICE_ID_DEFAULT, 5749 TextUtils.formatSimple("DeviceId %d is not a virtual device id.", deviceId)); 5750 synchronized (ImfLock.class) { 5751 if (imeId == null) { 5752 mVirtualDeviceMethodMap.remove(deviceId); 5753 } else if (mVirtualDeviceMethodMap.contains(deviceId)) { 5754 throw new IllegalArgumentException("Virtual device " + deviceId 5755 + " already has a custom input method component"); 5756 } else { 5757 mVirtualDeviceMethodMap.put(deviceId, imeId); 5758 } 5759 } 5760 } 5761 5762 @ImfLockFree 5763 @Override registerInputMethodListListener(InputMethodListListener listener)5764 public void registerInputMethodListListener(InputMethodListListener listener) { 5765 mInputMethodListListeners.addIfAbsent(listener); 5766 } 5767 5768 @Override transferTouchFocusToImeWindow(@onNull IBinder sourceInputToken, int displayId, @UserIdInt int userId)5769 public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, 5770 int displayId, @UserIdInt int userId) { 5771 //TODO(b/150843766): Check if Input Token is valid. 5772 final IBinder curHostInputToken; 5773 synchronized (ImfLock.class) { 5774 final var bindingController = getInputMethodBindingController(userId); 5775 if (displayId != bindingController.getCurTokenDisplayId()) { 5776 return false; 5777 } 5778 curHostInputToken = bindingController.getCurHostInputToken(); 5779 if (curHostInputToken == null) { 5780 return false; 5781 } 5782 } 5783 return mInputManagerInternal.transferTouchGesture( 5784 sourceInputToken, curHostInputToken, /* transferEntireGesture */ false); 5785 } 5786 5787 @Override reportImeControl(@ullable IBinder windowToken)5788 public void reportImeControl(@Nullable IBinder windowToken) { 5789 synchronized (ImfLock.class) { 5790 final int userId = resolveImeUserIdFromWindowLocked(windowToken); 5791 final var userData = getUserData(userId); 5792 if (userData.mImeBindingState.mFocusedWindow != windowToken) { 5793 // A perceptible value was set for the focused window, but it is no longer in 5794 // control, so we reset the perceptible for the window passed as argument. 5795 // TODO(b/314149476): Investigate whether this logic is still relevant, if not 5796 // then consider removing using concurrent_input_methods feature flag. 5797 mFocusedWindowPerceptible.put(windowToken, true); 5798 } 5799 } 5800 } 5801 5802 @Override onImeParentChanged(int displayId)5803 public void onImeParentChanged(int displayId) { 5804 synchronized (ImfLock.class) { 5805 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); 5806 final var userData = getUserData(userId); 5807 // Hide the IME method menu only when the IME surface parent is changed by the 5808 // input target changed, in case seeing the dialog dismiss flickering during 5809 // the next focused window starting the input connection. 5810 final var visibilityStateComputer = userData.mVisibilityStateComputer; 5811 if (visibilityStateComputer.getLastImeTargetWindow() 5812 != userData.mImeBindingState.mFocusedWindow) { 5813 if (Flags.imeSwitcherRevamp()) { 5814 final var bindingController = getInputMethodBindingController(userId); 5815 mMenuControllerNew.hide(bindingController.getCurTokenDisplayId(), userId); 5816 } else { 5817 mMenuController.hideInputMethodMenuLocked(userId); 5818 } 5819 } 5820 } 5821 } 5822 5823 @ImfLockFree 5824 @Override removeImeSurface(int displayId)5825 public void removeImeSurface(int displayId) { 5826 mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE).sendToTarget(); 5827 } 5828 5829 @Override setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId)5830 public void setHasVisibleImeLayeringOverlay(boolean hasVisibleOverlay, int displayId) { 5831 synchronized (ImfLock.class) { 5832 final var userId = resolveImeUserIdFromDisplayIdLocked(displayId); 5833 getUserData(userId).mVisibilityStateComputer.setHasVisibleImeLayeringOverlay( 5834 hasVisibleOverlay); 5835 } 5836 } 5837 5838 @Override onImeInputTargetVisibilityChanged(@onNull IBinder imeInputTarget, boolean visibleAndNotRemoved, int displayId)5839 public void onImeInputTargetVisibilityChanged(@NonNull IBinder imeInputTarget, 5840 boolean visibleAndNotRemoved, int displayId) { 5841 synchronized (ImfLock.class) { 5842 final var userId = resolveImeUserIdFromDisplayIdLocked(displayId); 5843 getUserData(userId).mVisibilityStateComputer.onImeInputTargetVisibilityChanged( 5844 imeInputTarget, visibleAndNotRemoved); 5845 } 5846 } 5847 5848 @ImfLockFree 5849 @Override updateImeWindowStatus(boolean disableImeIcon, int displayId)5850 public void updateImeWindowStatus(boolean disableImeIcon, int displayId) { 5851 mHandler.post(() -> { 5852 synchronized (ImfLock.class) { 5853 updateImeWindowStatusLocked(disableImeIcon, displayId); 5854 } 5855 }); 5856 } 5857 5858 @Override updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId)5859 public void updateShouldShowImeSwitcher(int displayId, @UserIdInt int userId) { 5860 synchronized (ImfLock.class) { 5861 updateSystemUiLocked(userId); 5862 final var userData = getUserData(userId); 5863 sendOnNavButtonFlagsChangedLocked(userData); 5864 } 5865 } 5866 5867 @Override onSessionForAccessibilityCreated(int accessibilityConnectionId, IAccessibilityInputMethodSession session, @UserIdInt int userId)5868 public void onSessionForAccessibilityCreated(int accessibilityConnectionId, 5869 IAccessibilityInputMethodSession session, @UserIdInt int userId) { 5870 synchronized (ImfLock.class) { 5871 final var userData = getUserData(userId); 5872 final var bindingController = userData.mBindingController; 5873 // TODO(b/305829876): Implement user ID verification 5874 if (userData.mCurClient != null) { 5875 clearClientSessionForAccessibilityLocked(userData.mCurClient, 5876 accessibilityConnectionId); 5877 userData.mCurClient.mAccessibilitySessions.put( 5878 accessibilityConnectionId, 5879 new AccessibilitySessionState(userData.mCurClient, 5880 accessibilityConnectionId, 5881 session)); 5882 5883 attachNewAccessibilityLocked(StartInputReason.SESSION_CREATED_BY_ACCESSIBILITY, 5884 true, userId); 5885 5886 final SessionState sessionState = userData.mCurClient.mCurSession; 5887 final IInputMethodSession imeSession = sessionState == null 5888 ? null : sessionState.mSession; 5889 final SparseArray<IAccessibilityInputMethodSession> 5890 accessibilityInputMethodSessions = 5891 createAccessibilityInputMethodSessions( 5892 userData.mCurClient.mAccessibilitySessions); 5893 final InputBindResult res = new InputBindResult( 5894 InputBindResult.ResultCode.SUCCESS_WITH_ACCESSIBILITY_SESSION, 5895 imeSession, accessibilityInputMethodSessions, /* channel= */ null, 5896 bindingController.getCurId(), 5897 bindingController.getSequenceNumber(), 5898 /* isInputMethodSuppressingSpellChecker= */ false); 5899 userData.mCurClient.mClient.onBindAccessibilityService(res, 5900 accessibilityConnectionId); 5901 } 5902 } 5903 } 5904 5905 @Override unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, @UserIdInt int userId)5906 public void unbindAccessibilityFromCurrentClient(int accessibilityConnectionId, 5907 @UserIdInt int userId) { 5908 synchronized (ImfLock.class) { 5909 final var userData = getUserData(userId); 5910 final var bindingController = userData.mBindingController; 5911 // TODO(b/305829876): Implement user ID verification 5912 if (userData.mCurClient != null) { 5913 ProtoLog.v(IMMS_DEBUG, "unbindAccessibilityFromCurrentClientLocked: client=%s", 5914 userData.mCurClient.mClient.asBinder()); 5915 // A11yManagerService unbinds the disabled accessibility service. We don't need 5916 // to do it here. 5917 userData.mCurClient.mClient.onUnbindAccessibilityService( 5918 bindingController.getSequenceNumber(), 5919 accessibilityConnectionId); 5920 } 5921 // We only have sessions when we bound to an input method. Remove this session 5922 // from all clients. 5923 if (bindingController.getCurMethod() != null) { 5924 // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. 5925 @SuppressWarnings("GuardedBy") Consumer<ClientState> clearClientSession = c -> 5926 clearClientSessionForAccessibilityLocked(c, accessibilityConnectionId); 5927 mClientController.forAllClients(clearClientSession); 5928 5929 AccessibilitySessionState session = userData.mEnabledAccessibilitySessions.get( 5930 accessibilityConnectionId); 5931 if (session != null) { 5932 finishSessionForAccessibilityLocked(session); 5933 userData.mEnabledAccessibilitySessions.remove(accessibilityConnectionId); 5934 } 5935 } 5936 } 5937 } 5938 5939 @ImfLockFree 5940 @Override maybeFinishStylusHandwriting()5941 public void maybeFinishStylusHandwriting() { 5942 mHandler.removeMessages(MSG_FINISH_HANDWRITING); 5943 mHandler.obtainMessage(MSG_FINISH_HANDWRITING).sendToTarget(); 5944 } 5945 5946 @Override onSwitchKeyboardLayoutShortcut(int direction, int displayId, IBinder targetWindowToken)5947 public void onSwitchKeyboardLayoutShortcut(int direction, int displayId, 5948 IBinder targetWindowToken) { 5949 synchronized (ImfLock.class) { 5950 final int userId = resolveImeUserIdFromDisplayIdLocked(displayId); 5951 switchKeyboardLayoutLocked(direction, getUserData(userId)); 5952 } 5953 } 5954 } 5955 5956 @BinderThread 5957 @GuardedBy("ImfLock.class") 5958 @Nullable createInputContentUriTokenLocked(@onNull Uri contentUri, @NonNull String packageName, @NonNull UserData userData)5959 private IInputContentUriToken createInputContentUriTokenLocked(@NonNull Uri contentUri, 5960 @NonNull String packageName, @NonNull UserData userData) { 5961 Objects.requireNonNull(packageName, "packageName must not be null"); 5962 Objects.requireNonNull(contentUri, "contentUri must not be null"); 5963 final String contentUriScheme = contentUri.getScheme(); 5964 if (!"content".equals(contentUriScheme)) { 5965 throw new InvalidParameterException("contentUri must have content scheme"); 5966 } 5967 5968 final int uid = Binder.getCallingUid(); 5969 final var bindingController = userData.mBindingController; 5970 if (bindingController.getSelectedMethodId() == null) { 5971 return null; 5972 } 5973 // We cannot simply distinguish a bad IME that reports an arbitrary package name from 5974 // an unfortunate IME whose internal state is already obsolete due to the asynchronous 5975 // nature of our system. Let's compare it with our internal record. 5976 final var curPackageName = userData.mCurEditorInfo != null 5977 ? userData.mCurEditorInfo.packageName : null; 5978 if (!TextUtils.equals(curPackageName, packageName)) { 5979 Slog.e(TAG, "Ignoring createInputContentUriTokenLocked mCurEditorInfo.packageName=" 5980 + curPackageName + " packageName=" + packageName); 5981 return null; 5982 } 5983 // This user ID can never be spoofed. 5984 final int appUserId = UserHandle.getUserId(userData.mCurClient.mUid); 5985 // This user ID may be invalid if "contentUri" embedded an invalid user ID. 5986 final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri, 5987 userData.mUserId); 5988 final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri); 5989 // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid") 5990 // actually has the right to grant a read permission for "contentUriWithoutUserId" that 5991 // is claimed to belong to "contentUriOwnerUserId". For example, specifying random 5992 // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown 5993 // from InputContentUriTokenHandler.take() and can never be allowed beyond what is 5994 // actually allowed to "uid", which is guaranteed to be the IME's one. 5995 return new InputContentUriTokenHandler(contentUriWithoutUserId, uid, 5996 packageName, contentUriOwnerUserId, appUserId); 5997 } 5998 5999 @BinderThread 6000 @GuardedBy("ImfLock.class") reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData)6001 private void reportFullscreenModeLocked(boolean fullscreen, @NonNull UserData userData) { 6002 if (userData.mCurClient != null) { 6003 userData.mInFullscreenMode = fullscreen; 6004 userData.mCurClient.mClient.reportFullscreenMode(fullscreen); 6005 } 6006 } 6007 6008 private final PriorityDump.PriorityDumper mPriorityDumper = new PriorityDump.PriorityDumper() { 6009 /** 6010 * {@inheritDoc} 6011 */ 6012 @BinderThread 6013 @Override 6014 public void dumpCritical(FileDescriptor fd, PrintWriter pw, String[] args, 6015 boolean asProto) { 6016 if (asProto) { 6017 dumpAsProtoNoCheck(fd); 6018 } else { 6019 dumpAsStringNoCheck(fd, pw, args, true /* isCritical */); 6020 } 6021 } 6022 6023 /** 6024 * {@inheritDoc} 6025 */ 6026 @BinderThread 6027 @Override 6028 public void dumpHigh(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { 6029 dumpNormal(fd, pw, args, asProto); 6030 } 6031 6032 /** 6033 * {@inheritDoc} 6034 */ 6035 @BinderThread 6036 @Override 6037 public void dumpNormal(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { 6038 if (asProto) { 6039 dumpAsProtoNoCheck(fd); 6040 } else { 6041 dumpAsStringNoCheck(fd, pw, args, false /* isCritical */); 6042 } 6043 } 6044 6045 /** 6046 * {@inheritDoc} 6047 */ 6048 @BinderThread 6049 @Override 6050 public void dump(FileDescriptor fd, PrintWriter pw, String[] args, boolean asProto) { 6051 dumpNormal(fd, pw, args, asProto); 6052 } 6053 6054 @BinderThread 6055 private void dumpAsProtoNoCheck(FileDescriptor fd) { 6056 final ProtoOutputStream proto = new ProtoOutputStream(fd); 6057 // Dump in the format of an ImeTracing trace with a single entry. 6058 final long magicNumber = 6059 ((long) InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_H << 32) 6060 | InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER_L; 6061 final long timeOffsetNs = TimeUnit.MILLISECONDS.toNanos(System.currentTimeMillis()) 6062 - SystemClock.elapsedRealtimeNanos(); 6063 proto.write(InputMethodManagerServiceTraceFileProto.MAGIC_NUMBER, 6064 magicNumber); 6065 proto.write(InputMethodManagerServiceTraceFileProto.REAL_TO_ELAPSED_TIME_OFFSET_NANOS, 6066 timeOffsetNs); 6067 final long token = proto.start(InputMethodManagerServiceTraceFileProto.ENTRY); 6068 proto.write(InputMethodManagerServiceTraceProto.ELAPSED_REALTIME_NANOS, 6069 SystemClock.elapsedRealtimeNanos()); 6070 proto.write(InputMethodManagerServiceTraceProto.WHERE, 6071 "InputMethodManagerService.mPriorityDumper#dumpAsProtoNoCheck"); 6072 dumpDebug(proto, InputMethodManagerServiceTraceProto.INPUT_METHOD_MANAGER_SERVICE); 6073 proto.end(token); 6074 proto.flush(); 6075 } 6076 }; 6077 6078 @BinderThread 6079 @Override dump(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args)6080 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, @Nullable String[] args) { 6081 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 6082 6083 PriorityDump.dump(mPriorityDumper, fd, pw, args); 6084 } 6085 6086 @BinderThread dumpAsStringNoCheck(@onNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical)6087 private void dumpAsStringNoCheck(@NonNull FileDescriptor fd, @NonNull PrintWriter pw, 6088 @NonNull String[] args, boolean isCritical) { 6089 final int argUserId = parseUserIdFromDumpArgs(args); 6090 final Printer p = new PrintWriterPrinter(pw); 6091 p.println("Input Method Manager Service state:"); 6092 p.println(" mSystemReady=" + mSystemReady); 6093 p.println(" mInteractive=" + mIsInteractive); 6094 p.println(" mConcurrentMultiUserModeEnabled=" + mConcurrentMultiUserModeEnabled); 6095 final int currentImeUserId; 6096 synchronized (ImfLock.class) { 6097 currentImeUserId = mCurrentImeUserId; 6098 p.println(" mCurrentImeUserId=" + currentImeUserId); 6099 p.println(" mStylusIds=" + (mStylusIds != null 6100 ? Arrays.toString(mStylusIds.toArray()) : "")); 6101 } 6102 // TODO(b/305849394): Make mMenuController multi-user aware. 6103 if (Flags.imeSwitcherRevamp()) { 6104 p.println(" mMenuControllerNew:"); 6105 mMenuControllerNew.dump(p, " "); 6106 } else { 6107 p.println(" mMenuController:"); 6108 mMenuController.dump(p, " "); 6109 } 6110 dumpClientController(p); 6111 dumpUserRepository(p); 6112 6113 // TODO(b/365868861): Make StartInputHistory and ImeTracker multi-user aware. 6114 synchronized (ImfLock.class) { 6115 p.println(" mStartInputHistory:"); 6116 mStartInputHistory.dump(pw, " "); 6117 6118 p.println(" mSoftInputShowHideHistory:"); 6119 mSoftInputShowHideHistory.dump(pw, " "); 6120 } 6121 6122 p.println(" mImeTrackerService#History:"); 6123 mImeTrackerService.dump(pw, " "); 6124 6125 if (mConcurrentMultiUserModeEnabled && argUserId == UserHandle.USER_NULL) { 6126 mUserDataRepository.forAllUserData( 6127 u -> dumpAsStringNoCheckForUser(u, fd, pw, args, isCritical)); 6128 } else { 6129 final int userId = argUserId != UserHandle.USER_NULL ? argUserId : currentImeUserId; 6130 final var userData = getUserData(userId); 6131 dumpAsStringNoCheckForUser(userData, fd, pw, args, isCritical); 6132 } 6133 } 6134 6135 @UserIdInt parseUserIdFromDumpArgs(@onNull String[] args)6136 private static int parseUserIdFromDumpArgs(@NonNull String[] args) { 6137 final int userIdx = Arrays.binarySearch(args, "--user"); 6138 if (userIdx == -1 || userIdx == args.length - 1) { 6139 return UserHandle.USER_NULL; 6140 } 6141 return Integer.parseInt(args[userIdx + 1]); 6142 } 6143 6144 // TODO(b/356239178): Update dump format output to better group per-user info. 6145 @BinderThread dumpAsStringNoCheckForUser(@onNull UserData userData, @NonNull FileDescriptor fd, @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical)6146 private void dumpAsStringNoCheckForUser(@NonNull UserData userData, @NonNull FileDescriptor fd, 6147 @NonNull PrintWriter pw, @NonNull String[] args, boolean isCritical) { 6148 final Printer p = new PrintWriterPrinter(pw); 6149 ClientState client; 6150 IInputMethodInvoker method; 6151 p.println(" UserId=" + userData.mUserId); 6152 synchronized (ImfLock.class) { 6153 final var bindingController = userData.mBindingController; 6154 client = userData.mCurClient; 6155 method = bindingController.getCurMethod(); 6156 p.println(" mBindingController:"); 6157 bindingController.dump(pw, " "); 6158 p.println(" mCurClient=" + client); 6159 p.println(" mFocusedWindowPerceptible=" + mFocusedWindowPerceptible); 6160 p.println(" mImeBindingState:"); 6161 userData.mImeBindingState.dump(p, " "); 6162 p.println(" mBoundToMethod=" + userData.mBoundToMethod); 6163 p.println(" mEnabledSession=" + userData.mEnabledSession); 6164 p.println(" mVisibilityStateComputer:"); 6165 userData.mVisibilityStateComputer.dump(pw, " "); 6166 p.println(" mInFullscreenMode=" + userData.mInFullscreenMode); 6167 6168 final var settings = InputMethodSettingsRepository.get(userData.mUserId); 6169 final List<InputMethodInfo> methodList = settings.getMethodList(); 6170 final int numImes = methodList.size(); 6171 p.println(" Input Methods:"); 6172 for (int i = 0; i < numImes; i++) { 6173 final InputMethodInfo info = methodList.get(i); 6174 p.println(" InputMethod #" + i + ":"); 6175 info.dump(p, " "); 6176 } 6177 } 6178 6179 // Exit here for critical dump, as remaining sections require IPCs to other processes. 6180 if (isCritical) { 6181 return; 6182 } 6183 6184 p.println(""); 6185 if (client != null) { 6186 pw.flush(); 6187 try { 6188 TransferPipe.dumpAsync(client.mClient.asBinder(), fd, args); 6189 } catch (IOException | RemoteException e) { 6190 p.println("Failed to dump input method client: " + e); 6191 } 6192 } else { 6193 p.println("No input method client."); 6194 } 6195 synchronized (ImfLock.class) { 6196 final var focusedWindowClient = userData.mImeBindingState.mFocusedWindowClient; 6197 if (focusedWindowClient != null && client != focusedWindowClient) { 6198 p.println(""); 6199 p.println("Warning: Current input method client doesn't match the last focused" 6200 + " window."); 6201 p.println("Dumping input method client in the last focused window just in case."); 6202 p.println(""); 6203 pw.flush(); 6204 try { 6205 TransferPipe.dumpAsync(focusedWindowClient.mClient.asBinder(), fd, args); 6206 } catch (IOException | RemoteException e) { 6207 p.println("Failed to dump input method client in focused window: " + e); 6208 } 6209 } 6210 } 6211 6212 p.println(""); 6213 if (method != null) { 6214 pw.flush(); 6215 try { 6216 TransferPipe.dumpAsync(method.asBinder(), fd, args); 6217 } catch (IOException | RemoteException e) { 6218 p.println("Failed to dump input method service: " + e); 6219 } 6220 } else { 6221 p.println("No input method service."); 6222 } 6223 } 6224 dumpClientController(@onNull Printer p)6225 private void dumpClientController(@NonNull Printer p) { 6226 p.println(" mClientController:"); 6227 // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. 6228 @SuppressWarnings("GuardedBy") Consumer<ClientState> clientControllerDump = c -> { 6229 p.println(" " + c + ":"); 6230 p.println(" client=" + c.mClient); 6231 p.println(" fallbackInputConnection=" + c.mFallbackInputConnection); 6232 p.println(" sessionRequested=" + c.mSessionRequested); 6233 p.println(" sessionRequestedForAccessibility=" 6234 + c.mSessionRequestedForAccessibility); 6235 p.println(" curSession=" + c.mCurSession); 6236 p.println(" selfReportedDisplayId=" + c.mSelfReportedDisplayId); 6237 p.println(" uid=" + c.mUid); 6238 p.println(" pid=" + c.mPid); 6239 }; 6240 synchronized (ImfLock.class) { 6241 mClientController.forAllClients(clientControllerDump); 6242 } 6243 } 6244 dumpUserRepository(@onNull Printer p)6245 private void dumpUserRepository(@NonNull Printer p) { 6246 p.println(" mUserDataRepository:"); 6247 // TODO(b/324907325): Remove the suppress warnings once b/324907325 is fixed. 6248 @SuppressWarnings("GuardedBy") Consumer<UserData> userDataDump = u -> { 6249 p.println(" userId=" + u.mUserId); 6250 p.println(" unlocked=" + u.mIsUnlockingOrUnlocked.get()); 6251 p.println(" hasMainConnection=" + u.mBindingController.hasMainConnection()); 6252 p.println(" isVisibleBound=" + u.mBindingController.isVisibleBound()); 6253 p.println(" boundToMethod=" + u.mBoundToMethod); 6254 p.println(" curClient=" + u.mCurClient); 6255 if (u.mCurEditorInfo != null) { 6256 p.println(" curEditorInfo:"); 6257 u.mCurEditorInfo.dump(p, " ", false /* dumpExtras */); 6258 } else { 6259 p.println(" curEditorInfo: null"); 6260 } 6261 p.println(" imeBindingState:"); 6262 u.mImeBindingState.dump(p, " "); 6263 p.println(" enabledSession=" + u.mEnabledSession); 6264 p.println(" inFullscreenMode=" + u.mInFullscreenMode); 6265 p.println(" imeDrawsNavBar=" + u.mImeDrawsNavBar.get()); 6266 p.println(" switchingController:"); 6267 u.mSwitchingController.dump(p, " "); 6268 p.println(" mLastEnabledInputMethodsStr=" + u.mLastEnabledInputMethodsStr); 6269 }; 6270 synchronized (ImfLock.class) { 6271 mUserDataRepository.forAllUserData(userDataDump); 6272 } 6273 } 6274 6275 @BinderThread 6276 @Override onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver, @NonNull Binder self)6277 public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, 6278 @Nullable FileDescriptor err, 6279 @NonNull String[] args, @Nullable ShellCallback callback, 6280 @NonNull ResultReceiver resultReceiver, @NonNull Binder self) { 6281 final int callingUid = Binder.getCallingUid(); 6282 // Reject any incoming calls from non-shell users, including ones from the system user. 6283 if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) { 6284 // Note that Binder#onTransact() will automatically close "in", "out", and "err" when 6285 // returned from this method, hence there is no need to close those FDs. 6286 // "resultReceiver" is the only thing that needs to be taken care of here. 6287 if (resultReceiver != null) { 6288 resultReceiver.send(ShellCommandResult.FAILURE, null); 6289 } 6290 final String errorMsg = "InputMethodManagerService does not support shell commands from" 6291 + " non-shell users. callingUid=" + callingUid 6292 + " args=" + Arrays.toString(args); 6293 if (Process.isCoreUid(callingUid)) { 6294 // Let's not crash the calling process if the caller is one of core components. 6295 Slog.e(TAG, errorMsg); 6296 return; 6297 } 6298 throw new SecurityException(errorMsg); 6299 } 6300 new ShellCommandImpl(this).exec( 6301 self, in, out, err, args, callback, resultReceiver); 6302 } 6303 6304 private static final class ShellCommandImpl extends ShellCommand { 6305 @NonNull 6306 final InputMethodManagerService mService; 6307 ShellCommandImpl(InputMethodManagerService service)6308 ShellCommandImpl(InputMethodManagerService service) { 6309 mService = service; 6310 } 6311 6312 @BinderThread 6313 @ShellCommandResult 6314 @Override onCommand(@ullable String cmd)6315 public int onCommand(@Nullable String cmd) { 6316 final long identity = Binder.clearCallingIdentity(); 6317 try { 6318 return onCommandWithSystemIdentity(cmd); 6319 } finally { 6320 Binder.restoreCallingIdentity(identity); 6321 } 6322 } 6323 6324 @BinderThread 6325 @ShellCommandResult onCommandWithSystemIdentity(@ullable String cmd)6326 private int onCommandWithSystemIdentity(@Nullable String cmd) { 6327 switch (TextUtils.emptyIfNull(cmd)) { 6328 case "tracing": 6329 return mService.handleShellCommandTraceInputMethod(this); 6330 case "ime": { // For "adb shell ime <command>". 6331 final String imeCommand = TextUtils.emptyIfNull(getNextArg()); 6332 switch (imeCommand) { 6333 case "": 6334 case "-h": 6335 case "help": 6336 return onImeCommandHelp(); 6337 case "list": 6338 return mService.handleShellCommandListInputMethods(this); 6339 case "enable": 6340 return mService.handleShellCommandEnableDisableInputMethod(this, true); 6341 case "disable": 6342 return mService.handleShellCommandEnableDisableInputMethod(this, false); 6343 case "set": 6344 return mService.handleShellCommandSetInputMethod(this); 6345 case "reset": 6346 return mService.handleShellCommandResetInputMethod(this); 6347 case "tracing": // TODO(b/180765389): Unsupport "adb shell ime tracing" 6348 return mService.handleShellCommandTraceInputMethod(this); 6349 default: 6350 getOutPrintWriter().println("Unknown command: " + imeCommand); 6351 return ShellCommandResult.FAILURE; 6352 } 6353 } 6354 default: 6355 return handleDefaultCommands(cmd); 6356 } 6357 } 6358 6359 @BinderThread 6360 @Override onHelp()6361 public void onHelp() { 6362 try (PrintWriter pw = getOutPrintWriter()) { 6363 pw.println("InputMethodManagerService commands:"); 6364 pw.println(" help"); 6365 pw.println(" Prints this help text."); 6366 pw.println(" dump [options]"); 6367 pw.println(" Synonym of dumpsys."); 6368 pw.println(" ime <command> [options]"); 6369 pw.println(" Manipulate IMEs. Run \"ime help\" for details."); 6370 pw.println(" tracing <command>"); 6371 pw.println(" start: Start tracing."); 6372 pw.println(" stop : Stop tracing."); 6373 pw.println(" help : Show help."); 6374 } 6375 } 6376 6377 @BinderThread 6378 @ShellCommandResult onImeCommandHelp()6379 private int onImeCommandHelp() { 6380 try (IndentingPrintWriter pw = 6381 new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) { 6382 pw.println("ime <command>:"); 6383 pw.increaseIndent(); 6384 6385 pw.println("list [-a] [-s]"); 6386 pw.increaseIndent(); 6387 pw.println("prints all enabled input methods."); 6388 pw.increaseIndent(); 6389 pw.println("-a: see all input methods"); 6390 pw.println("-s: only a single summary line of each"); 6391 pw.decreaseIndent(); 6392 pw.decreaseIndent(); 6393 6394 pw.println("enable [--user <USER_ID>] <ID>"); 6395 pw.increaseIndent(); 6396 pw.println("allows the given input method ID to be used."); 6397 pw.increaseIndent(); 6398 pw.print("--user <USER_ID>: Specify which user to enable."); 6399 pw.println(" Assumes the current user if not specified."); 6400 pw.decreaseIndent(); 6401 pw.decreaseIndent(); 6402 6403 pw.println("disable [--user <USER_ID>] <ID>"); 6404 pw.increaseIndent(); 6405 pw.println("disallows the given input method ID to be used."); 6406 pw.increaseIndent(); 6407 pw.print("--user <USER_ID>: Specify which user to disable."); 6408 pw.println(" Assumes the current user if not specified."); 6409 pw.decreaseIndent(); 6410 pw.decreaseIndent(); 6411 6412 pw.println("set [--user <USER_ID>] <ID>"); 6413 pw.increaseIndent(); 6414 pw.println("switches to the given input method ID."); 6415 pw.increaseIndent(); 6416 pw.print("--user <USER_ID>: Specify which user to enable."); 6417 pw.println(" Assumes the current user if not specified."); 6418 pw.decreaseIndent(); 6419 pw.decreaseIndent(); 6420 6421 pw.println("reset [--user <USER_ID>]"); 6422 pw.increaseIndent(); 6423 pw.println("reset currently selected/enabled IMEs to the default ones as if " 6424 + "the device is initially booted with the current locale."); 6425 pw.increaseIndent(); 6426 pw.print("--user <USER_ID>: Specify which user to reset."); 6427 pw.println(" Assumes the current user if not specified."); 6428 pw.decreaseIndent(); 6429 6430 pw.decreaseIndent(); 6431 6432 pw.decreaseIndent(); 6433 } 6434 return ShellCommandResult.SUCCESS; 6435 } 6436 } 6437 6438 // ---------------------------------------------------------------------- 6439 // Shell command handlers: 6440 6441 /** 6442 * Handles {@code adb shell ime list}. 6443 * 6444 * @param shellCommand {@link ShellCommand} object that is handling this command 6445 * @return exit code of the command 6446 */ 6447 @BinderThread 6448 @ShellCommandResult handleShellCommandListInputMethods(@onNull ShellCommand shellCommand)6449 private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { 6450 boolean all = false; 6451 boolean brief = false; 6452 int userIdToBeResolved = UserHandle.USER_CURRENT; 6453 while (true) { 6454 final String nextOption = shellCommand.getNextOption(); 6455 if (nextOption == null) { 6456 break; 6457 } 6458 switch (nextOption) { 6459 case "-a": 6460 all = true; 6461 break; 6462 case "-s": 6463 brief = true; 6464 break; 6465 case "-u": 6466 case "--user": 6467 userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired()); 6468 break; 6469 } 6470 } 6471 final int[] userIds; 6472 synchronized (ImfLock.class) { 6473 userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, mCurrentImeUserId, 6474 shellCommand.getErrPrintWriter()); 6475 } 6476 try (PrintWriter pr = shellCommand.getOutPrintWriter()) { 6477 for (int userId : userIds) { 6478 final List<InputMethodInfo> methods = all 6479 ? getInputMethodListInternal( 6480 userId, DirectBootAwareness.AUTO, Process.SHELL_UID) 6481 : getEnabledInputMethodListInternal(userId, Process.SHELL_UID); 6482 if (userIds.length > 1) { 6483 pr.print("User #"); 6484 pr.print(userId); 6485 pr.println(":"); 6486 } 6487 for (InputMethodInfo info : methods) { 6488 if (brief) { 6489 pr.println(info.getId()); 6490 } else { 6491 pr.print(info.getId()); 6492 pr.println(":"); 6493 info.dump(pr::println, " "); 6494 } 6495 } 6496 } 6497 } 6498 return ShellCommandResult.SUCCESS; 6499 } 6500 6501 /** 6502 * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. 6503 * 6504 * @param shellCommand {@link ShellCommand} object that is handling this command 6505 * @param enabled {@code true} if the command was {@code adb shell ime enable} 6506 * @return exit code of the command 6507 */ 6508 @BinderThread 6509 @ShellCommandResult handleShellCommandEnableDisableInputMethod( @onNull ShellCommand shellCommand, boolean enabled)6510 private int handleShellCommandEnableDisableInputMethod( 6511 @NonNull ShellCommand shellCommand, boolean enabled) { 6512 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 6513 final String imeId = shellCommand.getNextArgRequired(); 6514 boolean hasFailed = false; 6515 try (PrintWriter out = shellCommand.getOutPrintWriter(); 6516 PrintWriter error = shellCommand.getErrPrintWriter()) { 6517 synchronized (ImfLock.class) { 6518 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 6519 mCurrentImeUserId, shellCommand.getErrPrintWriter()); 6520 for (int userId : userIds) { 6521 if (!userHasDebugPriv(userId, shellCommand)) { 6522 continue; 6523 } 6524 hasFailed |= !handleShellCommandEnableDisableInputMethodInternalLocked( 6525 userId, imeId, enabled, out, error); 6526 } 6527 } 6528 } 6529 return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS; 6530 } 6531 6532 /** 6533 * A special helper method for commands that only have {@code -u} and {@code --user} options. 6534 * 6535 * <p>You cannot use this helper method if the command has other options.</p> 6536 * 6537 * <p>CAVEAT: This method must be called only once before any other 6538 * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the 6539 * main arguments.</p> 6540 * 6541 * @param shellCommand {@link ShellCommand} from which options should be obtained 6542 * @return user ID to be resolved. {@link UserHandle#CURRENT} if not specified 6543 */ 6544 @BinderThread 6545 @UserIdInt handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand)6546 private static int handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand) { 6547 while (true) { 6548 final String nextOption = shellCommand.getNextOption(); 6549 if (nextOption == null) { 6550 break; 6551 } 6552 switch (nextOption) { 6553 case "-u": 6554 case "--user": 6555 return UserHandle.parseUserArg(shellCommand.getNextArgRequired()); 6556 } 6557 } 6558 return UserHandle.USER_CURRENT; 6559 } 6560 6561 /** 6562 * Handles core logic of {@code adb shell ime enable} and {@code adb shell ime disable}. 6563 * 6564 * @param userId user ID specified to the command (pseudo user IDs are not supported) 6565 * @param imeId IME ID specified to the command 6566 * @param enabled {@code true} for {@code adb shell ime enable} 6567 * @param out {@link PrintWriter} to output standard messages 6568 * @param error {@link PrintWriter} to output error messages 6569 * @return {@code false} if it fails to enable the IME 6570 */ 6571 @BinderThread 6572 @GuardedBy("ImfLock.class") handleShellCommandEnableDisableInputMethodInternalLocked( @serIdInt int userId, String imeId, boolean enabled, PrintWriter out, PrintWriter error)6573 private boolean handleShellCommandEnableDisableInputMethodInternalLocked( 6574 @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out, 6575 PrintWriter error) { 6576 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 6577 if (enabled && !settings.getMethodMap().containsKey(imeId)) { 6578 error.print("Unknown input method "); 6579 error.print(imeId); 6580 error.println(" cannot be enabled for user #" + userId); 6581 // Also print this failure into logcat for better debuggability. 6582 Slog.e(TAG, "\"ime enable " + imeId + "\" for user #" + userId 6583 + " failed due to its unrecognized IME ID."); 6584 return false; 6585 } 6586 6587 final boolean previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled, userId); 6588 out.print("Input method "); 6589 out.print(imeId); 6590 out.print(": "); 6591 out.print((enabled == previouslyEnabled) ? "already " : "now "); 6592 out.print(enabled ? "enabled" : "disabled"); 6593 out.print(" for user #"); 6594 out.println(userId); 6595 return true; 6596 } 6597 6598 /** 6599 * Handles {@code adb shell ime set}. 6600 * 6601 * @param shellCommand {@link ShellCommand} object that is handling this command 6602 * @return Exit code of the command. 6603 */ 6604 @BinderThread 6605 @ShellCommandResult handleShellCommandSetInputMethod(@onNull ShellCommand shellCommand)6606 private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) { 6607 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 6608 final String imeId = shellCommand.getNextArgRequired(); 6609 boolean hasFailed = false; 6610 try (PrintWriter out = shellCommand.getOutPrintWriter(); 6611 PrintWriter error = shellCommand.getErrPrintWriter()) { 6612 synchronized (ImfLock.class) { 6613 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 6614 mCurrentImeUserId, shellCommand.getErrPrintWriter()); 6615 for (int userId : userIds) { 6616 if (!userHasDebugPriv(userId, shellCommand)) { 6617 continue; 6618 } 6619 boolean failedToSelectUnknownIme = !switchToInputMethodLocked(imeId, 6620 NOT_A_SUBTYPE_INDEX, userId); 6621 if (failedToSelectUnknownIme) { 6622 error.print("Unknown input method "); 6623 error.print(imeId); 6624 error.print(" cannot be selected for user #"); 6625 error.println(userId); 6626 // Also print this failure into logcat for better debuggability. 6627 Slog.e(TAG, "\"ime set " + imeId + "\" for user #" + userId 6628 + " failed due to its unrecognized IME ID."); 6629 } else { 6630 out.print("Input method "); 6631 out.print(imeId); 6632 out.print(" selected for user #"); 6633 out.println(userId); 6634 6635 // Workaround for b/354782333. 6636 final InputMethodSettings settings = 6637 InputMethodSettingsRepository.get(userId); 6638 final var bindingController = getInputMethodBindingController(userId); 6639 final int deviceId = bindingController.getDeviceIdToShowIme(); 6640 final String settingsValue; 6641 if (deviceId == DEVICE_ID_DEFAULT) { 6642 settingsValue = settings.getSelectedInputMethod(); 6643 } else { 6644 settingsValue = settings.getSelectedDefaultDeviceInputMethod(); 6645 } 6646 if (!TextUtils.equals(settingsValue, imeId)) { 6647 Slog.w(TAG, "DEFAULT_INPUT_METHOD=" + settingsValue 6648 + " is not updated. Fixing it up to " + imeId 6649 + " See b/354782333."); 6650 if (deviceId == DEVICE_ID_DEFAULT) { 6651 settings.putSelectedInputMethod(imeId); 6652 } else { 6653 settings.putSelectedDefaultDeviceInputMethod(imeId); 6654 } 6655 } 6656 } 6657 hasFailed |= failedToSelectUnknownIme; 6658 } 6659 } 6660 } 6661 return hasFailed ? ShellCommandResult.FAILURE : ShellCommandResult.SUCCESS; 6662 } 6663 6664 /** 6665 * Handles {@code adb shell ime reset-ime}. 6666 * 6667 * @param shellCommand {@link ShellCommand} object that is handling this command 6668 * @return Exit code of the command. 6669 */ 6670 @BinderThread 6671 @ShellCommandResult handleShellCommandResetInputMethod(@onNull ShellCommand shellCommand)6672 private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) { 6673 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 6674 synchronized (ImfLock.class) { 6675 try (PrintWriter out = shellCommand.getOutPrintWriter()) { 6676 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 6677 mCurrentImeUserId, shellCommand.getErrPrintWriter()); 6678 for (int userId : userIds) { 6679 if (!userHasDebugPriv(userId, shellCommand)) { 6680 continue; 6681 } 6682 // Skip on headless user 6683 final var userInfo = mUserManagerInternal.getUserInfo(userId); 6684 if (userInfo != null && USER_TYPE_SYSTEM_HEADLESS.equals(userInfo.userType)) { 6685 continue; 6686 } 6687 final String nextIme; 6688 final List<InputMethodInfo> nextEnabledImes; 6689 final InputMethodSettings settings = InputMethodSettingsRepository.get(userId); 6690 final var userData = getUserData(userId); 6691 if (Flags.refactorInsetsController()) { 6692 setImeVisibilityOnFocusedWindowClient(false, userData, 6693 null /* TODO(b329229469) initialize statsToken here? */); 6694 } else { 6695 hideCurrentInputLocked(userData.mImeBindingState.mFocusedWindow, 6696 0 /* flags */, 6697 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND, userId); 6698 } 6699 final var bindingController = userData.mBindingController; 6700 bindingController.unbindCurrentMethod(); 6701 6702 // Enable default IMEs, disable others 6703 var toDisable = settings.getEnabledInputMethodList(); 6704 var defaultEnabled = InputMethodInfoUtils.getDefaultEnabledImes( 6705 mContext, settings.getMethodList()); 6706 toDisable.removeAll(defaultEnabled); 6707 for (InputMethodInfo info : toDisable) { 6708 setInputMethodEnabledLocked(info.getId(), false, userId); 6709 } 6710 for (InputMethodInfo info : defaultEnabled) { 6711 setInputMethodEnabledLocked(info.getId(), true, userId); 6712 } 6713 // Choose new default IME, reset to none if no IME available. 6714 if (!chooseNewDefaultIMELocked(userId)) { 6715 resetSelectedInputMethodAndSubtypeLocked(null, userId); 6716 } 6717 updateInputMethodsFromSettingsLocked(true /* enabledMayChange */, userId); 6718 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( 6719 getPackageManagerForUser(mContext, settings.getUserId()), 6720 settings.getEnabledInputMethodList()); 6721 nextIme = settings.getSelectedInputMethod(); 6722 nextEnabledImes = settings.getEnabledInputMethodList(); 6723 out.println("Reset current and enabled IMEs for user #" + userId); 6724 out.println(" Selected: " + nextIme); 6725 nextEnabledImes.forEach(ime -> out.println(" Enabled: " + ime.getId())); 6726 } 6727 } 6728 } 6729 return ShellCommandResult.SUCCESS; 6730 } 6731 6732 @GuardedBy("ImfLock.class") setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData, @NonNull ImeTracker.Token statsToken)6733 boolean setImeVisibilityOnFocusedWindowClient(boolean visible, UserData userData, 6734 @NonNull ImeTracker.Token statsToken) { 6735 if (Flags.refactorInsetsController()) { 6736 if (userData.mImeBindingState.mFocusedWindowClient != null) { 6737 userData.mImeBindingState.mFocusedWindowClient.mClient.setImeVisibility(visible, 6738 statsToken); 6739 return true; 6740 } 6741 ImeTracker.forLogging().onFailed(statsToken, 6742 ImeTracker.PHASE_SERVER_SET_VISIBILITY_ON_FOCUSED_WINDOW); 6743 } 6744 return false; 6745 } 6746 6747 /** 6748 * Handles {@code adb shell cmd input_method tracing start/stop/save-for-bugreport}. 6749 * 6750 * @param shellCommand {@link ShellCommand} object that is handling this command 6751 * @return Exit code of the command. 6752 */ 6753 @BinderThread 6754 @ShellCommandResult handleShellCommandTraceInputMethod(@onNull ShellCommand shellCommand)6755 private int handleShellCommandTraceInputMethod(@NonNull ShellCommand shellCommand) { 6756 final String cmd = shellCommand.getNextArgRequired(); 6757 try (PrintWriter pw = shellCommand.getOutPrintWriter()) { 6758 switch (cmd) { 6759 case "start": 6760 ImeTracing.getInstance().startTrace(pw); 6761 break; // proceed to the next step to update the IME client processes. 6762 case "stop": 6763 ImeTracing.getInstance().stopTrace(pw); 6764 break; // proceed to the next step to update the IME client processes. 6765 case "save-for-bugreport": 6766 ImeTracing.getInstance().saveForBugreport(pw); 6767 // no need to update the IME client processes. 6768 return ShellCommandResult.SUCCESS; 6769 default: 6770 pw.println("Unknown command: " + cmd); 6771 pw.println("Input method trace options:"); 6772 pw.println(" start: Start tracing"); 6773 pw.println(" stop: Stop tracing"); 6774 // no need to update the IME client processes. 6775 return ShellCommandResult.FAILURE; 6776 } 6777 } 6778 boolean isImeTraceEnabled = ImeTracing.getInstance().isEnabled(); 6779 synchronized (ImfLock.class) { 6780 // TODO(b/322816970): Replace this with lambda. 6781 mClientController.forAllClients(new Consumer<ClientState>() { 6782 6783 @GuardedBy("ImfLock.class") 6784 @Override 6785 public void accept(ClientState c) { 6786 c.mClient.setImeTraceEnabled(isImeTraceEnabled); 6787 } 6788 }); 6789 } 6790 return ShellCommandResult.SUCCESS; 6791 } 6792 6793 /** 6794 * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()} 6795 * and *not* pseudo ids like {@link UserHandle#USER_ALL etc} 6796 * @return {@code true} if userId has debugging privileges 6797 * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false} 6798 */ userHasDebugPriv(@serIdInt int userId, ShellCommand shellCommand)6799 private boolean userHasDebugPriv(@UserIdInt int userId, ShellCommand shellCommand) { 6800 if (mUserManagerInternal.hasUserRestriction( 6801 UserManager.DISALLOW_DEBUGGING_FEATURES, userId)) { 6802 shellCommand.getErrPrintWriter().println("User #" + userId 6803 + " is restricted with DISALLOW_DEBUGGING_FEATURES."); 6804 return false; 6805 } 6806 return true; 6807 } 6808 6809 /** @hide */ 6810 @Override getImeTrackerService()6811 public IImeTracker getImeTrackerService() { 6812 return mImeTrackerService; 6813 } 6814 6815 /** 6816 * Creates an IME request tracking token for the current focused client. 6817 * 6818 * @param show whether this is a show or a hide request 6819 * @param reason the reason why the IME request was created 6820 */ 6821 @NonNull 6822 @GuardedBy("ImfLock.class") createStatsTokenForFocusedClient(boolean show, @SoftInputShowHideReason int reason, @UserIdInt int userId)6823 private ImeTracker.Token createStatsTokenForFocusedClient(boolean show, 6824 @SoftInputShowHideReason int reason, @UserIdInt int userId) { 6825 final var userData = getUserData(userId); 6826 final int uid = userData.mImeBindingState.mFocusedWindowClient != null 6827 ? userData.mImeBindingState.mFocusedWindowClient.mUid 6828 : -1; 6829 final var packageName = userData.mImeBindingState.mFocusedWindowEditorInfo != null 6830 ? userData.mImeBindingState.mFocusedWindowEditorInfo.packageName 6831 : "uid(" + uid + ")"; 6832 6833 return ImeTracker.forLogging().onStart(packageName, uid, 6834 show ? ImeTracker.TYPE_SHOW : ImeTracker.TYPE_HIDE, ImeTracker.ORIGIN_SERVER, 6835 reason, false /* fromUser */); 6836 } 6837 6838 private static final class InputMethodPrivilegedOperationsImpl 6839 extends IInputMethodPrivilegedOperations.Stub { 6840 @NonNull 6841 private final InputMethodManagerService mImms; 6842 @NonNull 6843 private final IBinder mToken; 6844 @NonNull 6845 private final UserData mUserData; 6846 InputMethodPrivilegedOperationsImpl(@onNull InputMethodManagerService imms, @NonNull IBinder token, @NonNull UserData userData)6847 InputMethodPrivilegedOperationsImpl(@NonNull InputMethodManagerService imms, 6848 @NonNull IBinder token, @NonNull UserData userData) { 6849 mImms = imms; 6850 mToken = token; 6851 mUserData = userData; 6852 } 6853 6854 @BinderThread 6855 @Override setImeWindowStatusAsync(@meWindowVisibility int vis, @BackDispositionMode int backDisposition)6856 public void setImeWindowStatusAsync(@ImeWindowVisibility int vis, 6857 @BackDispositionMode int backDisposition) { 6858 synchronized (ImfLock.class) { 6859 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6860 return; 6861 } 6862 mImms.setImeWindowStatusLocked(vis, backDisposition, mUserData); 6863 } 6864 } 6865 6866 @BinderThread 6867 @Override reportStartInputAsync(IBinder startInputToken)6868 public void reportStartInputAsync(IBinder startInputToken) { 6869 synchronized (ImfLock.class) { 6870 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6871 return; 6872 } 6873 mImms.reportStartInputLocked(startInputToken, mUserData); 6874 } 6875 } 6876 6877 @BinderThread 6878 @Override setHandwritingSurfaceNotTouchable(boolean notTouchable)6879 public void setHandwritingSurfaceNotTouchable(boolean notTouchable) { 6880 synchronized (ImfLock.class) { 6881 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6882 return; 6883 } 6884 mImms.mHwController.setNotTouchable(notTouchable); 6885 } 6886 } 6887 6888 @BinderThread 6889 @Override setHandwritingTouchableRegion(Region region)6890 public void setHandwritingTouchableRegion(Region region) { 6891 synchronized (ImfLock.class) { 6892 mImms.mHwController.setHandwritingTouchableRegion(region); 6893 } 6894 } 6895 6896 @BinderThread 6897 @Override createInputContentUriToken(Uri contentUri, String packageName, AndroidFuture future )6898 public void createInputContentUriToken(Uri contentUri, String packageName, 6899 AndroidFuture future /* T=IBinder */) { 6900 @SuppressWarnings("unchecked") final AndroidFuture<IBinder> typedFuture = future; 6901 try { 6902 synchronized (ImfLock.class) { 6903 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6904 typedFuture.complete(null); 6905 return; 6906 } 6907 typedFuture.complete(mImms.createInputContentUriTokenLocked( 6908 contentUri, packageName, mUserData).asBinder()); 6909 } 6910 } catch (Throwable e) { 6911 typedFuture.completeExceptionally(e); 6912 } 6913 } 6914 6915 @BinderThread 6916 @Override reportFullscreenModeAsync(boolean fullscreen)6917 public void reportFullscreenModeAsync(boolean fullscreen) { 6918 synchronized (ImfLock.class) { 6919 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6920 return; 6921 } 6922 mImms.reportFullscreenModeLocked(fullscreen, mUserData); 6923 } 6924 } 6925 6926 @BinderThread 6927 @Override setInputMethod(String id, AndroidFuture future )6928 public void setInputMethod(String id, AndroidFuture future /* T=Void */) { 6929 @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; 6930 try { 6931 synchronized (ImfLock.class) { 6932 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6933 typedFuture.complete(null); 6934 return; 6935 } 6936 mImms.setInputMethodAndSubtypeLocked(id, null /* subtype */, mUserData); 6937 typedFuture.complete(null); 6938 } 6939 } catch (Throwable e) { 6940 typedFuture.completeExceptionally(e); 6941 } 6942 } 6943 6944 @BinderThread 6945 @Override setInputMethodAndSubtype(String id, InputMethodSubtype subtype, AndroidFuture future )6946 public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype, 6947 AndroidFuture future /* T=Void */) { 6948 @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; 6949 try { 6950 synchronized (ImfLock.class) { 6951 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6952 typedFuture.complete(null); 6953 return; 6954 } 6955 mImms.setInputMethodAndSubtypeLocked(id, subtype, mUserData); 6956 typedFuture.complete(null); 6957 } 6958 } catch (Throwable e) { 6959 typedFuture.completeExceptionally(e); 6960 } 6961 } 6962 6963 @BinderThread 6964 @Override hideMySoftInput(@onNull ImeTracker.Token statsToken, @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future )6965 public void hideMySoftInput(@NonNull ImeTracker.Token statsToken, 6966 @InputMethodManager.HideFlags int flags, @SoftInputShowHideReason int reason, 6967 AndroidFuture future /* T=Void */) { 6968 @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; 6969 try { 6970 synchronized (ImfLock.class) { 6971 if (!calledWithValidTokenLocked(mToken, mUserData)) { 6972 ImeTracker.forLogging().onFailed(statsToken, 6973 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 6974 typedFuture.complete(null); 6975 return; 6976 } 6977 ImeTracker.forLogging().onProgress(statsToken, 6978 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 6979 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.hideMySoftInput"); 6980 final long ident = Binder.clearCallingIdentity(); 6981 try { 6982 mImms.hideMySoftInputLocked(statsToken, flags, reason, mUserData); 6983 typedFuture.complete(null); 6984 } finally { 6985 Binder.restoreCallingIdentity(ident); 6986 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 6987 } 6988 } 6989 } catch (Throwable e) { 6990 typedFuture.completeExceptionally(e); 6991 } 6992 } 6993 6994 @BinderThread 6995 @Override showMySoftInput(@onNull ImeTracker.Token statsToken, @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, AndroidFuture future )6996 public void showMySoftInput(@NonNull ImeTracker.Token statsToken, 6997 @InputMethodManager.ShowFlags int flags, @SoftInputShowHideReason int reason, 6998 AndroidFuture future /* T=Void */) { 6999 @SuppressWarnings("unchecked") final AndroidFuture<Void> typedFuture = future; 7000 try { 7001 synchronized (ImfLock.class) { 7002 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7003 ImeTracker.forLogging().onFailed(statsToken, 7004 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 7005 typedFuture.complete(null); 7006 return; 7007 } 7008 ImeTracker.forLogging().onProgress(statsToken, 7009 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 7010 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.showMySoftInput"); 7011 final long ident = Binder.clearCallingIdentity(); 7012 try { 7013 mImms.showMySoftInputLocked(statsToken, flags, reason, mUserData); 7014 typedFuture.complete(null); 7015 } finally { 7016 Binder.restoreCallingIdentity(ident); 7017 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER); 7018 } 7019 } 7020 } catch (Throwable e) { 7021 typedFuture.completeExceptionally(e); 7022 } 7023 } 7024 7025 @BinderThread 7026 @Override updateStatusIconAsync(String packageName, @DrawableRes int iconId)7027 public void updateStatusIconAsync(String packageName, @DrawableRes int iconId) { 7028 synchronized (ImfLock.class) { 7029 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7030 return; 7031 } 7032 final long ident = Binder.clearCallingIdentity(); 7033 try { 7034 mImms.updateStatusIconLocked(packageName, iconId, mUserData); 7035 } finally { 7036 Binder.restoreCallingIdentity(ident); 7037 } 7038 } 7039 } 7040 7041 @BinderThread 7042 @Override switchToPreviousInputMethod(AndroidFuture future )7043 public void switchToPreviousInputMethod(AndroidFuture future /* T=Boolean */) { 7044 @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; 7045 try { 7046 synchronized (ImfLock.class) { 7047 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7048 typedFuture.complete(false); 7049 return; 7050 } 7051 typedFuture.complete(mImms.switchToPreviousInputMethodLocked(mUserData)); 7052 } 7053 } catch (Throwable e) { 7054 typedFuture.completeExceptionally(e); 7055 } 7056 } 7057 7058 @BinderThread 7059 @Override switchToNextInputMethod(boolean onlyCurrentIme, AndroidFuture future )7060 public void switchToNextInputMethod(boolean onlyCurrentIme, 7061 AndroidFuture future /* T=Boolean */) { 7062 @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; 7063 try { 7064 synchronized (ImfLock.class) { 7065 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7066 typedFuture.complete(false); 7067 return; 7068 } 7069 typedFuture.complete(mImms.switchToNextInputMethodLocked(onlyCurrentIme, 7070 mUserData)); 7071 } 7072 } catch (Throwable e) { 7073 typedFuture.completeExceptionally(e); 7074 } 7075 } 7076 7077 @BinderThread 7078 @Override shouldOfferSwitchingToNextInputMethod(AndroidFuture future )7079 public void shouldOfferSwitchingToNextInputMethod(AndroidFuture future /* T=Boolean */) { 7080 @SuppressWarnings("unchecked") final AndroidFuture<Boolean> typedFuture = future; 7081 try { 7082 synchronized (ImfLock.class) { 7083 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7084 typedFuture.complete(false); 7085 return; 7086 } 7087 typedFuture.complete(mImms.shouldOfferSwitchingToNextInputMethodLocked( 7088 mUserData)); 7089 } 7090 } catch (Throwable e) { 7091 typedFuture.completeExceptionally(e); 7092 } 7093 } 7094 7095 @BinderThread 7096 @Override onImeSwitchButtonClickFromClient(int displayId)7097 public void onImeSwitchButtonClickFromClient(int displayId) { 7098 synchronized (ImfLock.class) { 7099 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7100 return; 7101 } 7102 mImms.onImeSwitchButtonClickLocked(displayId, mUserData); 7103 } 7104 } 7105 7106 @BinderThread 7107 @Override notifyUserActionAsync()7108 public void notifyUserActionAsync() { 7109 synchronized (ImfLock.class) { 7110 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7111 return; 7112 } 7113 mImms.notifyUserActionLocked(mUserData); 7114 } 7115 } 7116 7117 @BinderThread 7118 @Override applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, @NonNull ImeTracker.Token statsToken)7119 public void applyImeVisibilityAsync(IBinder windowToken, boolean setVisible, 7120 @NonNull ImeTracker.Token statsToken) { 7121 synchronized (ImfLock.class) { 7122 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7123 ImeTracker.forLogging().onFailed(statsToken, 7124 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 7125 return; 7126 } 7127 ImeTracker.forLogging().onProgress(statsToken, 7128 ImeTracker.PHASE_SERVER_CURRENT_ACTIVE_IME); 7129 mImms.applyImeVisibilityLocked(windowToken, setVisible, statsToken, mUserData); 7130 } 7131 } 7132 7133 @BinderThread 7134 @Override onStylusHandwritingReady(int requestId, int pid)7135 public void onStylusHandwritingReady(int requestId, int pid) { 7136 synchronized (ImfLock.class) { 7137 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7138 return; 7139 } 7140 mImms.onStylusHandwritingReadyLocked(requestId, pid, mUserData); 7141 } 7142 } 7143 7144 @BinderThread 7145 @Override resetStylusHandwriting(int requestId)7146 public void resetStylusHandwriting(int requestId) { 7147 synchronized (ImfLock.class) { 7148 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7149 return; 7150 } 7151 mImms.resetStylusHandwritingLocked(requestId); 7152 } 7153 } 7154 7155 @BinderThread 7156 @Override switchKeyboardLayoutAsync(int direction)7157 public void switchKeyboardLayoutAsync(int direction) { 7158 synchronized (ImfLock.class) { 7159 if (!calledWithValidTokenLocked(mToken, mUserData)) { 7160 return; 7161 } 7162 final long ident = Binder.clearCallingIdentity(); 7163 try { 7164 mImms.switchKeyboardLayoutLocked(direction, mUserData); 7165 } finally { 7166 Binder.restoreCallingIdentity(ident); 7167 } 7168 } 7169 } 7170 7171 /** 7172 * Returns true iff the caller is identified to be the current input method with the token. 7173 * 7174 * @param token the window token given to the input method when it was started 7175 * @param userData {@link UserData} of the calling IME process 7176 * @return true if and only if non-null valid token is specified 7177 */ 7178 @GuardedBy("ImfLock.class") calledWithValidTokenLocked(@onNull IBinder token, @NonNull UserData userData)7179 private static boolean calledWithValidTokenLocked(@NonNull IBinder token, 7180 @NonNull UserData userData) { 7181 Objects.requireNonNull(token, "token must not be null"); 7182 final var bindingController = userData.mBindingController; 7183 if (token != bindingController.getCurToken()) { 7184 Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token." 7185 + " uid:" + Binder.getCallingUid() + " token:" + token); 7186 return false; 7187 } 7188 return true; 7189 } 7190 } 7191 } 7192