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