1 /* 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 * use this file except in compliance with the License. You may obtain a copy of 5 * the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 * License for the specific language governing permissions and limitations under 13 * the License. 14 */ 15 16 package com.android.server.inputmethod; 17 18 import static android.view.Display.DEFAULT_DISPLAY; 19 import static android.view.Display.INVALID_DISPLAY; 20 21 import static java.lang.annotation.RetentionPolicy.SOURCE; 22 23 import android.Manifest; 24 import android.accessibilityservice.AccessibilityService; 25 import android.annotation.AnyThread; 26 import android.annotation.BinderThread; 27 import android.annotation.ColorInt; 28 import android.annotation.DrawableRes; 29 import android.annotation.IntDef; 30 import android.annotation.MainThread; 31 import android.annotation.NonNull; 32 import android.annotation.Nullable; 33 import android.annotation.RequiresPermission; 34 import android.annotation.UserIdInt; 35 import android.app.ActivityManager; 36 import android.app.ActivityManagerInternal; 37 import android.app.ActivityThread; 38 import android.app.AlertDialog; 39 import android.app.AppGlobals; 40 import android.app.AppOpsManager; 41 import android.app.KeyguardManager; 42 import android.app.Notification; 43 import android.app.NotificationManager; 44 import android.app.PendingIntent; 45 import android.content.BroadcastReceiver; 46 import android.content.ComponentName; 47 import android.content.ContentProvider; 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.content.DialogInterface; 51 import android.content.DialogInterface.OnCancelListener; 52 import android.content.DialogInterface.OnClickListener; 53 import android.content.Intent; 54 import android.content.IntentFilter; 55 import android.content.ServiceConnection; 56 import android.content.pm.ApplicationInfo; 57 import android.content.pm.IPackageManager; 58 import android.content.pm.PackageManager; 59 import android.content.pm.PackageManagerInternal; 60 import android.content.pm.ResolveInfo; 61 import android.content.pm.ServiceInfo; 62 import android.content.res.Configuration; 63 import android.content.res.Resources; 64 import android.content.res.TypedArray; 65 import android.database.ContentObserver; 66 import android.graphics.Matrix; 67 import android.graphics.drawable.Drawable; 68 import android.hardware.display.DisplayManagerInternal; 69 import android.hardware.input.InputManagerInternal; 70 import android.inputmethodservice.InputMethodService; 71 import android.media.AudioManagerInternal; 72 import android.net.Uri; 73 import android.os.Binder; 74 import android.os.Bundle; 75 import android.os.Debug; 76 import android.os.Handler; 77 import android.os.IBinder; 78 import android.os.IInterface; 79 import android.os.LocaleList; 80 import android.os.Message; 81 import android.os.Parcel; 82 import android.os.Process; 83 import android.os.RemoteException; 84 import android.os.ResultReceiver; 85 import android.os.ServiceManager; 86 import android.os.ShellCallback; 87 import android.os.ShellCommand; 88 import android.os.SystemClock; 89 import android.os.SystemProperties; 90 import android.os.UserHandle; 91 import android.os.UserManager; 92 import android.os.UserManagerInternal; 93 import android.provider.Settings; 94 import android.text.TextUtils; 95 import android.text.style.SuggestionSpan; 96 import android.util.ArrayMap; 97 import android.util.ArraySet; 98 import android.util.EventLog; 99 import android.util.LruCache; 100 import android.util.Pair; 101 import android.util.PrintWriterPrinter; 102 import android.util.Printer; 103 import android.util.Slog; 104 import android.util.SparseArray; 105 import android.view.ContextThemeWrapper; 106 import android.view.DisplayInfo; 107 import android.view.IWindowManager; 108 import android.view.InputChannel; 109 import android.view.LayoutInflater; 110 import android.view.View; 111 import android.view.ViewGroup; 112 import android.view.Window; 113 import android.view.WindowManager.LayoutParams; 114 import android.view.WindowManager.LayoutParams.SoftInputModeFlags; 115 import android.view.autofill.AutofillId; 116 import android.view.inputmethod.EditorInfo; 117 import android.view.inputmethod.InlineSuggestionsRequest; 118 import android.view.inputmethod.InputBinding; 119 import android.view.inputmethod.InputConnection; 120 import android.view.inputmethod.InputConnectionInspector; 121 import android.view.inputmethod.InputConnectionInspector.MissingMethodFlags; 122 import android.view.inputmethod.InputMethod; 123 import android.view.inputmethod.InputMethodInfo; 124 import android.view.inputmethod.InputMethodManager; 125 import android.view.inputmethod.InputMethodSubtype; 126 import android.widget.ArrayAdapter; 127 import android.widget.CompoundButton; 128 import android.widget.CompoundButton.OnCheckedChangeListener; 129 import android.widget.RadioButton; 130 import android.widget.Switch; 131 import android.widget.TextView; 132 133 import com.android.internal.annotations.GuardedBy; 134 import com.android.internal.content.PackageMonitor; 135 import com.android.internal.inputmethod.IInputContentUriToken; 136 import com.android.internal.inputmethod.IInputMethodPrivilegedOperations; 137 import com.android.internal.inputmethod.InputMethodDebug; 138 import com.android.internal.inputmethod.SoftInputShowHideReason; 139 import com.android.internal.inputmethod.StartInputFlags; 140 import com.android.internal.inputmethod.StartInputReason; 141 import com.android.internal.inputmethod.UnbindReason; 142 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage; 143 import com.android.internal.notification.SystemNotificationChannels; 144 import com.android.internal.os.HandlerCaller; 145 import com.android.internal.os.SomeArgs; 146 import com.android.internal.os.TransferPipe; 147 import com.android.internal.util.DumpUtils; 148 import com.android.internal.util.IndentingPrintWriter; 149 import com.android.internal.view.IInlineSuggestionsRequestCallback; 150 import com.android.internal.view.IInlineSuggestionsResponseCallback; 151 import com.android.internal.view.IInputContext; 152 import com.android.internal.view.IInputMethod; 153 import com.android.internal.view.IInputMethodClient; 154 import com.android.internal.view.IInputMethodManager; 155 import com.android.internal.view.IInputMethodSession; 156 import com.android.internal.view.IInputSessionCallback; 157 import com.android.internal.view.InlineSuggestionsRequestInfo; 158 import com.android.internal.view.InputBindResult; 159 import com.android.server.EventLogTags; 160 import com.android.server.LocalServices; 161 import com.android.server.SystemService; 162 import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener; 163 import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem; 164 import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings; 165 import com.android.server.statusbar.StatusBarManagerService; 166 import com.android.server.wm.WindowManagerInternal; 167 168 import java.io.FileDescriptor; 169 import java.io.IOException; 170 import java.io.PrintWriter; 171 import java.lang.annotation.Retention; 172 import java.security.InvalidParameterException; 173 import java.text.SimpleDateFormat; 174 import java.util.ArrayList; 175 import java.util.Arrays; 176 import java.util.Collections; 177 import java.util.Date; 178 import java.util.List; 179 import java.util.Locale; 180 import java.util.Objects; 181 import java.util.WeakHashMap; 182 import java.util.concurrent.CopyOnWriteArrayList; 183 import java.util.concurrent.atomic.AtomicInteger; 184 185 /** 186 * This class provides a system service that manages input methods. 187 */ 188 public class InputMethodManagerService extends IInputMethodManager.Stub 189 implements ServiceConnection, Handler.Callback { 190 static final boolean DEBUG = false; 191 static final String TAG = "InputMethodManagerService"; 192 193 @Retention(SOURCE) 194 @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE}) 195 private @interface ShellCommandResult { 196 int SUCCESS = 0; 197 int FAILURE = -1; 198 } 199 200 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1; 201 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2; 202 static final int MSG_SHOW_IM_CONFIG = 3; 203 204 static final int MSG_UNBIND_INPUT = 1000; 205 static final int MSG_BIND_INPUT = 1010; 206 static final int MSG_SHOW_SOFT_INPUT = 1020; 207 static final int MSG_HIDE_SOFT_INPUT = 1030; 208 static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035; 209 static final int MSG_INITIALIZE_IME = 1040; 210 static final int MSG_CREATE_SESSION = 1050; 211 static final int MSG_REMOVE_IME_SURFACE = 1060; 212 static final int MSG_REMOVE_IME_SURFACE_FROM_WINDOW = 1061; 213 214 static final int MSG_START_INPUT = 2000; 215 216 static final int MSG_UNBIND_CLIENT = 3000; 217 static final int MSG_BIND_CLIENT = 3010; 218 static final int MSG_SET_ACTIVE = 3020; 219 static final int MSG_SET_INTERACTIVE = 3030; 220 static final int MSG_REPORT_FULLSCREEN_MODE = 3045; 221 static final int MSG_REPORT_PRE_RENDERED = 3060; 222 static final int MSG_APPLY_IME_VISIBILITY = 3070; 223 224 static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; 225 226 static final int MSG_SYSTEM_UNLOCK_USER = 5000; 227 static final int MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED = 5010; 228 229 static final int MSG_INLINE_SUGGESTIONS_REQUEST = 6000; 230 231 static final int MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE = 7000; 232 233 static final long TIME_TO_RECONNECT = 3 * 1000; 234 235 static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; 236 237 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; 238 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; 239 240 /** 241 * Binding flags for establishing connection to the {@link InputMethodService}. 242 */ 243 private static final int IME_CONNECTION_BIND_FLAGS = 244 Context.BIND_AUTO_CREATE 245 | Context.BIND_NOT_VISIBLE 246 | Context.BIND_NOT_FOREGROUND 247 | Context.BIND_IMPORTANT_BACKGROUND; 248 249 /** 250 * Binding flags used only while the {@link InputMethodService} is showing window. 251 */ 252 private static final int IME_VISIBLE_BIND_FLAGS = 253 Context.BIND_AUTO_CREATE 254 | Context.BIND_TREAT_LIKE_ACTIVITY 255 | Context.BIND_FOREGROUND_SERVICE 256 | Context.BIND_INCLUDE_CAPABILITIES 257 | Context.BIND_SHOWING_UI 258 | Context.BIND_SCHEDULE_LIKE_TOP_APP; 259 260 /** 261 * A protected broadcast intent action for internal use for {@link PendingIntent} in 262 * the notification. 263 */ 264 private static final String ACTION_SHOW_INPUT_METHOD_PICKER = 265 "com.android.server.inputmethod.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER"; 266 267 /** 268 * Debug flag for overriding runtime {@link SystemProperties}. 269 */ 270 @AnyThread 271 private static final class DebugFlag { 272 private static final Object LOCK = new Object(); 273 private final String mKey; 274 private final boolean mDefaultValue; 275 @GuardedBy("LOCK") 276 private boolean mValue; 277 DebugFlag(String key, boolean defaultValue)278 public DebugFlag(String key, boolean defaultValue) { 279 mKey = key; 280 mDefaultValue = defaultValue; 281 mValue = SystemProperties.getBoolean(key, defaultValue); 282 } 283 refresh()284 void refresh() { 285 synchronized (LOCK) { 286 mValue = SystemProperties.getBoolean(mKey, mDefaultValue); 287 } 288 } 289 value()290 boolean value() { 291 synchronized (LOCK) { 292 return mValue; 293 } 294 } 295 } 296 297 /** 298 * Debug flags that can be overridden using "adb shell setprop <key>" 299 * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties". 300 */ 301 private static final class DebugFlags { 302 static final DebugFlag FLAG_OPTIMIZE_START_INPUT = 303 new DebugFlag("debug.optimize_startinput", false); 304 static final DebugFlag FLAG_PRE_RENDER_IME_VIEWS = 305 new DebugFlag("persist.pre_render_ime_views", false); 306 } 307 308 @UserIdInt 309 private int mLastSwitchUserId; 310 311 final Context mContext; 312 final Resources mRes; 313 final Handler mHandler; 314 final InputMethodSettings mSettings; 315 final SettingsObserver mSettingsObserver; 316 final IWindowManager mIWindowManager; 317 final WindowManagerInternal mWindowManagerInternal; 318 final PackageManagerInternal mPackageManagerInternal; 319 final InputManagerInternal mInputManagerInternal; 320 private final DisplayManagerInternal mDisplayManagerInternal; 321 final HandlerCaller mCaller; 322 final boolean mHasFeature; 323 private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap = 324 new ArrayMap<>(); 325 private final boolean mIsLowRam; 326 private final HardKeyboardListener mHardKeyboardListener; 327 private final AppOpsManager mAppOpsManager; 328 private final UserManager mUserManager; 329 private final UserManagerInternal mUserManagerInternal; 330 331 /** 332 * Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}. 333 * 334 * <p>This field is used only within {@link #handleMessage(Message)} hence synchronization is 335 * not necessary.</p> 336 */ 337 @Nullable 338 private AudioManagerInternal mAudioManagerInternal = null; 339 340 341 // All known input methods. mMethodMap also serves as the global 342 // lock for this class. 343 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>(); 344 final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>(); 345 private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = 346 new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE); 347 private final InputMethodSubtypeSwitchingController mSwitchingController; 348 349 /** 350 * Tracks how many times {@link #mMethodMap} was updated. 351 */ 352 @GuardedBy("mMethodMap") 353 private int mMethodMapUpdateCount = 0; 354 355 // Used to bring IME service up to visible adjustment while it is being shown. 356 final ServiceConnection mVisibleConnection = new ServiceConnection() { 357 @Override public void onBindingDied(ComponentName name) { 358 synchronized (mMethodMap) { 359 if (mVisibleBound) { 360 mContext.unbindService(mVisibleConnection); 361 mVisibleBound = false; 362 } 363 } 364 } 365 366 @Override public void onServiceConnected(ComponentName name, IBinder service) { 367 } 368 369 @Override public void onServiceDisconnected(ComponentName name) { 370 } 371 }; 372 boolean mVisibleBound = false; 373 374 // Ongoing notification 375 private NotificationManager mNotificationManager; 376 private KeyguardManager mKeyguardManager; 377 private @Nullable StatusBarManagerService mStatusBar; 378 private Notification.Builder mImeSwitcherNotification; 379 private PendingIntent mImeSwitchPendingIntent; 380 private boolean mShowOngoingImeSwitcherForPhones; 381 private boolean mNotificationShown; 382 383 static class SessionState { 384 final ClientState client; 385 final IInputMethod method; 386 387 IInputMethodSession session; 388 InputChannel channel; 389 390 @Override toString()391 public String toString() { 392 return "SessionState{uid " + client.uid + " pid " + client.pid 393 + " method " + Integer.toHexString( 394 System.identityHashCode(method)) 395 + " session " + Integer.toHexString( 396 System.identityHashCode(session)) 397 + " channel " + channel 398 + "}"; 399 } 400 SessionState(ClientState _client, IInputMethod _method, IInputMethodSession _session, InputChannel _channel)401 SessionState(ClientState _client, IInputMethod _method, 402 IInputMethodSession _session, InputChannel _channel) { 403 client = _client; 404 method = _method; 405 session = _session; 406 channel = _channel; 407 } 408 } 409 410 private static final class ClientDeathRecipient implements IBinder.DeathRecipient { 411 private final InputMethodManagerService mImms; 412 private final IInputMethodClient mClient; 413 ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client)414 ClientDeathRecipient(InputMethodManagerService imms, IInputMethodClient client) { 415 mImms = imms; 416 mClient = client; 417 } 418 419 @Override binderDied()420 public void binderDied() { 421 mImms.removeClient(mClient); 422 } 423 } 424 425 static final class ClientState { 426 final IInputMethodClient client; 427 final IInputContext inputContext; 428 final int uid; 429 final int pid; 430 final int selfReportedDisplayId; 431 final InputBinding binding; 432 final ClientDeathRecipient clientDeathRecipient; 433 434 boolean sessionRequested; 435 // Determines if IMEs should be pre-rendered. 436 // DebugFlag can be flipped anytime. This flag is kept per-client to maintain behavior 437 // through the life of the current client. 438 boolean shouldPreRenderIme; 439 SessionState curSession; 440 441 @Override toString()442 public String toString() { 443 return "ClientState{" + Integer.toHexString( 444 System.identityHashCode(this)) + " uid=" + uid 445 + " pid=" + pid + " displayId=" + selfReportedDisplayId + "}"; 446 } 447 ClientState(IInputMethodClient _client, IInputContext _inputContext, int _uid, int _pid, int _selfReportedDisplayId, ClientDeathRecipient _clientDeathRecipient)448 ClientState(IInputMethodClient _client, IInputContext _inputContext, 449 int _uid, int _pid, int _selfReportedDisplayId, 450 ClientDeathRecipient _clientDeathRecipient) { 451 client = _client; 452 inputContext = _inputContext; 453 uid = _uid; 454 pid = _pid; 455 selfReportedDisplayId = _selfReportedDisplayId; 456 binding = new InputBinding(null, inputContext.asBinder(), uid, pid); 457 clientDeathRecipient = _clientDeathRecipient; 458 } 459 } 460 461 final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<>(); 462 463 private static final class ActivityViewInfo { 464 /** 465 * {@link ClientState} where {@link android.app.ActivityView} is running. 466 */ 467 private final ClientState mParentClient; 468 /** 469 * {@link Matrix} to convert screen coordinates in the embedded virtual display to 470 * screen coordinates where {@link #mParentClient} exists. 471 */ 472 private final Matrix mMatrix; 473 ActivityViewInfo(ClientState parentClient, Matrix matrix)474 ActivityViewInfo(ClientState parentClient, Matrix matrix) { 475 mParentClient = parentClient; 476 mMatrix = matrix; 477 } 478 } 479 480 /** 481 * A mapping table from virtual display IDs created for {@link android.app.ActivityView} 482 * to its parent IME client where {@link android.app.ActivityView} is running. 483 * 484 * <p>Note: this can be used only for virtual display IDs created by 485 * {@link android.app.ActivityView}.</p> 486 */ 487 private SparseArray<ActivityViewInfo> mActivityViewDisplayIdToParentMap = new SparseArray<>(); 488 489 /** 490 * Set once the system is ready to run third party code. 491 */ 492 boolean mSystemReady; 493 494 /** 495 * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method. 496 * method. This is to be synchronized with the secure settings keyed with 497 * {@link Settings.Secure#DEFAULT_INPUT_METHOD}. 498 * 499 * <p>This can be transiently {@code null} when the system is re-initializing input method 500 * settings, e.g., the system locale is just changed.</p> 501 * 502 * <p>Note that {@link #mCurId} is used to track which IME is being connected to 503 * {@link InputMethodManagerService}.</p> 504 * 505 * @see #mCurId 506 */ 507 @Nullable 508 String mCurMethodId; 509 510 /** 511 * The current binding sequence number, incremented every time there is 512 * a new bind performed. 513 */ 514 int mCurSeq; 515 516 /** 517 * The client that is currently bound to an input method. 518 */ 519 ClientState mCurClient; 520 521 /** 522 * The last window token that we confirmed to be focused. This is always updated upon reports 523 * from the input method client. If the window state is already changed before the report is 524 * handled, this field just keeps the last value. 525 */ 526 IBinder mCurFocusedWindow; 527 528 /** 529 * The last window token that we confirmed that IME started talking to. This is always updated 530 * upon reports from the input method. If the window state is already changed before the report 531 * is handled, this field just keeps the last value. 532 */ 533 IBinder mLastImeTargetWindow; 534 535 /** 536 * {@link LayoutParams#softInputMode} of {@link #mCurFocusedWindow}. 537 * 538 * @see #mCurFocusedWindow 539 */ 540 @SoftInputModeFlags 541 int mCurFocusedWindowSoftInputMode; 542 543 /** 544 * The client by which {@link #mCurFocusedWindow} was reported. 545 */ 546 ClientState mCurFocusedWindowClient; 547 548 /** 549 * The input context last provided by the current client. 550 */ 551 IInputContext mCurInputContext; 552 553 /** 554 * The missing method flags for the input context last provided by the current client. 555 * 556 * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags 557 */ 558 @MissingMethodFlags 559 int mCurInputContextMissingMethods; 560 561 /** 562 * The attributes last provided by the current client. 563 */ 564 EditorInfo mCurAttribute; 565 566 /** 567 * A special {@link Matrix} to convert virtual screen coordinates to the IME target display 568 * coordinates. 569 * 570 * <p>Used only while the IME client is running in a virtual display inside 571 * {@link android.app.ActivityView}. {@code null} otherwise.</p> 572 */ 573 @Nullable 574 private Matrix mCurActivityViewToScreenMatrix = null; 575 576 /** 577 * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently 578 * connected to or in the process of connecting to. 579 * 580 * <p>This can be {@code null} when no input method is connected.</p> 581 * 582 * @see #mCurMethodId 583 */ 584 @Nullable 585 String mCurId; 586 587 /** 588 * The current subtype of the current input method. 589 */ 590 private InputMethodSubtype mCurrentSubtype; 591 592 // Was the keyguard locked when this client became current? 593 private boolean mCurClientInKeyguard; 594 595 /** 596 * {@code true} if the IME has not been mostly hidden via {@link android.view.InsetsController} 597 */ 598 private boolean mCurPerceptible; 599 600 /** 601 * Set to true if our ServiceConnection is currently actively bound to 602 * a service (whether or not we have gotten its IBinder back yet). 603 */ 604 boolean mHaveConnection; 605 606 /** 607 * Set if the client has asked for the input method to be shown. 608 */ 609 boolean mShowRequested; 610 611 /** 612 * Set if we were explicitly told to show the input method. 613 */ 614 boolean mShowExplicitlyRequested; 615 616 /** 617 * Set if we were forced to be shown. 618 */ 619 boolean mShowForced; 620 621 /** 622 * Set if we last told the input method to show itself. 623 */ 624 boolean mInputShown; 625 626 /** 627 * {@code true} if the current input method is in fullscreen mode. 628 */ 629 boolean mInFullscreenMode; 630 631 /** 632 * The Intent used to connect to the current input method. 633 */ 634 Intent mCurIntent; 635 636 /** 637 * The token we have made for the currently active input method, to 638 * identify it in the future. 639 */ 640 IBinder mCurToken; 641 642 /** 643 * The displayId of current active input method. 644 */ 645 int mCurTokenDisplayId = INVALID_DISPLAY; 646 647 /** 648 * The host input token of the current active input method. 649 */ 650 @GuardedBy("mMethodMap") 651 @Nullable 652 private IBinder mCurHostInputToken; 653 654 /** 655 * The display ID of the input method indicates the fallback display which returned by 656 * {@link #computeImeDisplayIdForTarget}. 657 */ 658 private static final int FALLBACK_DISPLAY_ID = DEFAULT_DISPLAY; 659 660 final ImeDisplayValidator mImeDisplayValidator; 661 662 /** 663 * If non-null, this is the input method service we are currently connected 664 * to. 665 */ 666 IInputMethod mCurMethod; 667 668 /** 669 * If not {@link Process#INVALID_UID}, then the UID of {@link #mCurIntent}. 670 */ 671 int mCurMethodUid = Process.INVALID_UID; 672 673 /** 674 * Time that we last initiated a bind to the input method, to determine 675 * if we should try to disconnect and reconnect to it. 676 */ 677 long mLastBindTime; 678 679 /** 680 * Have we called mCurMethod.bindInput()? 681 */ 682 boolean mBoundToMethod; 683 684 /** 685 * Currently enabled session. Only touched by service thread, not 686 * protected by a lock. 687 */ 688 SessionState mEnabledSession; 689 690 /** 691 * True if the device is currently interactive with user. The value is true initially. 692 */ 693 boolean mIsInteractive = true; 694 695 int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; 696 697 /** 698 * A set of status bits regarding the active IME. 699 * 700 * <p>This value is a combination of following two bits:</p> 701 * <dl> 702 * <dt>{@link InputMethodService#IME_ACTIVE}</dt> 703 * <dd> 704 * If this bit is ON, connected IME is ready to accept touch/key events. 705 * </dd> 706 * <dt>{@link InputMethodService#IME_VISIBLE}</dt> 707 * <dd> 708 * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible. 709 * </dd> 710 * dt>{@link InputMethodService#IME_INVISIBLE}</dt> 711 * <dd> If this bit is ON, IME is ready with views from last EditorInfo but is 712 * currently invisible. 713 * </dd> 714 * </dl> 715 * <em>Do not update this value outside of {@link #setImeWindowStatus(IBinder, int, int)} and 716 * {@link #unbindCurrentMethodLocked()}.</em> 717 */ 718 int mImeWindowVis; 719 720 private AlertDialog.Builder mDialogBuilder; 721 private AlertDialog mSwitchingDialog; 722 private IBinder mSwitchingDialogToken = new Binder(); 723 private View mSwitchingDialogTitleView; 724 private InputMethodInfo[] mIms; 725 private int[] mSubtypeIds; 726 private LocaleList mLastSystemLocales; 727 private boolean mShowImeWithHardKeyboard; 728 private boolean mAccessibilityRequestingNoSoftKeyboard; 729 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); 730 private final IPackageManager mIPackageManager; 731 private final String mSlotIme; 732 733 /** 734 * Registered {@link InputMethodListListeners}. 735 * This variable can be accessed from both of MainThread and BinderThread. 736 */ 737 private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners = 738 new CopyOnWriteArrayList<>(); 739 740 /** 741 * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the 742 * internal message queue. Any subsequent state change inside {@link InputMethodManagerService} 743 * will not affect those tasks that are already posted. 744 * 745 * <p>Posting {@link #MSG_START_INPUT} message basically means that 746 * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called 747 * back in the current IME process shortly, which will also affect what the current IME starts 748 * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this 749 * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new 750 * logical input session between the client application and the current IME.</p> 751 * 752 * <p>Be careful to not keep strong references to this object forever, which can prevent 753 * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed. 754 * </p> 755 */ 756 private static class StartInputInfo { 757 private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); 758 759 final int mSequenceNumber; 760 final long mTimestamp; 761 final long mWallTime; 762 @UserIdInt 763 final int mImeUserId; 764 @NonNull 765 final IBinder mImeToken; 766 final int mImeDisplayId; 767 @NonNull 768 final String mImeId; 769 @StartInputReason 770 final int mStartInputReason; 771 final boolean mRestarting; 772 @UserIdInt 773 final int mTargetUserId; 774 final int mTargetDisplayId; 775 @Nullable 776 final IBinder mTargetWindow; 777 @NonNull 778 final EditorInfo mEditorInfo; 779 @SoftInputModeFlags 780 final int mTargetWindowSoftInputMode; 781 final int mClientBindSequenceNumber; 782 StartInputInfo(@serIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId, @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting, @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode, int clientBindSequenceNumber)783 StartInputInfo(@UserIdInt int imeUserId, @NonNull IBinder imeToken, int imeDisplayId, 784 @NonNull String imeId, @StartInputReason int startInputReason, boolean restarting, 785 @UserIdInt int targetUserId, int targetDisplayId, @Nullable IBinder targetWindow, 786 @NonNull EditorInfo editorInfo, @SoftInputModeFlags int targetWindowSoftInputMode, 787 int clientBindSequenceNumber) { 788 mSequenceNumber = sSequenceNumber.getAndIncrement(); 789 mTimestamp = SystemClock.uptimeMillis(); 790 mWallTime = System.currentTimeMillis(); 791 mImeUserId = imeUserId; 792 mImeToken = imeToken; 793 mImeDisplayId = imeDisplayId; 794 mImeId = imeId; 795 mStartInputReason = startInputReason; 796 mRestarting = restarting; 797 mTargetUserId = targetUserId; 798 mTargetDisplayId = targetDisplayId; 799 mTargetWindow = targetWindow; 800 mEditorInfo = editorInfo; 801 mTargetWindowSoftInputMode = targetWindowSoftInputMode; 802 mClientBindSequenceNumber = clientBindSequenceNumber; 803 } 804 } 805 806 @GuardedBy("mMethodMap") 807 private final WeakHashMap<IBinder, IBinder> mImeTargetWindowMap = new WeakHashMap<>(); 808 809 private static final class SoftInputShowHideHistory { 810 private Entry[] mEntries = new Entry[16]; 811 private int mNextIndex = 0; 812 private static final AtomicInteger sSequenceNumber = new AtomicInteger(0); 813 814 private static final class Entry { 815 final int mSequenceNumber = sSequenceNumber.getAndIncrement(); 816 final ClientState mClientState; 817 @SoftInputModeFlags 818 final int mFocusedWindowSoftInputMode; 819 @SoftInputShowHideReason 820 final int mReason; 821 // The timing of handling MSG_SHOW_SOFT_INPUT or MSG_HIDE_SOFT_INPUT. 822 final long mTimestamp; 823 final long mWallTime; 824 final boolean mInFullscreenMode; 825 @NonNull 826 final String mFocusedWindowName; 827 @NonNull 828 final EditorInfo mEditorInfo; 829 @NonNull 830 final String mRequestWindowName; 831 @Nullable 832 final String mImeControlTargetName; 833 @Nullable 834 final String mImeTargetNameFromWm; 835 Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName, @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason, boolean inFullscreenMode, String requestWindowName, @Nullable String imeControlTargetName, @Nullable String imeTargetName)836 Entry(ClientState client, EditorInfo editorInfo, String focusedWindowName, 837 @SoftInputModeFlags int softInputMode, @SoftInputShowHideReason int reason, 838 boolean inFullscreenMode, String requestWindowName, 839 @Nullable String imeControlTargetName, @Nullable String imeTargetName) { 840 mClientState = client; 841 mEditorInfo = editorInfo; 842 mFocusedWindowName = focusedWindowName; 843 mFocusedWindowSoftInputMode = softInputMode; 844 mReason = reason; 845 mTimestamp = SystemClock.uptimeMillis(); 846 mWallTime = System.currentTimeMillis(); 847 mInFullscreenMode = inFullscreenMode; 848 mRequestWindowName = requestWindowName; 849 mImeControlTargetName = imeControlTargetName; 850 mImeTargetNameFromWm = imeTargetName; 851 } 852 } 853 addEntry(@onNull Entry entry)854 void addEntry(@NonNull Entry entry) { 855 final int index = mNextIndex; 856 mEntries[index] = entry; 857 mNextIndex = (mNextIndex + 1) % mEntries.length; 858 } 859 dump(@onNull PrintWriter pw, @NonNull String prefix)860 void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 861 final SimpleDateFormat dataFormat = 862 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); 863 864 for (int i = 0; i < mEntries.length; ++i) { 865 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; 866 if (entry == null) { 867 continue; 868 } 869 pw.print(prefix); 870 pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":"); 871 872 pw.print(prefix); 873 pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime)) 874 + " (timestamp=" + entry.mTimestamp + ")"); 875 876 pw.print(prefix); 877 pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString( 878 entry.mReason)); 879 pw.println(" inFullscreenMode=" + entry.mInFullscreenMode); 880 881 pw.print(prefix); 882 pw.println(" requestClient=" + entry.mClientState); 883 884 pw.print(prefix); 885 pw.println(" focusedWindowName=" + entry.mFocusedWindowName); 886 887 pw.print(prefix); 888 pw.println(" requestWindowName=" + entry.mRequestWindowName); 889 890 pw.print(prefix); 891 pw.println(" imeControlTargetName=" + entry.mImeControlTargetName); 892 893 pw.print(prefix); 894 pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm); 895 896 pw.print(prefix); 897 pw.print(" editorInfo: "); 898 pw.print(" inputType=" + entry.mEditorInfo.inputType); 899 pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions); 900 pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId); 901 902 pw.print(prefix); 903 pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString( 904 entry.mFocusedWindowSoftInputMode)); 905 } 906 } 907 } 908 909 /** 910 * Map of generated token to windowToken that is requesting 911 * {@link InputMethodManager#showSoftInput(View, int)}. 912 * This map tracks origin of showSoftInput requests. 913 */ 914 @GuardedBy("mMethodMap") 915 private final WeakHashMap<IBinder, IBinder> mShowRequestWindowMap = new WeakHashMap<>(); 916 917 /** 918 * Map of generated token to windowToken that is requesting 919 * {@link InputMethodManager#hideSoftInputFromWindow(IBinder, int)}. 920 * This map tracks origin of hideSoftInput requests. 921 */ 922 @GuardedBy("mMethodMap") 923 private final WeakHashMap<IBinder, IBinder> mHideRequestWindowMap = new WeakHashMap<>(); 924 925 /** 926 * A ring buffer to store the history of {@link StartInputInfo}. 927 */ 928 private static final class StartInputHistory { 929 /** 930 * Entry size for non low-RAM devices. 931 * 932 * <p>TODO: Consider to follow what other system services have been doing to manage 933 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> 934 */ 935 private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16; 936 937 /** 938 * Entry size for non low-RAM devices. 939 * 940 * <p>TODO: Consider to follow what other system services have been doing to manage 941 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p> 942 */ 943 private final static int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5; 944 getEntrySize()945 private static int getEntrySize() { 946 if (ActivityManager.isLowRamDeviceStatic()) { 947 return ENTRY_SIZE_FOR_LOW_RAM_DEVICE; 948 } else { 949 return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE; 950 } 951 } 952 953 /** 954 * Backing store for the ring bugger. 955 */ 956 private final Entry[] mEntries = new Entry[getEntrySize()]; 957 958 /** 959 * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should 960 * write. 961 */ 962 private int mNextIndex = 0; 963 964 /** 965 * Recyclable entry to store the information in {@link StartInputInfo}. 966 */ 967 private static final class Entry { 968 int mSequenceNumber; 969 long mTimestamp; 970 long mWallTime; 971 @UserIdInt 972 int mImeUserId; 973 @NonNull 974 String mImeTokenString; 975 int mImeDisplayId; 976 @NonNull 977 String mImeId; 978 @StartInputReason 979 int mStartInputReason; 980 boolean mRestarting; 981 @UserIdInt 982 int mTargetUserId; 983 int mTargetDisplayId; 984 @NonNull 985 String mTargetWindowString; 986 @NonNull 987 EditorInfo mEditorInfo; 988 @SoftInputModeFlags 989 int mTargetWindowSoftInputMode; 990 int mClientBindSequenceNumber; 991 Entry(@onNull StartInputInfo original)992 Entry(@NonNull StartInputInfo original) { 993 set(original); 994 } 995 set(@onNull StartInputInfo original)996 void set(@NonNull StartInputInfo original) { 997 mSequenceNumber = original.mSequenceNumber; 998 mTimestamp = original.mTimestamp; 999 mWallTime = original.mWallTime; 1000 mImeUserId = original.mImeUserId; 1001 // Intentionally convert to String so as not to keep a strong reference to a Binder 1002 // object. 1003 mImeTokenString = String.valueOf(original.mImeToken); 1004 mImeDisplayId = original.mImeDisplayId; 1005 mImeId = original.mImeId; 1006 mStartInputReason = original.mStartInputReason; 1007 mRestarting = original.mRestarting; 1008 mTargetUserId = original.mTargetUserId; 1009 mTargetDisplayId = original.mTargetDisplayId; 1010 // Intentionally convert to String so as not to keep a strong reference to a Binder 1011 // object. 1012 mTargetWindowString = String.valueOf(original.mTargetWindow); 1013 mEditorInfo = original.mEditorInfo; 1014 mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode; 1015 mClientBindSequenceNumber = original.mClientBindSequenceNumber; 1016 } 1017 } 1018 1019 /** 1020 * Add a new entry and discard the oldest entry as needed. 1021 * @param info {@lin StartInputInfo} to be added. 1022 */ addEntry(@onNull StartInputInfo info)1023 void addEntry(@NonNull StartInputInfo info) { 1024 final int index = mNextIndex; 1025 if (mEntries[index] == null) { 1026 mEntries[index] = new Entry(info); 1027 } else { 1028 mEntries[index].set(info); 1029 } 1030 mNextIndex = (mNextIndex + 1) % mEntries.length; 1031 } 1032 dump(@onNull PrintWriter pw, @NonNull String prefix)1033 void dump(@NonNull PrintWriter pw, @NonNull String prefix) { 1034 final SimpleDateFormat dataFormat = 1035 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US); 1036 1037 for (int i = 0; i < mEntries.length; ++i) { 1038 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length]; 1039 if (entry == null) { 1040 continue; 1041 } 1042 pw.print(prefix); 1043 pw.println("StartInput #" + entry.mSequenceNumber + ":"); 1044 1045 pw.print(prefix); 1046 pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime)) 1047 + " (timestamp=" + entry.mTimestamp + ")" 1048 + " reason=" 1049 + InputMethodDebug.startInputReasonToString(entry.mStartInputReason) 1050 + " restarting=" + entry.mRestarting); 1051 1052 pw.print(prefix); 1053 pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]"); 1054 pw.print(" imeUserId=" + entry.mImeUserId); 1055 pw.println(" imeDisplayId=" + entry.mImeDisplayId); 1056 1057 pw.print(prefix); 1058 pw.println(" targetWin=" + entry.mTargetWindowString 1059 + " [" + entry.mEditorInfo.packageName + "]" 1060 + " targetUserId=" + entry.mTargetUserId 1061 + " targetDisplayId=" + entry.mTargetDisplayId 1062 + " clientBindSeq=" + entry.mClientBindSequenceNumber); 1063 1064 pw.print(prefix); 1065 pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString( 1066 entry.mTargetWindowSoftInputMode)); 1067 1068 pw.print(prefix); 1069 pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType) 1070 + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions) 1071 + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId) 1072 + " fieldName=" + entry.mEditorInfo.fieldName 1073 + " actionId=" + entry.mEditorInfo.actionId 1074 + " actionLabel=" + entry.mEditorInfo.actionLabel); 1075 } 1076 } 1077 } 1078 1079 @GuardedBy("mMethodMap") 1080 @NonNull 1081 private final StartInputHistory mStartInputHistory = new StartInputHistory(); 1082 1083 @GuardedBy("mMethodMap") 1084 @NonNull 1085 private final SoftInputShowHideHistory mSoftInputShowHideHistory = 1086 new SoftInputShowHideHistory(); 1087 1088 class SettingsObserver extends ContentObserver { 1089 int mUserId; 1090 boolean mRegistered = false; 1091 @NonNull 1092 String mLastEnabled = ""; 1093 1094 /** 1095 * <em>This constructor must be called within the lock.</em> 1096 */ SettingsObserver(Handler handler)1097 SettingsObserver(Handler handler) { 1098 super(handler); 1099 } 1100 registerContentObserverLocked(@serIdInt int userId)1101 public void registerContentObserverLocked(@UserIdInt int userId) { 1102 if (mRegistered && mUserId == userId) { 1103 return; 1104 } 1105 ContentResolver resolver = mContext.getContentResolver(); 1106 if (mRegistered) { 1107 mContext.getContentResolver().unregisterContentObserver(this); 1108 mRegistered = false; 1109 } 1110 if (mUserId != userId) { 1111 mLastEnabled = ""; 1112 mUserId = userId; 1113 } 1114 resolver.registerContentObserver(Settings.Secure.getUriFor( 1115 Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId); 1116 resolver.registerContentObserver(Settings.Secure.getUriFor( 1117 Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId); 1118 resolver.registerContentObserver(Settings.Secure.getUriFor( 1119 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId); 1120 resolver.registerContentObserver(Settings.Secure.getUriFor( 1121 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId); 1122 resolver.registerContentObserver(Settings.Secure.getUriFor( 1123 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId); 1124 mRegistered = true; 1125 } 1126 onChange(boolean selfChange, Uri uri)1127 @Override public void onChange(boolean selfChange, Uri uri) { 1128 final Uri showImeUri = Settings.Secure.getUriFor( 1129 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD); 1130 final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor( 1131 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE); 1132 synchronized (mMethodMap) { 1133 if (showImeUri.equals(uri)) { 1134 updateKeyboardFromSettingsLocked(); 1135 } else if (accessibilityRequestingNoImeUri.equals(uri)) { 1136 final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser( 1137 mContext.getContentResolver(), 1138 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE, 0, mUserId); 1139 mAccessibilityRequestingNoSoftKeyboard = 1140 (accessibilitySoftKeyboardSetting & AccessibilityService.SHOW_MODE_MASK) 1141 == AccessibilityService.SHOW_MODE_HIDDEN; 1142 if (mAccessibilityRequestingNoSoftKeyboard) { 1143 final boolean showRequested = mShowRequested; 1144 hideCurrentInputLocked(mCurFocusedWindow, 0, null, 1145 SoftInputShowHideReason.HIDE_SETTINGS_ON_CHANGE); 1146 mShowRequested = showRequested; 1147 } else if (mShowRequested) { 1148 showCurrentInputLocked(mCurFocusedWindow, 1149 InputMethodManager.SHOW_IMPLICIT, null, 1150 SoftInputShowHideReason.SHOW_SETTINGS_ON_CHANGE); 1151 } 1152 } else { 1153 boolean enabledChanged = false; 1154 String newEnabled = mSettings.getEnabledInputMethodsStr(); 1155 if (!mLastEnabled.equals(newEnabled)) { 1156 mLastEnabled = newEnabled; 1157 enabledChanged = true; 1158 } 1159 updateInputMethodsFromSettingsLocked(enabledChanged); 1160 } 1161 } 1162 } 1163 1164 @Override toString()1165 public String toString() { 1166 return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered 1167 + " mLastEnabled=" + mLastEnabled + "}"; 1168 } 1169 } 1170 1171 /** 1172 * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to the system user 1173 * only. 1174 */ 1175 private final class ImmsBroadcastReceiverForSystemUser extends BroadcastReceiver { 1176 @Override onReceive(Context context, Intent intent)1177 public void onReceive(Context context, Intent intent) { 1178 final String action = intent.getAction(); 1179 if (Intent.ACTION_USER_ADDED.equals(action) 1180 || Intent.ACTION_USER_REMOVED.equals(action)) { 1181 updateCurrentProfileIds(); 1182 return; 1183 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) { 1184 onActionLocaleChanged(); 1185 } else if (ACTION_SHOW_INPUT_METHOD_PICKER.equals(action)) { 1186 // ACTION_SHOW_INPUT_METHOD_PICKER action is a protected-broadcast and it is 1187 // guaranteed to be send only from the system, so that there is no need for extra 1188 // security check such as 1189 // {@link #canShowInputMethodPickerLocked(IInputMethodClient)}. 1190 mHandler.obtainMessage( 1191 MSG_SHOW_IM_SUBTYPE_PICKER, 1192 // TODO(b/120076400): Design and implement IME switcher for heterogeneous 1193 // navbar configuration. 1194 InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES, 1195 DEFAULT_DISPLAY).sendToTarget(); 1196 } else { 1197 Slog.w(TAG, "Unexpected intent " + intent); 1198 } 1199 } 1200 } 1201 1202 /** 1203 * {@link BroadcastReceiver} that is intended to listen to broadcasts sent to all the users. 1204 */ 1205 private final class ImmsBroadcastReceiverForAllUsers extends BroadcastReceiver { 1206 @Override onReceive(Context context, Intent intent)1207 public void onReceive(Context context, Intent intent) { 1208 final String action = intent.getAction(); 1209 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 1210 final PendingResult pendingResult = getPendingResult(); 1211 if (pendingResult == null) { 1212 return; 1213 } 1214 // sender userId can be a real user ID or USER_ALL. 1215 final int senderUserId = pendingResult.getSendingUserId(); 1216 if (senderUserId != UserHandle.USER_ALL) { 1217 if (senderUserId != mSettings.getCurrentUserId()) { 1218 // A background user is trying to hide the dialog. Ignore. 1219 return; 1220 } 1221 } 1222 hideInputMethodMenu(); 1223 } else { 1224 Slog.w(TAG, "Unexpected intent " + intent); 1225 } 1226 } 1227 } 1228 1229 /** 1230 * Handles {@link Intent#ACTION_LOCALE_CHANGED}. 1231 * 1232 * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all 1233 * the users. We should ignore this event if this is about any background user's locale.</p> 1234 * 1235 * <p>Caution: This method must not be called when system is not ready.</p> 1236 */ onActionLocaleChanged()1237 void onActionLocaleChanged() { 1238 synchronized (mMethodMap) { 1239 final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales(); 1240 if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) { 1241 return; 1242 } 1243 buildInputMethodListLocked(true); 1244 // If the locale is changed, needs to reset the default ime 1245 resetDefaultImeLocked(mContext); 1246 updateFromSettingsLocked(true); 1247 mLastSystemLocales = possibleNewLocale; 1248 } 1249 } 1250 1251 final class MyPackageMonitor extends PackageMonitor { 1252 /** 1253 * Package names that are known to contain {@link InputMethodService}. 1254 * 1255 * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan 1256 * all the packages when the user is unlocked, and direct-boot awareness will not be changed 1257 * dynamically unless the entire package is updated, which also always triggers package 1258 * rescanning.</p> 1259 */ 1260 @GuardedBy("mMethodMap") 1261 final private ArraySet<String> mKnownImePackageNames = new ArraySet<>(); 1262 1263 /** 1264 * Packages that are appeared, disappeared, or modified for whatever reason. 1265 * 1266 * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet} 1267 * because 1) the number of elements is almost always 1 or so, and 2) we do not care 1268 * duplicate elements for our use case.</p> 1269 * 1270 * <p>This object must be accessed only from callback methods in {@link PackageMonitor}, 1271 * which should be bound to {@link #getRegisteredHandler()}.</p> 1272 */ 1273 private final ArrayList<String> mChangedPackages = new ArrayList<>(); 1274 1275 /** 1276 * {@code true} if one or more packages that contain {@link InputMethodService} appeared. 1277 * 1278 * <p>This field must be accessed only from callback methods in {@link PackageMonitor}, 1279 * which should be bound to {@link #getRegisteredHandler()}.</p> 1280 */ 1281 private boolean mImePackageAppeared = false; 1282 1283 @GuardedBy("mMethodMap") clearKnownImePackageNamesLocked()1284 void clearKnownImePackageNamesLocked() { 1285 mKnownImePackageNames.clear(); 1286 } 1287 1288 @GuardedBy("mMethodMap") addKnownImePackageNameLocked(@onNull String packageName)1289 final void addKnownImePackageNameLocked(@NonNull String packageName) { 1290 mKnownImePackageNames.add(packageName); 1291 } 1292 1293 @GuardedBy("mMethodMap") isChangingPackagesOfCurrentUserLocked()1294 private boolean isChangingPackagesOfCurrentUserLocked() { 1295 final int userId = getChangingUserId(); 1296 final boolean retval = userId == mSettings.getCurrentUserId(); 1297 if (DEBUG) { 1298 if (!retval) { 1299 Slog.d(TAG, "--- ignore this call back from a background user: " + userId); 1300 } 1301 } 1302 return retval; 1303 } 1304 1305 @Override onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit)1306 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 1307 synchronized (mMethodMap) { 1308 if (!isChangingPackagesOfCurrentUserLocked()) { 1309 return false; 1310 } 1311 String curInputMethodId = mSettings.getSelectedInputMethod(); 1312 final int N = mMethodList.size(); 1313 if (curInputMethodId != null) { 1314 for (int i=0; i<N; i++) { 1315 InputMethodInfo imi = mMethodList.get(i); 1316 if (imi.getId().equals(curInputMethodId)) { 1317 for (String pkg : packages) { 1318 if (imi.getPackageName().equals(pkg)) { 1319 if (!doit) { 1320 return true; 1321 } 1322 resetSelectedInputMethodAndSubtypeLocked(""); 1323 chooseNewDefaultIMELocked(); 1324 return true; 1325 } 1326 } 1327 } 1328 } 1329 } 1330 } 1331 return false; 1332 } 1333 1334 @Override onBeginPackageChanges()1335 public void onBeginPackageChanges() { 1336 clearPackageChangeState(); 1337 } 1338 1339 @Override onPackageAppeared(String packageName, int reason)1340 public void onPackageAppeared(String packageName, int reason) { 1341 if (!mImePackageAppeared) { 1342 final PackageManager pm = mContext.getPackageManager(); 1343 final List<ResolveInfo> services = pm.queryIntentServicesAsUser( 1344 new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName), 1345 PackageManager.MATCH_DISABLED_COMPONENTS, getChangingUserId()); 1346 // No need to lock this because we access it only on getRegisteredHandler(). 1347 if (!services.isEmpty()) { 1348 mImePackageAppeared = true; 1349 } 1350 } 1351 // No need to lock this because we access it only on getRegisteredHandler(). 1352 mChangedPackages.add(packageName); 1353 } 1354 1355 @Override onPackageDisappeared(String packageName, int reason)1356 public void onPackageDisappeared(String packageName, int reason) { 1357 // No need to lock this because we access it only on getRegisteredHandler(). 1358 mChangedPackages.add(packageName); 1359 } 1360 1361 @Override onPackageModified(String packageName)1362 public void onPackageModified(String packageName) { 1363 // No need to lock this because we access it only on getRegisteredHandler(). 1364 mChangedPackages.add(packageName); 1365 } 1366 1367 @Override onPackagesSuspended(String[] packages)1368 public void onPackagesSuspended(String[] packages) { 1369 // No need to lock this because we access it only on getRegisteredHandler(). 1370 for (String packageName : packages) { 1371 mChangedPackages.add(packageName); 1372 } 1373 } 1374 1375 @Override onPackagesUnsuspended(String[] packages)1376 public void onPackagesUnsuspended(String[] packages) { 1377 // No need to lock this because we access it only on getRegisteredHandler(). 1378 for (String packageName : packages) { 1379 mChangedPackages.add(packageName); 1380 } 1381 } 1382 1383 @Override onFinishPackageChanges()1384 public void onFinishPackageChanges() { 1385 onFinishPackageChangesInternal(); 1386 clearPackageChangeState(); 1387 } 1388 clearPackageChangeState()1389 private void clearPackageChangeState() { 1390 // No need to lock them because we access these fields only on getRegisteredHandler(). 1391 mChangedPackages.clear(); 1392 mImePackageAppeared = false; 1393 } 1394 1395 @GuardedBy("mMethodMap") shouldRebuildInputMethodListLocked()1396 private boolean shouldRebuildInputMethodListLocked() { 1397 // This method is guaranteed to be called only by getRegisteredHandler(). 1398 1399 // If there is any new package that contains at least one IME, then rebuilt the list 1400 // of IMEs. 1401 if (mImePackageAppeared) { 1402 return true; 1403 } 1404 1405 // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection. 1406 // TODO: Consider to create a utility method to do the following test. List.retainAll() 1407 // is an option, but it may still do some extra operations that we do not need here. 1408 final int N = mChangedPackages.size(); 1409 for (int i = 0; i < N; ++i) { 1410 final String packageName = mChangedPackages.get(i); 1411 if (mKnownImePackageNames.contains(packageName)) { 1412 return true; 1413 } 1414 } 1415 return false; 1416 } 1417 onFinishPackageChangesInternal()1418 private void onFinishPackageChangesInternal() { 1419 synchronized (mMethodMap) { 1420 if (!isChangingPackagesOfCurrentUserLocked()) { 1421 return; 1422 } 1423 if (!shouldRebuildInputMethodListLocked()) { 1424 return; 1425 } 1426 1427 InputMethodInfo curIm = null; 1428 String curInputMethodId = mSettings.getSelectedInputMethod(); 1429 final int N = mMethodList.size(); 1430 if (curInputMethodId != null) { 1431 for (int i=0; i<N; i++) { 1432 InputMethodInfo imi = mMethodList.get(i); 1433 final String imiId = imi.getId(); 1434 if (imiId.equals(curInputMethodId)) { 1435 curIm = imi; 1436 } 1437 1438 int change = isPackageDisappearing(imi.getPackageName()); 1439 if (isPackageModified(imi.getPackageName())) { 1440 mAdditionalSubtypeMap.remove(imi.getId()); 1441 AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap, 1442 mSettings.getCurrentUserId()); 1443 } 1444 if (change == PACKAGE_TEMPORARY_CHANGE 1445 || change == PACKAGE_PERMANENT_CHANGE) { 1446 Slog.i(TAG, "Input method uninstalled, disabling: " 1447 + imi.getComponent()); 1448 setInputMethodEnabledLocked(imi.getId(), false); 1449 } 1450 } 1451 } 1452 1453 buildInputMethodListLocked(false /* resetDefaultEnabledIme */); 1454 1455 boolean changed = false; 1456 1457 if (curIm != null) { 1458 int change = isPackageDisappearing(curIm.getPackageName()); 1459 if (change == PACKAGE_TEMPORARY_CHANGE 1460 || change == PACKAGE_PERMANENT_CHANGE) { 1461 ServiceInfo si = null; 1462 try { 1463 si = mIPackageManager.getServiceInfo( 1464 curIm.getComponent(), 0, mSettings.getCurrentUserId()); 1465 } catch (RemoteException ex) { 1466 } 1467 if (si == null) { 1468 // Uh oh, current input method is no longer around! 1469 // Pick another one... 1470 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 1471 updateSystemUiLocked(0 /* vis */, mBackDisposition); 1472 if (!chooseNewDefaultIMELocked()) { 1473 changed = true; 1474 curIm = null; 1475 Slog.i(TAG, "Unsetting current input method"); 1476 resetSelectedInputMethodAndSubtypeLocked(""); 1477 } 1478 } 1479 } 1480 } 1481 1482 if (curIm == null) { 1483 // We currently don't have a default input method... is 1484 // one now available? 1485 changed = chooseNewDefaultIMELocked(); 1486 } else if (!changed && isPackageModified(curIm.getPackageName())) { 1487 // Even if the current input method is still available, mCurrentSubtype could 1488 // be obsolete when the package is modified in practice. 1489 changed = true; 1490 } 1491 1492 if (changed) { 1493 updateFromSettingsLocked(false); 1494 } 1495 } 1496 } 1497 } 1498 1499 private static final class MethodCallback extends IInputSessionCallback.Stub { 1500 private final InputMethodManagerService mParentIMMS; 1501 private final IInputMethod mMethod; 1502 private final InputChannel mChannel; 1503 MethodCallback(InputMethodManagerService imms, IInputMethod method, InputChannel channel)1504 MethodCallback(InputMethodManagerService imms, IInputMethod method, 1505 InputChannel channel) { 1506 mParentIMMS = imms; 1507 mMethod = method; 1508 mChannel = channel; 1509 } 1510 1511 @Override sessionCreated(IInputMethodSession session)1512 public void sessionCreated(IInputMethodSession session) { 1513 long ident = Binder.clearCallingIdentity(); 1514 try { 1515 mParentIMMS.onSessionCreated(mMethod, session, mChannel); 1516 } finally { 1517 Binder.restoreCallingIdentity(ident); 1518 } 1519 } 1520 } 1521 1522 private class HardKeyboardListener 1523 implements WindowManagerInternal.OnHardKeyboardStatusChangeListener { 1524 @Override onHardKeyboardStatusChange(boolean available)1525 public void onHardKeyboardStatusChange(boolean available) { 1526 mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED, 1527 available ? 1 : 0)); 1528 } 1529 handleHardKeyboardStatusChange(boolean available)1530 public void handleHardKeyboardStatusChange(boolean available) { 1531 if (DEBUG) { 1532 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available); 1533 } 1534 synchronized(mMethodMap) { 1535 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null 1536 && mSwitchingDialog.isShowing()) { 1537 mSwitchingDialogTitleView.findViewById( 1538 com.android.internal.R.id.hard_keyboard_section).setVisibility( 1539 available ? View.VISIBLE : View.GONE); 1540 } 1541 } 1542 } 1543 } 1544 1545 private static final class UserSwitchHandlerTask implements Runnable { 1546 final InputMethodManagerService mService; 1547 1548 @UserIdInt 1549 final int mToUserId; 1550 1551 @Nullable 1552 IInputMethodClient mClientToBeReset; 1553 UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, @Nullable IInputMethodClient clientToBeReset)1554 UserSwitchHandlerTask(InputMethodManagerService service, @UserIdInt int toUserId, 1555 @Nullable IInputMethodClient clientToBeReset) { 1556 mService = service; 1557 mToUserId = toUserId; 1558 mClientToBeReset = clientToBeReset; 1559 } 1560 1561 @Override run()1562 public void run() { 1563 synchronized (mService.mMethodMap) { 1564 if (mService.mUserSwitchHandlerTask != this) { 1565 // This task was already canceled before it is handled here. So do nothing. 1566 return; 1567 } 1568 mService.switchUserOnHandlerLocked(mService.mUserSwitchHandlerTask.mToUserId, 1569 mClientToBeReset); 1570 mService.mUserSwitchHandlerTask = null; 1571 } 1572 } 1573 } 1574 1575 /** 1576 * When non-{@code null}, this represents pending user-switch task, which is to be executed as 1577 * a handler callback. This needs to be set and unset only within the lock. 1578 */ 1579 @Nullable 1580 @GuardedBy("mMethodMap") 1581 private UserSwitchHandlerTask mUserSwitchHandlerTask; 1582 1583 public static final class Lifecycle extends SystemService { 1584 private InputMethodManagerService mService; 1585 Lifecycle(Context context)1586 public Lifecycle(Context context) { 1587 super(context); 1588 mService = new InputMethodManagerService(context); 1589 } 1590 1591 @Override onStart()1592 public void onStart() { 1593 LocalServices.addService(InputMethodManagerInternal.class, 1594 new LocalServiceImpl(mService)); 1595 publishBinderService(Context.INPUT_METHOD_SERVICE, mService); 1596 } 1597 1598 @Override onSwitchUser(@serIdInt int userHandle)1599 public void onSwitchUser(@UserIdInt int userHandle) { 1600 // Called on ActivityManager thread. 1601 synchronized (mService.mMethodMap) { 1602 mService.scheduleSwitchUserTaskLocked(userHandle, null /* clientToBeReset */); 1603 } 1604 } 1605 1606 @Override onBootPhase(int phase)1607 public void onBootPhase(int phase) { 1608 // Called on ActivityManager thread. 1609 // TODO: Dispatch this to a worker thread as needed. 1610 if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) { 1611 StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager 1612 .getService(Context.STATUS_BAR_SERVICE); 1613 mService.systemRunning(statusBarService); 1614 } 1615 } 1616 1617 @Override onUnlockUser(final @UserIdInt int userHandle)1618 public void onUnlockUser(final @UserIdInt int userHandle) { 1619 // Called on ActivityManager thread. 1620 mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER, 1621 userHandle /* arg1 */, 0 /* arg2 */)); 1622 } 1623 } 1624 onUnlockUser(@serIdInt int userId)1625 void onUnlockUser(@UserIdInt int userId) { 1626 synchronized(mMethodMap) { 1627 final int currentUserId = mSettings.getCurrentUserId(); 1628 if (DEBUG) { 1629 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId); 1630 } 1631 if (userId != currentUserId) { 1632 return; 1633 } 1634 mSettings.switchCurrentUser(currentUserId, !mSystemReady); 1635 if (mSystemReady) { 1636 // We need to rebuild IMEs. 1637 buildInputMethodListLocked(false /* resetDefaultEnabledIme */); 1638 updateInputMethodsFromSettingsLocked(true /* enabledChanged */); 1639 } 1640 } 1641 } 1642 1643 @GuardedBy("mMethodMap") scheduleSwitchUserTaskLocked(@serIdInt int userId, @Nullable IInputMethodClient clientToBeReset)1644 void scheduleSwitchUserTaskLocked(@UserIdInt int userId, 1645 @Nullable IInputMethodClient clientToBeReset) { 1646 if (mUserSwitchHandlerTask != null) { 1647 if (mUserSwitchHandlerTask.mToUserId == userId) { 1648 mUserSwitchHandlerTask.mClientToBeReset = clientToBeReset; 1649 return; 1650 } 1651 mHandler.removeCallbacks(mUserSwitchHandlerTask); 1652 } 1653 final UserSwitchHandlerTask task = new UserSwitchHandlerTask(this, userId, 1654 clientToBeReset); 1655 mUserSwitchHandlerTask = task; 1656 mHandler.post(task); 1657 } 1658 InputMethodManagerService(Context context)1659 public InputMethodManagerService(Context context) { 1660 mIPackageManager = AppGlobals.getPackageManager(); 1661 mContext = context; 1662 mRes = context.getResources(); 1663 mHandler = new Handler(this); 1664 // Note: SettingsObserver doesn't register observers in its constructor. 1665 mSettingsObserver = new SettingsObserver(mHandler); 1666 mIWindowManager = IWindowManager.Stub.asInterface( 1667 ServiceManager.getService(Context.WINDOW_SERVICE)); 1668 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class); 1669 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class); 1670 mInputManagerInternal = LocalServices.getService(InputManagerInternal.class); 1671 mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class); 1672 mImeDisplayValidator = displayId -> mWindowManagerInternal.shouldShowIme(displayId); 1673 mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() { 1674 @Override 1675 public void executeMessage(Message msg) { 1676 handleMessage(msg); 1677 } 1678 }, true /*asyncHandler*/); 1679 mAppOpsManager = mContext.getSystemService(AppOpsManager.class); 1680 mUserManager = mContext.getSystemService(UserManager.class); 1681 mUserManagerInternal = LocalServices.getService(UserManagerInternal.class); 1682 mHardKeyboardListener = new HardKeyboardListener(); 1683 mHasFeature = context.getPackageManager().hasSystemFeature( 1684 PackageManager.FEATURE_INPUT_METHODS); 1685 mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime); 1686 mIsLowRam = ActivityManager.isLowRamDeviceStatic(); 1687 1688 Bundle extras = new Bundle(); 1689 extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true); 1690 @ColorInt final int accentColor = mContext.getColor( 1691 com.android.internal.R.color.system_notification_accent_color); 1692 mImeSwitcherNotification = 1693 new Notification.Builder(mContext, SystemNotificationChannels.VIRTUAL_KEYBOARD) 1694 .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default) 1695 .setWhen(0) 1696 .setOngoing(true) 1697 .addExtras(extras) 1698 .setCategory(Notification.CATEGORY_SYSTEM) 1699 .setColor(accentColor); 1700 1701 Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER) 1702 .setPackage(mContext.getPackageName()); 1703 mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 1704 1705 mShowOngoingImeSwitcherForPhones = false; 1706 1707 mNotificationShown = false; 1708 int userId = 0; 1709 try { 1710 userId = ActivityManager.getService().getCurrentUser().id; 1711 } catch (RemoteException e) { 1712 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); 1713 } 1714 1715 mLastSwitchUserId = userId; 1716 1717 // mSettings should be created before buildInputMethodListLocked 1718 mSettings = new InputMethodSettings( 1719 mRes, context.getContentResolver(), mMethodMap, userId, !mSystemReady); 1720 1721 updateCurrentProfileIds(); 1722 AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId); 1723 mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked( 1724 mSettings, context); 1725 } 1726 resetDefaultImeLocked(Context context)1727 private void resetDefaultImeLocked(Context context) { 1728 // Do not reset the default (current) IME when it is a 3rd-party IME 1729 if (mCurMethodId != null && !mMethodMap.get(mCurMethodId).isSystem()) { 1730 return; 1731 } 1732 final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes( 1733 context, mSettings.getEnabledInputMethodListLocked()); 1734 if (suitableImes.isEmpty()) { 1735 Slog.i(TAG, "No default found"); 1736 return; 1737 } 1738 final InputMethodInfo defIm = suitableImes.get(0); 1739 if (DEBUG) { 1740 Slog.i(TAG, "Default found, using " + defIm.getId()); 1741 } 1742 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); 1743 } 1744 1745 @GuardedBy("mMethodMap") switchUserOnHandlerLocked(@serIdInt int newUserId, IInputMethodClient clientToBeReset)1746 private void switchUserOnHandlerLocked(@UserIdInt int newUserId, 1747 IInputMethodClient clientToBeReset) { 1748 if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId 1749 + " currentUserId=" + mSettings.getCurrentUserId()); 1750 1751 // ContentObserver should be registered again when the user is changed 1752 mSettingsObserver.registerContentObserverLocked(newUserId); 1753 1754 // If the system is not ready or the device is not yed unlocked by the user, then we use 1755 // copy-on-write settings. 1756 final boolean useCopyOnWriteSettings = 1757 !mSystemReady || !mUserManagerInternal.isUserUnlockingOrUnlocked(newUserId); 1758 mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings); 1759 updateCurrentProfileIds(); 1760 // Additional subtypes should be reset when the user is changed 1761 AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, newUserId); 1762 final String defaultImiId = mSettings.getSelectedInputMethod(); 1763 1764 if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId 1765 + " defaultImiId=" + defaultImiId); 1766 1767 // For secondary users, the list of enabled IMEs may not have been updated since the 1768 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may 1769 // not be empty even if the IME has been uninstalled by the primary user. 1770 // Even in such cases, IMMS works fine because it will find the most applicable 1771 // IME for that user. 1772 final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); 1773 mLastSystemLocales = mRes.getConfiguration().getLocales(); 1774 1775 // TODO: Is it really possible that switchUserLocked() happens before system ready? 1776 if (mSystemReady) { 1777 hideCurrentInputLocked( 1778 mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_SWITCH_USER); 1779 1780 resetCurrentMethodAndClient(UnbindReason.SWITCH_USER); 1781 buildInputMethodListLocked(initialUserSwitch); 1782 if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) { 1783 // This is the first time of the user switch and 1784 // set the current ime to the proper one. 1785 resetDefaultImeLocked(mContext); 1786 } 1787 updateFromSettingsLocked(true); 1788 } 1789 1790 if (initialUserSwitch) { 1791 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, 1792 mSettings.getEnabledInputMethodListLocked(), newUserId, 1793 mContext.getBasePackageName()); 1794 } 1795 1796 if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId 1797 + " selectedIme=" + mSettings.getSelectedInputMethod()); 1798 1799 mLastSwitchUserId = newUserId; 1800 1801 if (mIsInteractive && clientToBeReset != null) { 1802 final ClientState cs = mClients.get(clientToBeReset.asBinder()); 1803 if (cs == null) { 1804 // The client is already gone. 1805 return; 1806 } 1807 try { 1808 cs.client.scheduleStartInputIfNecessary(mInFullscreenMode); 1809 } catch (RemoteException e) { 1810 } 1811 } 1812 } 1813 updateCurrentProfileIds()1814 void updateCurrentProfileIds() { 1815 mSettings.setCurrentProfileIds( 1816 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId())); 1817 } 1818 1819 @Override onTransact(int code, Parcel data, Parcel reply, int flags)1820 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 1821 throws RemoteException { 1822 try { 1823 return super.onTransact(code, data, reply, flags); 1824 } catch (RuntimeException e) { 1825 // The input method manager only throws security exceptions, so let's 1826 // log all others. 1827 if (!(e instanceof SecurityException)) { 1828 Slog.wtf(TAG, "Input Method Manager Crash", e); 1829 } 1830 throw e; 1831 } 1832 } 1833 systemRunning(StatusBarManagerService statusBar)1834 public void systemRunning(StatusBarManagerService statusBar) { 1835 synchronized (mMethodMap) { 1836 if (DEBUG) { 1837 Slog.d(TAG, "--- systemReady"); 1838 } 1839 if (!mSystemReady) { 1840 mSystemReady = true; 1841 mLastSystemLocales = mRes.getConfiguration().getLocales(); 1842 final int currentUserId = mSettings.getCurrentUserId(); 1843 mSettings.switchCurrentUser(currentUserId, 1844 !mUserManagerInternal.isUserUnlockingOrUnlocked(currentUserId)); 1845 mKeyguardManager = mContext.getSystemService(KeyguardManager.class); 1846 mNotificationManager = mContext.getSystemService(NotificationManager.class); 1847 mStatusBar = statusBar; 1848 if (mStatusBar != null) { 1849 mStatusBar.setIconVisibility(mSlotIme, false); 1850 } 1851 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 1852 mShowOngoingImeSwitcherForPhones = mRes.getBoolean( 1853 com.android.internal.R.bool.show_ongoing_ime_switcher); 1854 if (mShowOngoingImeSwitcherForPhones) { 1855 mWindowManagerInternal.setOnHardKeyboardStatusChangeListener( 1856 mHardKeyboardListener); 1857 } 1858 1859 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); 1860 mSettingsObserver.registerContentObserverLocked(currentUserId); 1861 1862 final IntentFilter broadcastFilterForSystemUser = new IntentFilter(); 1863 broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_ADDED); 1864 broadcastFilterForSystemUser.addAction(Intent.ACTION_USER_REMOVED); 1865 broadcastFilterForSystemUser.addAction(Intent.ACTION_LOCALE_CHANGED); 1866 broadcastFilterForSystemUser.addAction(ACTION_SHOW_INPUT_METHOD_PICKER); 1867 mContext.registerReceiver(new ImmsBroadcastReceiverForSystemUser(), 1868 broadcastFilterForSystemUser); 1869 1870 final IntentFilter broadcastFilterForAllUsers = new IntentFilter(); 1871 broadcastFilterForAllUsers.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 1872 mContext.registerReceiverAsUser(new ImmsBroadcastReceiverForAllUsers(), 1873 UserHandle.ALL, broadcastFilterForAllUsers, null, null); 1874 1875 final String defaultImiId = mSettings.getSelectedInputMethod(); 1876 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); 1877 buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */); 1878 updateFromSettingsLocked(true); 1879 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, 1880 mSettings.getEnabledInputMethodListLocked(), currentUserId, 1881 mContext.getBasePackageName()); 1882 } 1883 } 1884 } 1885 1886 // --------------------------------------------------------------------------------------- 1887 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either 1888 // 1) it comes from the system process 1889 // 2) the calling process' user id is identical to the current user id IMMS thinks. 1890 @GuardedBy("mMethodMap") calledFromValidUserLocked()1891 private boolean calledFromValidUserLocked() { 1892 final int uid = Binder.getCallingUid(); 1893 final int userId = UserHandle.getUserId(uid); 1894 if (DEBUG) { 1895 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " 1896 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID 1897 + " calling userId = " + userId + ", foreground user id = " 1898 + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid() 1899 + InputMethodUtils.getApiCallStack()); 1900 } 1901 if (uid == Process.SYSTEM_UID) { 1902 return true; 1903 } 1904 if (userId == mSettings.getCurrentUserId()) { 1905 return true; 1906 } 1907 1908 // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the 1909 // foreground user, not for the user of that process. Accordingly InputMethodManagerService 1910 // must not manage background users' states in any functions. 1911 // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded 1912 // by a token. 1913 if (mContext.checkCallingOrSelfPermission( 1914 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) 1915 == PackageManager.PERMISSION_GRANTED) { 1916 if (DEBUG) { 1917 Slog.d(TAG, "--- Access granted because the calling process has " 1918 + "the INTERACT_ACROSS_USERS_FULL permission"); 1919 } 1920 return true; 1921 } 1922 // TODO(b/34886274): The semantics of this verification is actually not well-defined. 1923 Slog.w(TAG, "--- IPC called from background users. Ignore. callers=" 1924 + Debug.getCallers(10)); 1925 return false; 1926 } 1927 1928 1929 /** 1930 * Returns true iff the caller is identified to be the current input method with the token. 1931 * @param token The window token given to the input method when it was started. 1932 * @return true if and only if non-null valid token is specified. 1933 */ 1934 @GuardedBy("mMethodMap") calledWithValidTokenLocked(@onNull IBinder token)1935 private boolean calledWithValidTokenLocked(@NonNull IBinder token) { 1936 if (token == null) { 1937 throw new InvalidParameterException("token must not be null."); 1938 } 1939 if (token != mCurToken) { 1940 Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token." 1941 + " uid:" + Binder.getCallingUid() + " token:" + token); 1942 return false; 1943 } 1944 return true; 1945 } 1946 1947 @GuardedBy("mMethodMap") bindCurrentInputMethodServiceLocked( Intent service, ServiceConnection conn, int flags)1948 private boolean bindCurrentInputMethodServiceLocked( 1949 Intent service, ServiceConnection conn, int flags) { 1950 if (service == null || conn == null) { 1951 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); 1952 return false; 1953 } 1954 return mContext.bindServiceAsUser(service, conn, flags, 1955 new UserHandle(mSettings.getCurrentUserId())); 1956 } 1957 1958 @Override getInputMethodList(@serIdInt int userId)1959 public List<InputMethodInfo> getInputMethodList(@UserIdInt int userId) { 1960 if (UserHandle.getCallingUserId() != userId) { 1961 mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1962 } 1963 synchronized (mMethodMap) { 1964 final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, 1965 mSettings.getCurrentUserId(), null); 1966 if (resolvedUserIds.length != 1) { 1967 return Collections.emptyList(); 1968 } 1969 final long ident = Binder.clearCallingIdentity(); 1970 try { 1971 return getInputMethodListLocked(resolvedUserIds[0]); 1972 } finally { 1973 Binder.restoreCallingIdentity(ident); 1974 } 1975 } 1976 } 1977 1978 @Override getEnabledInputMethodList(@serIdInt int userId)1979 public List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) { 1980 if (UserHandle.getCallingUserId() != userId) { 1981 mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, null); 1982 } 1983 synchronized (mMethodMap) { 1984 final int[] resolvedUserIds = InputMethodUtils.resolveUserId(userId, 1985 mSettings.getCurrentUserId(), null); 1986 if (resolvedUserIds.length != 1) { 1987 return Collections.emptyList(); 1988 } 1989 final long ident = Binder.clearCallingIdentity(); 1990 try { 1991 return getEnabledInputMethodListLocked(resolvedUserIds[0]); 1992 } finally { 1993 Binder.restoreCallingIdentity(ident); 1994 } 1995 } 1996 } 1997 1998 @GuardedBy("mMethodMap") getInputMethodListLocked(@serIdInt int userId)1999 private List<InputMethodInfo> getInputMethodListLocked(@UserIdInt int userId) { 2000 final ArrayList<InputMethodInfo> methodList; 2001 if (userId == mSettings.getCurrentUserId()) { 2002 // Create a copy. 2003 methodList = new ArrayList<>(mMethodList); 2004 } else { 2005 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 2006 methodList = new ArrayList<>(); 2007 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 2008 new ArrayMap<>(); 2009 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 2010 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, 2011 methodList); 2012 } 2013 return methodList; 2014 } 2015 2016 @GuardedBy("mMethodMap") getEnabledInputMethodListLocked(@serIdInt int userId)2017 private List<InputMethodInfo> getEnabledInputMethodListLocked(@UserIdInt int userId) { 2018 if (userId == mSettings.getCurrentUserId()) { 2019 return mSettings.getEnabledInputMethodListLocked(); 2020 } 2021 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 2022 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); 2023 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 2024 new ArrayMap<>(); 2025 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 2026 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, 2027 methodList); 2028 final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), 2029 mContext.getContentResolver(), methodMap, userId, true); 2030 return settings.getEnabledInputMethodListLocked(); 2031 } 2032 2033 @GuardedBy("mMethodMap") onCreateInlineSuggestionsRequestLocked(@serIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback)2034 private void onCreateInlineSuggestionsRequestLocked(@UserIdInt int userId, 2035 InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback) { 2036 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2037 try { 2038 if (userId == mSettings.getCurrentUserId() && imi != null 2039 && imi.isInlineSuggestionsEnabled() && mCurMethod != null) { 2040 executeOrSendMessage(mCurMethod, 2041 mCaller.obtainMessageOOO(MSG_INLINE_SUGGESTIONS_REQUEST, mCurMethod, 2042 requestInfo, new InlineSuggestionsRequestCallbackDecorator(callback, 2043 imi.getPackageName(), mCurTokenDisplayId, mCurToken, 2044 this))); 2045 } else { 2046 callback.onInlineSuggestionsUnsupported(); 2047 } 2048 } catch (RemoteException e) { 2049 Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); 2050 } 2051 } 2052 2053 /** 2054 * The decorator which validates the host package name in the 2055 * {@link InlineSuggestionsRequest} argument to make sure it matches the IME package name. 2056 */ 2057 private static final class InlineSuggestionsRequestCallbackDecorator 2058 extends IInlineSuggestionsRequestCallback.Stub { 2059 @NonNull 2060 private final IInlineSuggestionsRequestCallback mCallback; 2061 @NonNull 2062 private final String mImePackageName; 2063 2064 private final int mImeDisplayId; 2065 2066 @NonNull 2067 private final IBinder mImeToken; 2068 @NonNull 2069 private final InputMethodManagerService mImms; 2070 InlineSuggestionsRequestCallbackDecorator( @onNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName, int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms)2071 InlineSuggestionsRequestCallbackDecorator( 2072 @NonNull IInlineSuggestionsRequestCallback callback, @NonNull String imePackageName, 2073 int displayId, @NonNull IBinder imeToken, @NonNull InputMethodManagerService imms) { 2074 mCallback = callback; 2075 mImePackageName = imePackageName; 2076 mImeDisplayId = displayId; 2077 mImeToken = imeToken; 2078 mImms = imms; 2079 } 2080 2081 @Override onInlineSuggestionsUnsupported()2082 public void onInlineSuggestionsUnsupported() throws RemoteException { 2083 mCallback.onInlineSuggestionsUnsupported(); 2084 } 2085 2086 @Override onInlineSuggestionsRequest(InlineSuggestionsRequest request, IInlineSuggestionsResponseCallback callback)2087 public void onInlineSuggestionsRequest(InlineSuggestionsRequest request, 2088 IInlineSuggestionsResponseCallback callback) 2089 throws RemoteException { 2090 if (!mImePackageName.equals(request.getHostPackageName())) { 2091 throw new SecurityException( 2092 "Host package name in the provide request=[" + request.getHostPackageName() 2093 + "] doesn't match the IME package name=[" + mImePackageName 2094 + "]."); 2095 } 2096 request.setHostDisplayId(mImeDisplayId); 2097 mImms.setCurHostInputToken(mImeToken, request.getHostInputToken()); 2098 mCallback.onInlineSuggestionsRequest(request, callback); 2099 } 2100 2101 @Override onInputMethodStartInput(AutofillId imeFieldId)2102 public void onInputMethodStartInput(AutofillId imeFieldId) throws RemoteException { 2103 mCallback.onInputMethodStartInput(imeFieldId); 2104 } 2105 2106 @Override onInputMethodShowInputRequested(boolean requestResult)2107 public void onInputMethodShowInputRequested(boolean requestResult) throws RemoteException { 2108 mCallback.onInputMethodShowInputRequested(requestResult); 2109 } 2110 2111 @Override onInputMethodStartInputView()2112 public void onInputMethodStartInputView() throws RemoteException { 2113 mCallback.onInputMethodStartInputView(); 2114 } 2115 2116 @Override onInputMethodFinishInputView()2117 public void onInputMethodFinishInputView() throws RemoteException { 2118 mCallback.onInputMethodFinishInputView(); 2119 } 2120 2121 @Override onInputMethodFinishInput()2122 public void onInputMethodFinishInput() throws RemoteException { 2123 mCallback.onInputMethodFinishInput(); 2124 } 2125 2126 @Override onInlineSuggestionsSessionInvalidated()2127 public void onInlineSuggestionsSessionInvalidated() throws RemoteException { 2128 mCallback.onInlineSuggestionsSessionInvalidated(); 2129 } 2130 } 2131 2132 /** 2133 * Sets current host input token. 2134 * 2135 * @param callerImeToken the token has been made for the current active input method 2136 * @param hostInputToken the host input token of the current active input method 2137 */ setCurHostInputToken(@onNull IBinder callerImeToken, @Nullable IBinder hostInputToken)2138 void setCurHostInputToken(@NonNull IBinder callerImeToken, @Nullable IBinder hostInputToken) { 2139 synchronized (mMethodMap) { 2140 if (!calledWithValidTokenLocked(callerImeToken)) { 2141 return; 2142 } 2143 mCurHostInputToken = hostInputToken; 2144 } 2145 } 2146 2147 /** 2148 * @param imiId if null, returns enabled subtypes for the current imi 2149 * @return enabled subtypes of the specified imi 2150 */ 2151 @Override getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes)2152 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, 2153 boolean allowsImplicitlySelectedSubtypes) { 2154 final int callingUserId = UserHandle.getCallingUserId(); 2155 synchronized (mMethodMap) { 2156 final int[] resolvedUserIds = InputMethodUtils.resolveUserId(callingUserId, 2157 mSettings.getCurrentUserId(), null); 2158 if (resolvedUserIds.length != 1) { 2159 return Collections.emptyList(); 2160 } 2161 final long ident = Binder.clearCallingIdentity(); 2162 try { 2163 return getEnabledInputMethodSubtypeListLocked(imiId, 2164 allowsImplicitlySelectedSubtypes, resolvedUserIds[0]); 2165 } finally { 2166 Binder.restoreCallingIdentity(ident); 2167 } 2168 } 2169 } 2170 2171 @GuardedBy("mMethodMap") getEnabledInputMethodSubtypeListLocked(String imiId, boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId)2172 private List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(String imiId, 2173 boolean allowsImplicitlySelectedSubtypes, @UserIdInt int userId) { 2174 if (userId == mSettings.getCurrentUserId()) { 2175 final InputMethodInfo imi; 2176 if (imiId == null && mCurMethodId != null) { 2177 imi = mMethodMap.get(mCurMethodId); 2178 } else { 2179 imi = mMethodMap.get(imiId); 2180 } 2181 if (imi == null) { 2182 return Collections.emptyList(); 2183 } 2184 return mSettings.getEnabledInputMethodSubtypeListLocked( 2185 mContext, imi, allowsImplicitlySelectedSubtypes); 2186 } 2187 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 2188 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); 2189 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 2190 new ArrayMap<>(); 2191 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 2192 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, methodMap, 2193 methodList); 2194 final InputMethodInfo imi = methodMap.get(imiId); 2195 if (imi == null) { 2196 return Collections.emptyList(); 2197 } 2198 final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), 2199 mContext.getContentResolver(), methodMap, userId, true); 2200 return settings.getEnabledInputMethodSubtypeListLocked( 2201 mContext, imi, allowsImplicitlySelectedSubtypes); 2202 } 2203 2204 /** 2205 * Called by each application process as a preparation to start interacting with 2206 * {@link InputMethodManagerService}. 2207 * 2208 * <p>As a general principle, IPCs from the application process that take 2209 * {@link IInputMethodClient} will be rejected without this step.</p> 2210 * 2211 * @param client {@link android.os.Binder} proxy that is associated with the singleton instance 2212 * of {@link android.view.inputmethod.InputMethodManager} that runs on the client 2213 * process 2214 * @param inputContext communication channel for the dummy 2215 * {@link android.view.inputmethod.InputConnection} 2216 * @param selfReportedDisplayId self-reported display ID to which the client is associated. 2217 * Whether the client is still allowed to access to this display 2218 * or not needs to be evaluated every time the client interacts 2219 * with the display 2220 */ 2221 @Override addClient(IInputMethodClient client, IInputContext inputContext, int selfReportedDisplayId)2222 public void addClient(IInputMethodClient client, IInputContext inputContext, 2223 int selfReportedDisplayId) { 2224 // Here there are two scenarios where this method is called: 2225 // A. IMM is being instantiated in a different process and this is an IPC from that process 2226 // B. IMM is being instantiated in the same process but Binder.clearCallingIdentity() is 2227 // called in the caller side if necessary. 2228 // In either case the following UID/PID should be the ones where InputMethodManager is 2229 // actually running. 2230 final int callerUid = Binder.getCallingUid(); 2231 final int callerPid = Binder.getCallingPid(); 2232 synchronized (mMethodMap) { 2233 // TODO: Optimize this linear search. 2234 final int numClients = mClients.size(); 2235 for (int i = 0; i < numClients; ++i) { 2236 final ClientState state = mClients.valueAt(i); 2237 if (state.uid == callerUid && state.pid == callerPid 2238 && state.selfReportedDisplayId == selfReportedDisplayId) { 2239 throw new SecurityException("uid=" + callerUid + "/pid=" + callerPid 2240 + "/displayId=" + selfReportedDisplayId + " is already registered."); 2241 } 2242 } 2243 final ClientDeathRecipient deathRecipient = new ClientDeathRecipient(this, client); 2244 try { 2245 client.asBinder().linkToDeath(deathRecipient, 0); 2246 } catch (RemoteException e) { 2247 throw new IllegalStateException(e); 2248 } 2249 // We cannot fully avoid race conditions where the client UID already lost the access to 2250 // the given self-reported display ID, even if the client is not maliciously reporting 2251 // a fake display ID. Unconditionally returning SecurityException just because the 2252 // client doesn't pass display ID verification can cause many test failures hence not an 2253 // option right now. At the same time 2254 // context.getSystemService(InputMethodManager.class) 2255 // is expected to return a valid non-null instance at any time if we do not choose to 2256 // have the client crash. Thus we do not verify the display ID at all here. Instead we 2257 // later check the display ID every time the client needs to interact with the specified 2258 // display. 2259 mClients.put(client.asBinder(), new ClientState(client, inputContext, callerUid, 2260 callerPid, selfReportedDisplayId, deathRecipient)); 2261 } 2262 } 2263 removeClient(IInputMethodClient client)2264 void removeClient(IInputMethodClient client) { 2265 synchronized (mMethodMap) { 2266 ClientState cs = mClients.remove(client.asBinder()); 2267 if (cs != null) { 2268 client.asBinder().unlinkToDeath(cs.clientDeathRecipient, 0); 2269 clearClientSessionLocked(cs); 2270 2271 final int numItems = mActivityViewDisplayIdToParentMap.size(); 2272 for (int i = numItems - 1; i >= 0; --i) { 2273 final ActivityViewInfo info = mActivityViewDisplayIdToParentMap.valueAt(i); 2274 if (info.mParentClient == cs) { 2275 mActivityViewDisplayIdToParentMap.removeAt(i); 2276 } 2277 } 2278 2279 if (mCurClient == cs) { 2280 if (mBoundToMethod) { 2281 mBoundToMethod = false; 2282 if (mCurMethod != null) { 2283 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 2284 MSG_UNBIND_INPUT, mCurMethod)); 2285 } 2286 } 2287 mCurClient = null; 2288 mCurActivityViewToScreenMatrix = null; 2289 } 2290 if (mCurFocusedWindowClient == cs) { 2291 mCurFocusedWindowClient = null; 2292 } 2293 } 2294 } 2295 } 2296 executeOrSendMessage(IInterface target, Message msg)2297 void executeOrSendMessage(IInterface target, Message msg) { 2298 if (target.asBinder() instanceof Binder) { 2299 mCaller.sendMessage(msg); 2300 } else { 2301 handleMessage(msg); 2302 msg.recycle(); 2303 } 2304 } 2305 unbindCurrentClientLocked(@nbindReason int unbindClientReason)2306 void unbindCurrentClientLocked(@UnbindReason int unbindClientReason) { 2307 if (mCurClient != null) { 2308 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client=" 2309 + mCurClient.client.asBinder()); 2310 if (mBoundToMethod) { 2311 mBoundToMethod = false; 2312 if (mCurMethod != null) { 2313 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 2314 MSG_UNBIND_INPUT, mCurMethod)); 2315 } 2316 } 2317 2318 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( 2319 MSG_SET_ACTIVE, 0, 0, mCurClient)); 2320 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( 2321 MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client)); 2322 mCurClient.sessionRequested = false; 2323 mCurClient = null; 2324 mCurActivityViewToScreenMatrix = null; 2325 2326 hideInputMethodMenuLocked(); 2327 } 2328 } 2329 getImeShowFlags()2330 private int getImeShowFlags() { 2331 int flags = 0; 2332 if (mShowForced) { 2333 flags |= InputMethod.SHOW_FORCED 2334 | InputMethod.SHOW_EXPLICIT; 2335 } else if (mShowExplicitlyRequested) { 2336 flags |= InputMethod.SHOW_EXPLICIT; 2337 } 2338 return flags; 2339 } 2340 getAppShowFlags()2341 private int getAppShowFlags() { 2342 int flags = 0; 2343 if (mShowForced) { 2344 flags |= InputMethodManager.SHOW_FORCED; 2345 } else if (!mShowExplicitlyRequested) { 2346 flags |= InputMethodManager.SHOW_IMPLICIT; 2347 } 2348 return flags; 2349 } 2350 2351 @GuardedBy("mMethodMap") 2352 @NonNull attachNewInputLocked(@tartInputReason int startInputReason, boolean initial)2353 InputBindResult attachNewInputLocked(@StartInputReason int startInputReason, boolean initial) { 2354 if (!mBoundToMethod) { 2355 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 2356 MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); 2357 mBoundToMethod = true; 2358 } 2359 2360 final Binder startInputToken = new Binder(); 2361 final StartInputInfo info = new StartInputInfo(mSettings.getCurrentUserId(), mCurToken, 2362 mCurTokenDisplayId, mCurId, startInputReason, !initial, 2363 UserHandle.getUserId(mCurClient.uid), mCurClient.selfReportedDisplayId, 2364 mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode, mCurSeq); 2365 mImeTargetWindowMap.put(startInputToken, mCurFocusedWindow); 2366 mStartInputHistory.addEntry(info); 2367 2368 // Seems that PackageManagerInternal#grantImplicitAccess() doesn't handle cross-user 2369 // implicit visibility (e.g. IME[user=10] -> App[user=0]) thus we do this only for the 2370 // same-user scenarios. 2371 // That said ignoring cross-user scenario will never affect IMEs that do not have 2372 // INTERACT_ACROSS_USERS(_FULL) permissions, which is actually almost always the case. 2373 if (mSettings.getCurrentUserId() == UserHandle.getUserId(mCurClient.uid)) { 2374 mPackageManagerInternal.grantImplicitAccess(mSettings.getCurrentUserId(), 2375 null /* intent */, UserHandle.getAppId(mCurMethodUid), mCurClient.uid, true); 2376 } 2377 2378 final SessionState session = mCurClient.curSession; 2379 executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO( 2380 MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */, 2381 startInputToken, session, mCurInputContext, mCurAttribute)); 2382 if (mShowRequested) { 2383 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); 2384 showCurrentInputLocked(mCurFocusedWindow, getAppShowFlags(), null, 2385 SoftInputShowHideReason.ATTACH_NEW_INPUT); 2386 } 2387 return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION, 2388 session.session, (session.channel != null ? session.channel.dup() : null), 2389 mCurId, mCurSeq, mCurActivityViewToScreenMatrix); 2390 } 2391 2392 @Nullable getActivityViewToScreenMatrixLocked(int clientDisplayId, int imeDisplayId)2393 private Matrix getActivityViewToScreenMatrixLocked(int clientDisplayId, int imeDisplayId) { 2394 if (clientDisplayId == imeDisplayId) { 2395 return null; 2396 } 2397 int displayId = clientDisplayId; 2398 Matrix matrix = null; 2399 while (true) { 2400 final ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(displayId); 2401 if (info == null) { 2402 return null; 2403 } 2404 if (matrix == null) { 2405 matrix = new Matrix(info.mMatrix); 2406 } else { 2407 matrix.postConcat(info.mMatrix); 2408 } 2409 if (info.mParentClient.selfReportedDisplayId == imeDisplayId) { 2410 return matrix; 2411 } 2412 displayId = info.mParentClient.selfReportedDisplayId; 2413 } 2414 } 2415 2416 @GuardedBy("mMethodMap") 2417 @NonNull startInputUncheckedLocked(@onNull ClientState cs, IInputContext inputContext, @MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute, @StartInputFlags int startInputFlags, @StartInputReason int startInputReason)2418 InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext, 2419 @MissingMethodFlags int missingMethods, @NonNull EditorInfo attribute, 2420 @StartInputFlags int startInputFlags, @StartInputReason int startInputReason) { 2421 // If no method is currently selected, do nothing. 2422 if (mCurMethodId == null) { 2423 return InputBindResult.NO_IME; 2424 } 2425 2426 if (!mSystemReady) { 2427 // If the system is not yet ready, we shouldn't be running third 2428 // party code. 2429 return new InputBindResult( 2430 InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY, 2431 null, null, mCurMethodId, mCurSeq, null); 2432 } 2433 2434 if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid, 2435 attribute.packageName)) { 2436 Slog.e(TAG, "Rejecting this client as it reported an invalid package name." 2437 + " uid=" + cs.uid + " package=" + attribute.packageName); 2438 return InputBindResult.INVALID_PACKAGE_NAME; 2439 } 2440 2441 if (!mWindowManagerInternal.isUidAllowedOnDisplay(cs.selfReportedDisplayId, cs.uid)) { 2442 // Wait, the client no longer has access to the display. 2443 return InputBindResult.INVALID_DISPLAY_ID; 2444 } 2445 // Compute the final shown display ID with validated cs.selfReportedDisplayId for this 2446 // session & other conditions. 2447 final int displayIdToShowIme = computeImeDisplayIdForTarget(cs.selfReportedDisplayId, 2448 mImeDisplayValidator); 2449 2450 if (mCurClient != cs) { 2451 // Was the keyguard locked when switching over to the new client? 2452 mCurClientInKeyguard = isKeyguardLocked(); 2453 // If the client is changing, we need to switch over to the new 2454 // one. 2455 unbindCurrentClientLocked(UnbindReason.SWITCH_CLIENT); 2456 if (DEBUG) Slog.v(TAG, "switching to client: client=" 2457 + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); 2458 2459 // If the screen is on, inform the new client it is active 2460 if (mIsInteractive) { 2461 executeOrSendMessage(cs.client, mCaller.obtainMessageIO(MSG_SET_ACTIVE, 1, cs)); 2462 } 2463 } 2464 2465 // Bump up the sequence for this client and attach it. 2466 mCurSeq++; 2467 if (mCurSeq <= 0) mCurSeq = 1; 2468 mCurClient = cs; 2469 mCurInputContext = inputContext; 2470 mCurActivityViewToScreenMatrix = 2471 getActivityViewToScreenMatrixLocked(cs.selfReportedDisplayId, displayIdToShowIme); 2472 if (cs.selfReportedDisplayId != displayIdToShowIme 2473 && mCurActivityViewToScreenMatrix == null) { 2474 // CursorAnchorInfo API does not work as-is for cross-display scenario. Pretend that 2475 // InputConnection#requestCursorUpdates() is not implemented in the application so that 2476 // IMEs will always receive false from this API. 2477 missingMethods |= MissingMethodFlags.REQUEST_CURSOR_UPDATES; 2478 } 2479 mCurInputContextMissingMethods = missingMethods; 2480 mCurAttribute = attribute; 2481 2482 // Check if the input method is changing. 2483 // We expect the caller has already verified that the client is allowed to access this 2484 // display ID. 2485 if (mCurId != null && mCurId.equals(mCurMethodId) 2486 && displayIdToShowIme == mCurTokenDisplayId) { 2487 if (cs.curSession != null) { 2488 // Fast case: if we are already connected to the input method, 2489 // then just return it. 2490 return attachNewInputLocked(startInputReason, 2491 (startInputFlags & StartInputFlags.INITIAL_CONNECTION) != 0); 2492 } 2493 if (mHaveConnection) { 2494 if (mCurMethod != null) { 2495 // Return to client, and we will get back with it when 2496 // we have had a session made for it. 2497 requestClientSessionLocked(cs); 2498 return new InputBindResult( 2499 InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION, 2500 null, null, mCurId, mCurSeq, null); 2501 } else if (SystemClock.uptimeMillis() 2502 < (mLastBindTime+TIME_TO_RECONNECT)) { 2503 // In this case we have connected to the service, but 2504 // don't yet have its interface. If it hasn't been too 2505 // long since we did the connection, we'll return to 2506 // the client and wait to get the service interface so 2507 // we can report back. If it has been too long, we want 2508 // to fall through so we can try a disconnect/reconnect 2509 // to see if we can get back in touch with the service. 2510 return new InputBindResult( 2511 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 2512 null, null, mCurId, mCurSeq, null); 2513 } else { 2514 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 2515 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0); 2516 } 2517 } 2518 } 2519 2520 InputMethodInfo info = mMethodMap.get(mCurMethodId); 2521 if (info == null) { 2522 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 2523 } 2524 2525 unbindCurrentMethodLocked(); 2526 2527 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); 2528 mCurIntent.setComponent(info.getComponent()); 2529 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 2530 com.android.internal.R.string.input_method_binding_label); 2531 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 2532 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); 2533 2534 if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) { 2535 mLastBindTime = SystemClock.uptimeMillis(); 2536 mHaveConnection = true; 2537 mCurId = info.getId(); 2538 mCurToken = new Binder(); 2539 mCurTokenDisplayId = displayIdToShowIme; 2540 try { 2541 if (DEBUG) { 2542 Slog.v(TAG, "Adding window token: " + mCurToken + " for display: " 2543 + mCurTokenDisplayId); 2544 } 2545 mIWindowManager.addWindowToken(mCurToken, LayoutParams.TYPE_INPUT_METHOD, 2546 mCurTokenDisplayId); 2547 } catch (RemoteException e) { 2548 } 2549 return new InputBindResult( 2550 InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING, 2551 null, null, mCurId, mCurSeq, null); 2552 } 2553 mCurIntent = null; 2554 Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent); 2555 return InputBindResult.IME_NOT_CONNECTED; 2556 } 2557 2558 @FunctionalInterface 2559 interface ImeDisplayValidator { displayCanShowIme(int displayId)2560 boolean displayCanShowIme(int displayId); 2561 } 2562 2563 /** 2564 * Find the display where the IME should be shown. 2565 * 2566 * @param displayId the ID of the display where the IME client target is. 2567 * @param checker instance of {@link ImeDisplayValidator} which is used for 2568 * checking display config to adjust the final target display. 2569 * @return The ID of the display where the IME should be shown. 2570 */ computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker)2571 static int computeImeDisplayIdForTarget(int displayId, @NonNull ImeDisplayValidator checker) { 2572 if (displayId == DEFAULT_DISPLAY || displayId == INVALID_DISPLAY) { 2573 return FALLBACK_DISPLAY_ID; 2574 } 2575 2576 // Show IME window on fallback display when the display doesn't support system decorations 2577 // or the display is virtual and isn't owned by system for security concern. 2578 return checker.displayCanShowIme(displayId) ? displayId : FALLBACK_DISPLAY_ID; 2579 } 2580 2581 @AnyThread scheduleNotifyImeUidToAudioService(int uid)2582 private void scheduleNotifyImeUidToAudioService(int uid) { 2583 mCaller.removeMessages(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE); 2584 mCaller.obtainMessageI(MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE, uid).sendToTarget(); 2585 } 2586 2587 @Override onServiceConnected(ComponentName name, IBinder service)2588 public void onServiceConnected(ComponentName name, IBinder service) { 2589 synchronized (mMethodMap) { 2590 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 2591 mCurMethod = IInputMethod.Stub.asInterface(service); 2592 final String curMethodPackage = mCurIntent.getComponent().getPackageName(); 2593 final int curMethodUid = mPackageManagerInternal.getPackageUidInternal( 2594 curMethodPackage, 0 /* flags */, mSettings.getCurrentUserId()); 2595 if (curMethodUid < 0) { 2596 Slog.e(TAG, "Failed to get UID for package=" + curMethodPackage); 2597 mCurMethodUid = Process.INVALID_UID; 2598 } else { 2599 mCurMethodUid = curMethodUid; 2600 } 2601 if (mCurToken == null) { 2602 Slog.w(TAG, "Service connected without a token!"); 2603 unbindCurrentMethodLocked(); 2604 return; 2605 } 2606 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 2607 // Dispatch display id for InputMethodService to update context display. 2608 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 2609 MSG_INITIALIZE_IME, mCurTokenDisplayId, mCurMethod, mCurToken)); 2610 scheduleNotifyImeUidToAudioService(mCurMethodUid); 2611 if (mCurClient != null) { 2612 clearClientSessionLocked(mCurClient); 2613 requestClientSessionLocked(mCurClient); 2614 } 2615 } 2616 } 2617 } 2618 onSessionCreated(IInputMethod method, IInputMethodSession session, InputChannel channel)2619 void onSessionCreated(IInputMethod method, IInputMethodSession session, 2620 InputChannel channel) { 2621 synchronized (mMethodMap) { 2622 if (mUserSwitchHandlerTask != null) { 2623 // We have a pending user-switching task so it's better to just ignore this session. 2624 channel.dispose(); 2625 return; 2626 } 2627 if (mCurMethod != null && method != null 2628 && mCurMethod.asBinder() == method.asBinder()) { 2629 if (mCurClient != null) { 2630 clearClientSessionLocked(mCurClient); 2631 mCurClient.curSession = new SessionState(mCurClient, 2632 method, session, channel); 2633 InputBindResult res = attachNewInputLocked( 2634 StartInputReason.SESSION_CREATED_BY_IME, true); 2635 if (res.method != null) { 2636 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 2637 MSG_BIND_CLIENT, mCurClient.client, res)); 2638 } 2639 return; 2640 } 2641 } 2642 } 2643 2644 // Session abandoned. Close its associated input channel. 2645 channel.dispose(); 2646 } 2647 unbindCurrentMethodLocked()2648 void unbindCurrentMethodLocked() { 2649 if (mVisibleBound) { 2650 mContext.unbindService(mVisibleConnection); 2651 mVisibleBound = false; 2652 } 2653 2654 if (mHaveConnection) { 2655 mContext.unbindService(this); 2656 mHaveConnection = false; 2657 } 2658 2659 if (mCurToken != null) { 2660 try { 2661 if (DEBUG) { 2662 Slog.v(TAG, "Removing window token: " + mCurToken + " for display: " 2663 + mCurTokenDisplayId); 2664 } 2665 mIWindowManager.removeWindowToken(mCurToken, mCurTokenDisplayId); 2666 } catch (RemoteException e) { 2667 } 2668 // Set IME window status as invisible when unbind current method. 2669 mImeWindowVis = 0; 2670 mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; 2671 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 2672 mCurToken = null; 2673 mCurTokenDisplayId = INVALID_DISPLAY; 2674 mCurHostInputToken = null; 2675 } 2676 2677 mCurId = null; 2678 clearCurMethodLocked(); 2679 } 2680 resetCurrentMethodAndClient(@nbindReason int unbindClientReason)2681 void resetCurrentMethodAndClient(@UnbindReason int unbindClientReason) { 2682 mCurMethodId = null; 2683 unbindCurrentMethodLocked(); 2684 unbindCurrentClientLocked(unbindClientReason); 2685 } 2686 requestClientSessionLocked(ClientState cs)2687 void requestClientSessionLocked(ClientState cs) { 2688 if (!cs.sessionRequested) { 2689 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); 2690 InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); 2691 cs.sessionRequested = true; 2692 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO( 2693 MSG_CREATE_SESSION, mCurMethod, channels[1], 2694 new MethodCallback(this, mCurMethod, channels[0]))); 2695 } 2696 } 2697 clearClientSessionLocked(ClientState cs)2698 void clearClientSessionLocked(ClientState cs) { 2699 finishSessionLocked(cs.curSession); 2700 cs.curSession = null; 2701 cs.sessionRequested = false; 2702 } 2703 finishSessionLocked(SessionState sessionState)2704 private void finishSessionLocked(SessionState sessionState) { 2705 if (sessionState != null) { 2706 if (sessionState.session != null) { 2707 try { 2708 sessionState.session.finishSession(); 2709 } catch (RemoteException e) { 2710 Slog.w(TAG, "Session failed to close due to remote exception", e); 2711 updateSystemUiLocked(0 /* vis */, mBackDisposition); 2712 } 2713 sessionState.session = null; 2714 } 2715 if (sessionState.channel != null) { 2716 sessionState.channel.dispose(); 2717 sessionState.channel = null; 2718 } 2719 } 2720 } 2721 clearCurMethodLocked()2722 void clearCurMethodLocked() { 2723 if (mCurMethod != null) { 2724 final int numClients = mClients.size(); 2725 for (int i = 0; i < numClients; ++i) { 2726 clearClientSessionLocked(mClients.valueAt(i)); 2727 } 2728 2729 finishSessionLocked(mEnabledSession); 2730 mEnabledSession = null; 2731 mCurMethod = null; 2732 mCurMethodUid = Process.INVALID_UID; 2733 scheduleNotifyImeUidToAudioService(mCurMethodUid); 2734 } 2735 if (mStatusBar != null) { 2736 mStatusBar.setIconVisibility(mSlotIme, false); 2737 } 2738 mInFullscreenMode = false; 2739 } 2740 2741 @Override onServiceDisconnected(ComponentName name)2742 public void onServiceDisconnected(ComponentName name) { 2743 // Note that mContext.unbindService(this) does not trigger this. Hence if we are here the 2744 // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed), 2745 // which is irregular but can eventually happen for everyone just by continuing using the 2746 // device. Thus it is important to make sure that all the internal states are properly 2747 // refreshed when this method is called back. Running 2748 // adb install -r <APK that implements the current IME> 2749 // would be a good way to trigger such a situation. 2750 synchronized (mMethodMap) { 2751 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 2752 + " mCurIntent=" + mCurIntent); 2753 if (mCurMethod != null && mCurIntent != null 2754 && name.equals(mCurIntent.getComponent())) { 2755 clearCurMethodLocked(); 2756 // We consider this to be a new bind attempt, since the system 2757 // should now try to restart the service for us. 2758 mLastBindTime = SystemClock.uptimeMillis(); 2759 mShowRequested = mInputShown; 2760 mInputShown = false; 2761 unbindCurrentClientLocked(UnbindReason.DISCONNECT_IME); 2762 } 2763 } 2764 } 2765 2766 @BinderThread updateStatusIcon(@onNull IBinder token, String packageName, @DrawableRes int iconId)2767 private void updateStatusIcon(@NonNull IBinder token, String packageName, 2768 @DrawableRes int iconId) { 2769 synchronized (mMethodMap) { 2770 if (!calledWithValidTokenLocked(token)) { 2771 return; 2772 } 2773 final long ident = Binder.clearCallingIdentity(); 2774 try { 2775 if (iconId == 0) { 2776 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 2777 if (mStatusBar != null) { 2778 mStatusBar.setIconVisibility(mSlotIme, false); 2779 } 2780 } else if (packageName != null) { 2781 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 2782 CharSequence contentDescription = null; 2783 try { 2784 // Use PackageManager to load label 2785 final PackageManager packageManager = mContext.getPackageManager(); 2786 contentDescription = packageManager.getApplicationLabel( 2787 mIPackageManager.getApplicationInfo(packageName, 0, 2788 mSettings.getCurrentUserId())); 2789 } catch (RemoteException e) { 2790 /* ignore */ 2791 } 2792 if (mStatusBar != null) { 2793 mStatusBar.setIcon(mSlotIme, packageName, iconId, 0, 2794 contentDescription != null 2795 ? contentDescription.toString() : null); 2796 mStatusBar.setIconVisibility(mSlotIme, true); 2797 } 2798 } 2799 } finally { 2800 Binder.restoreCallingIdentity(ident); 2801 } 2802 } 2803 } 2804 shouldShowImeSwitcherLocked(int visibility)2805 private boolean shouldShowImeSwitcherLocked(int visibility) { 2806 if (!mShowOngoingImeSwitcherForPhones) return false; 2807 if (mSwitchingDialog != null) return false; 2808 if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded() 2809 && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false; 2810 if ((visibility & InputMethodService.IME_ACTIVE) == 0 2811 || (visibility & InputMethodService.IME_INVISIBLE) != 0) { 2812 return false; 2813 } 2814 if (mWindowManagerInternal.isHardKeyboardAvailable()) { 2815 // When physical keyboard is attached, we show the ime switcher (or notification if 2816 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently 2817 // exists in the IME switcher dialog. Might be OK to remove this condition once 2818 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live. 2819 return true; 2820 } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) { 2821 return false; 2822 } 2823 2824 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2825 final int N = imis.size(); 2826 if (N > 2) return true; 2827 if (N < 1) return false; 2828 int nonAuxCount = 0; 2829 int auxCount = 0; 2830 InputMethodSubtype nonAuxSubtype = null; 2831 InputMethodSubtype auxSubtype = null; 2832 for(int i = 0; i < N; ++i) { 2833 final InputMethodInfo imi = imis.get(i); 2834 final List<InputMethodSubtype> subtypes = 2835 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 2836 final int subtypeCount = subtypes.size(); 2837 if (subtypeCount == 0) { 2838 ++nonAuxCount; 2839 } else { 2840 for (int j = 0; j < subtypeCount; ++j) { 2841 final InputMethodSubtype subtype = subtypes.get(j); 2842 if (!subtype.isAuxiliary()) { 2843 ++nonAuxCount; 2844 nonAuxSubtype = subtype; 2845 } else { 2846 ++auxCount; 2847 auxSubtype = subtype; 2848 } 2849 } 2850 } 2851 } 2852 if (nonAuxCount > 1 || auxCount > 1) { 2853 return true; 2854 } else if (nonAuxCount == 1 && auxCount == 1) { 2855 if (nonAuxSubtype != null && auxSubtype != null 2856 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale()) 2857 || auxSubtype.overridesImplicitlyEnabledSubtype() 2858 || nonAuxSubtype.overridesImplicitlyEnabledSubtype()) 2859 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) { 2860 return false; 2861 } 2862 return true; 2863 } 2864 return false; 2865 } 2866 isKeyguardLocked()2867 private boolean isKeyguardLocked() { 2868 return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 2869 } 2870 2871 @BinderThread 2872 @SuppressWarnings("deprecation") setImeWindowStatus(@onNull IBinder token, int vis, int backDisposition)2873 private void setImeWindowStatus(@NonNull IBinder token, int vis, int backDisposition) { 2874 final int topFocusedDisplayId = mWindowManagerInternal.getTopFocusedDisplayId(); 2875 2876 synchronized (mMethodMap) { 2877 if (!calledWithValidTokenLocked(token)) { 2878 return; 2879 } 2880 // Skip update IME status when current token display is not same as focused display. 2881 // Note that we still need to update IME status when focusing external display 2882 // that does not support system decoration and fallback to show IME on default 2883 // display since it is intentional behavior. 2884 if (mCurTokenDisplayId != topFocusedDisplayId 2885 && mCurTokenDisplayId != FALLBACK_DISPLAY_ID) { 2886 return; 2887 } 2888 mImeWindowVis = vis; 2889 mBackDisposition = backDisposition; 2890 updateSystemUiLocked(vis, backDisposition); 2891 } 2892 2893 final boolean dismissImeOnBackKeyPressed; 2894 switch (backDisposition) { 2895 case InputMethodService.BACK_DISPOSITION_WILL_DISMISS: 2896 dismissImeOnBackKeyPressed = true; 2897 break; 2898 case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS: 2899 dismissImeOnBackKeyPressed = false; 2900 break; 2901 default: 2902 case InputMethodService.BACK_DISPOSITION_DEFAULT: 2903 dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0); 2904 break; 2905 } 2906 mWindowManagerInternal.updateInputMethodWindowStatus(token, 2907 (vis & InputMethodService.IME_VISIBLE) != 0, dismissImeOnBackKeyPressed); 2908 } 2909 2910 @BinderThread reportStartInput(@onNull IBinder token, IBinder startInputToken)2911 private void reportStartInput(@NonNull IBinder token, IBinder startInputToken) { 2912 synchronized (mMethodMap) { 2913 if (!calledWithValidTokenLocked(token)) { 2914 return; 2915 } 2916 final IBinder targetWindow = mImeTargetWindowMap.get(startInputToken); 2917 if (targetWindow != null && mLastImeTargetWindow != targetWindow) { 2918 mWindowManagerInternal.updateInputMethodTargetWindow(token, targetWindow); 2919 } 2920 mLastImeTargetWindow = targetWindow; 2921 } 2922 } 2923 2924 // Caution! This method is called in this class. Handle multi-user carefully updateSystemUiLocked(int vis, int backDisposition)2925 private void updateSystemUiLocked(int vis, int backDisposition) { 2926 if (mCurToken == null) { 2927 return; 2928 } 2929 if (DEBUG) { 2930 Slog.d(TAG, "IME window vis: " + vis 2931 + " active: " + (vis & InputMethodService.IME_ACTIVE) 2932 + " inv: " + (vis & InputMethodService.IME_INVISIBLE) 2933 + " displayId: " + mCurTokenDisplayId); 2934 } 2935 2936 // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure 2937 // all updateSystemUi happens on system previlege. 2938 final long ident = Binder.clearCallingIdentity(); 2939 try { 2940 // apply policy for binder calls 2941 if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { 2942 vis = 0; 2943 } 2944 if (!mCurPerceptible) { 2945 vis &= ~InputMethodService.IME_VISIBLE; 2946 } 2947 // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked(). 2948 final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis); 2949 if (mStatusBar != null) { 2950 mStatusBar.setImeWindowStatus(mCurTokenDisplayId, mCurToken, vis, backDisposition, 2951 needsToShowImeSwitcher, false /*isMultiClientImeEnabled*/); 2952 } 2953 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2954 if (imi != null && needsToShowImeSwitcher) { 2955 // Used to load label 2956 final CharSequence title = mRes.getText( 2957 com.android.internal.R.string.select_input_method); 2958 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName( 2959 mContext, imi, mCurrentSubtype); 2960 mImeSwitcherNotification.setContentTitle(title) 2961 .setContentText(summary) 2962 .setContentIntent(mImeSwitchPendingIntent); 2963 try { 2964 // TODO(b/120076400): Figure out what is the best behavior 2965 if ((mNotificationManager != null) 2966 && !mIWindowManager.hasNavigationBar(DEFAULT_DISPLAY)) { 2967 if (DEBUG) { 2968 Slog.d(TAG, "--- show notification: label = " + summary); 2969 } 2970 mNotificationManager.notifyAsUser(null, 2971 SystemMessage.NOTE_SELECT_INPUT_METHOD, 2972 mImeSwitcherNotification.build(), UserHandle.ALL); 2973 mNotificationShown = true; 2974 } 2975 } catch (RemoteException e) { 2976 } 2977 } else { 2978 if (mNotificationShown && mNotificationManager != null) { 2979 if (DEBUG) { 2980 Slog.d(TAG, "--- hide notification"); 2981 } 2982 mNotificationManager.cancelAsUser(null, 2983 SystemMessage.NOTE_SELECT_INPUT_METHOD, UserHandle.ALL); 2984 mNotificationShown = false; 2985 } 2986 } 2987 } finally { 2988 Binder.restoreCallingIdentity(ident); 2989 } 2990 } 2991 updateFromSettingsLocked(boolean enabledMayChange)2992 void updateFromSettingsLocked(boolean enabledMayChange) { 2993 updateInputMethodsFromSettingsLocked(enabledMayChange); 2994 updateKeyboardFromSettingsLocked(); 2995 } 2996 updateInputMethodsFromSettingsLocked(boolean enabledMayChange)2997 void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) { 2998 if (enabledMayChange) { 2999 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 3000 for (int i=0; i<enabled.size(); i++) { 3001 // We allow the user to select "disabled until used" apps, so if they 3002 // are enabling one of those here we now need to make it enabled. 3003 InputMethodInfo imm = enabled.get(i); 3004 try { 3005 ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(), 3006 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 3007 mSettings.getCurrentUserId()); 3008 if (ai != null && ai.enabledSetting 3009 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 3010 if (DEBUG) { 3011 Slog.d(TAG, "Update state(" + imm.getId() 3012 + "): DISABLED_UNTIL_USED -> DEFAULT"); 3013 } 3014 mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(), 3015 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 3016 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(), 3017 mContext.getBasePackageName()); 3018 } 3019 } catch (RemoteException e) { 3020 } 3021 } 3022 } 3023 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 3024 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 3025 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 3026 // enabled. 3027 String id = mSettings.getSelectedInputMethod(); 3028 // There is no input method selected, try to choose new applicable input method. 3029 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 3030 id = mSettings.getSelectedInputMethod(); 3031 } 3032 if (!TextUtils.isEmpty(id)) { 3033 try { 3034 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); 3035 } catch (IllegalArgumentException e) { 3036 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 3037 resetCurrentMethodAndClient(UnbindReason.SWITCH_IME_FAILED); 3038 } 3039 } else { 3040 // There is no longer an input method set, so stop any current one. 3041 resetCurrentMethodAndClient(UnbindReason.NO_IME); 3042 } 3043 // Here is not the perfect place to reset the switching controller. Ideally 3044 // mSwitchingController and mSettings should be able to share the same state. 3045 // TODO: Make sure that mSwitchingController and mSettings are sharing the 3046 // the same enabled IMEs list. 3047 mSwitchingController.resetCircularListLocked(mContext); 3048 3049 } 3050 updateKeyboardFromSettingsLocked()3051 public void updateKeyboardFromSettingsLocked() { 3052 mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled(); 3053 if (mSwitchingDialog != null 3054 && mSwitchingDialogTitleView != null 3055 && mSwitchingDialog.isShowing()) { 3056 final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById( 3057 com.android.internal.R.id.hard_keyboard_switch); 3058 hardKeySwitch.setChecked(mShowImeWithHardKeyboard); 3059 } 3060 } 3061 setInputMethodLocked(String id, int subtypeId)3062 /* package */ void setInputMethodLocked(String id, int subtypeId) { 3063 InputMethodInfo info = mMethodMap.get(id); 3064 if (info == null) { 3065 throw new IllegalArgumentException("Unknown id: " + id); 3066 } 3067 3068 // See if we need to notify a subtype change within the same IME. 3069 if (id.equals(mCurMethodId)) { 3070 final int subtypeCount = info.getSubtypeCount(); 3071 if (subtypeCount <= 0) { 3072 return; 3073 } 3074 final InputMethodSubtype oldSubtype = mCurrentSubtype; 3075 final InputMethodSubtype newSubtype; 3076 if (subtypeId >= 0 && subtypeId < subtypeCount) { 3077 newSubtype = info.getSubtypeAt(subtypeId); 3078 } else { 3079 // If subtype is null, try to find the most applicable one from 3080 // getCurrentInputMethodSubtype. 3081 newSubtype = getCurrentInputMethodSubtypeLocked(); 3082 } 3083 if (newSubtype == null || oldSubtype == null) { 3084 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype 3085 + ", new subtype = " + newSubtype); 3086 return; 3087 } 3088 if (newSubtype != oldSubtype) { 3089 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 3090 if (mCurMethod != null) { 3091 try { 3092 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 3093 mCurMethod.changeInputMethodSubtype(newSubtype); 3094 } catch (RemoteException e) { 3095 Slog.w(TAG, "Failed to call changeInputMethodSubtype"); 3096 } 3097 } 3098 } 3099 return; 3100 } 3101 3102 // Changing to a different IME. 3103 final long ident = Binder.clearCallingIdentity(); 3104 try { 3105 // Set a subtype to this input method. 3106 // subtypeId the name of a subtype which will be set. 3107 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); 3108 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 3109 // because mCurMethodId is stored as a history in 3110 // setSelectedInputMethodAndSubtypeLocked(). 3111 mCurMethodId = id; 3112 3113 if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) { 3114 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 3115 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 3116 intent.putExtra("input_method_id", id); 3117 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 3118 } 3119 unbindCurrentClientLocked(UnbindReason.SWITCH_IME); 3120 } finally { 3121 Binder.restoreCallingIdentity(ident); 3122 } 3123 } 3124 3125 @Override showSoftInput(IInputMethodClient client, IBinder windowToken, int flags, ResultReceiver resultReceiver)3126 public boolean showSoftInput(IInputMethodClient client, IBinder windowToken, int flags, 3127 ResultReceiver resultReceiver) { 3128 int uid = Binder.getCallingUid(); 3129 synchronized (mMethodMap) { 3130 if (!calledFromValidUserLocked()) { 3131 return false; 3132 } 3133 final long ident = Binder.clearCallingIdentity(); 3134 try { 3135 if (mCurClient == null || client == null 3136 || mCurClient.client.asBinder() != client.asBinder()) { 3137 // We need to check if this is the current client with 3138 // focus in the window manager, to allow this call to 3139 // be made before input is started in it. 3140 final ClientState cs = mClients.get(client.asBinder()); 3141 if (cs == null) { 3142 throw new IllegalArgumentException("unknown client " + client.asBinder()); 3143 } 3144 if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, 3145 cs.selfReportedDisplayId)) { 3146 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 3147 return false; 3148 } 3149 } 3150 if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); 3151 return showCurrentInputLocked(windowToken, flags, resultReceiver, 3152 SoftInputShowHideReason.SHOW_SOFT_INPUT); 3153 } finally { 3154 Binder.restoreCallingIdentity(ident); 3155 } 3156 } 3157 } 3158 3159 @BinderThread 3160 @Override reportPerceptible(IBinder windowToken, boolean perceptible)3161 public void reportPerceptible(IBinder windowToken, boolean perceptible) { 3162 Objects.requireNonNull(windowToken, "windowToken must not be null"); 3163 int uid = Binder.getCallingUid(); 3164 synchronized (mMethodMap) { 3165 if (!calledFromValidUserLocked()) { 3166 return; 3167 } 3168 final long ident = Binder.clearCallingIdentity(); 3169 try { 3170 if (mCurFocusedWindow == windowToken 3171 && mCurPerceptible != perceptible) { 3172 mCurPerceptible = perceptible; 3173 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 3174 } 3175 } finally { 3176 Binder.restoreCallingIdentity(ident); 3177 } 3178 } 3179 } 3180 3181 @GuardedBy("mMethodMap") showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)3182 boolean showCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, 3183 @SoftInputShowHideReason int reason) { 3184 mShowRequested = true; 3185 if (mAccessibilityRequestingNoSoftKeyboard) { 3186 return false; 3187 } 3188 3189 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 3190 mShowExplicitlyRequested = true; 3191 mShowForced = true; 3192 } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 3193 mShowExplicitlyRequested = true; 3194 } 3195 3196 if (!mSystemReady) { 3197 return false; 3198 } 3199 3200 boolean res = false; 3201 if (mCurMethod != null) { 3202 if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken); 3203 // create a dummy token for IMS so that IMS cannot inject windows into client app. 3204 Binder showInputToken = new Binder(); 3205 mShowRequestWindowMap.put(showInputToken, windowToken); 3206 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIIOOO( 3207 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), reason, mCurMethod, resultReceiver, 3208 showInputToken)); 3209 mInputShown = true; 3210 if (mHaveConnection && !mVisibleBound) { 3211 bindCurrentInputMethodServiceLocked( 3212 mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS); 3213 mVisibleBound = true; 3214 } 3215 res = true; 3216 } else if (mHaveConnection && SystemClock.uptimeMillis() 3217 >= (mLastBindTime+TIME_TO_RECONNECT)) { 3218 // The client has asked to have the input method shown, but 3219 // we have been sitting here too long with a connection to the 3220 // service and no interface received, so let's disconnect/connect 3221 // to try to prod things along. 3222 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 3223 SystemClock.uptimeMillis()-mLastBindTime,1); 3224 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); 3225 mContext.unbindService(this); 3226 bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS); 3227 } else { 3228 if (DEBUG) { 3229 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = " 3230 + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis())); 3231 } 3232 } 3233 3234 return res; 3235 } 3236 3237 @Override hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags, ResultReceiver resultReceiver)3238 public boolean hideSoftInput(IInputMethodClient client, IBinder windowToken, int flags, 3239 ResultReceiver resultReceiver) { 3240 int uid = Binder.getCallingUid(); 3241 synchronized (mMethodMap) { 3242 if (!calledFromValidUserLocked()) { 3243 return false; 3244 } 3245 final long ident = Binder.clearCallingIdentity(); 3246 try { 3247 if (mCurClient == null || client == null 3248 || mCurClient.client.asBinder() != client.asBinder()) { 3249 // We need to check if this is the current client with 3250 // focus in the window manager, to allow this call to 3251 // be made before input is started in it. 3252 final ClientState cs = mClients.get(client.asBinder()); 3253 if (cs == null) { 3254 throw new IllegalArgumentException("unknown client " + client.asBinder()); 3255 } 3256 if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, 3257 cs.selfReportedDisplayId)) { 3258 if (DEBUG) { 3259 Slog.w(TAG, "Ignoring hideSoftInput of uid " + uid + ": " + client); 3260 } 3261 return false; 3262 } 3263 } 3264 3265 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 3266 return hideCurrentInputLocked(windowToken, flags, resultReceiver, 3267 SoftInputShowHideReason.HIDE_SOFT_INPUT); 3268 } finally { 3269 Binder.restoreCallingIdentity(ident); 3270 } 3271 } 3272 } 3273 hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, @SoftInputShowHideReason int reason)3274 boolean hideCurrentInputLocked(IBinder windowToken, int flags, ResultReceiver resultReceiver, 3275 @SoftInputShowHideReason int reason) { 3276 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 3277 && (mShowExplicitlyRequested || mShowForced)) { 3278 if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); 3279 return false; 3280 } 3281 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 3282 if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); 3283 return false; 3284 } 3285 3286 // There is a chance that IMM#hideSoftInput() is called in a transient state where 3287 // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting 3288 // to be updated with the new value sent from IME process. Even in such a transient state 3289 // historically we have accepted an incoming call of IMM#hideSoftInput() from the 3290 // application process as a valid request, and have even promised such a behavior with CTS 3291 // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only 3292 // IMMS#InputShown indicates that the software keyboard is shown. 3293 // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested. 3294 final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown || 3295 (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0); 3296 boolean res; 3297 if (shouldHideSoftInput) { 3298 final Binder hideInputToken = new Binder(); 3299 mHideRequestWindowMap.put(hideInputToken, windowToken); 3300 // The IME will report its visible state again after the following message finally 3301 // delivered to the IME process as an IPC. Hence the inconsistency between 3302 // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in 3303 // the final state. 3304 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOOO(MSG_HIDE_SOFT_INPUT, 3305 reason, mCurMethod, resultReceiver, hideInputToken)); 3306 res = true; 3307 } else { 3308 res = false; 3309 } 3310 if (mHaveConnection && mVisibleBound) { 3311 mContext.unbindService(mVisibleConnection); 3312 mVisibleBound = false; 3313 } 3314 mInputShown = false; 3315 mShowRequested = false; 3316 mShowExplicitlyRequested = false; 3317 mShowForced = false; 3318 return res; 3319 } 3320 3321 @NonNull 3322 @Override startInputOrWindowGainedFocus( @tartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion)3323 public InputBindResult startInputOrWindowGainedFocus( 3324 @StartInputReason int startInputReason, IInputMethodClient client, IBinder windowToken, 3325 @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, 3326 int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext, 3327 @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion) { 3328 if (windowToken == null) { 3329 Slog.e(TAG, "windowToken cannot be null."); 3330 return InputBindResult.NULL; 3331 } 3332 final int callingUserId = UserHandle.getCallingUserId(); 3333 final int userId; 3334 if (attribute != null && attribute.targetInputMethodUser != null 3335 && attribute.targetInputMethodUser.getIdentifier() != callingUserId) { 3336 mContext.enforceCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL, 3337 "Using EditorInfo.targetInputMethodUser requires INTERACT_ACROSS_USERS_FULL."); 3338 userId = attribute.targetInputMethodUser.getIdentifier(); 3339 if (!mUserManagerInternal.isUserRunning(userId)) { 3340 // There is a chance that we hit here because of race condition. Let's just return 3341 // an error code instead of crashing the caller process, which at least has 3342 // INTERACT_ACROSS_USERS_FULL permission thus is likely to be an important process. 3343 Slog.e(TAG, "User #" + userId + " is not running."); 3344 return InputBindResult.INVALID_USER; 3345 } 3346 } else { 3347 userId = callingUserId; 3348 } 3349 final InputBindResult result; 3350 synchronized (mMethodMap) { 3351 final long ident = Binder.clearCallingIdentity(); 3352 try { 3353 result = startInputOrWindowGainedFocusInternalLocked(startInputReason, client, 3354 windowToken, startInputFlags, softInputMode, windowFlags, attribute, 3355 inputContext, missingMethods, unverifiedTargetSdkVersion, userId); 3356 } finally { 3357 Binder.restoreCallingIdentity(ident); 3358 } 3359 } 3360 if (result == null) { 3361 // This must never happen, but just in case. 3362 Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason=" 3363 + InputMethodDebug.startInputReasonToString(startInputReason) 3364 + " windowFlags=#" + Integer.toHexString(windowFlags) 3365 + " editorInfo=" + attribute); 3366 return InputBindResult.NULL; 3367 } 3368 return result; 3369 } 3370 3371 @NonNull startInputOrWindowGainedFocusInternalLocked( @tartInputReason int startInputReason, IInputMethodClient client, @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, IInputContext inputContext, @MissingMethodFlags int missingMethods, int unverifiedTargetSdkVersion, @UserIdInt int userId)3372 private InputBindResult startInputOrWindowGainedFocusInternalLocked( 3373 @StartInputReason int startInputReason, IInputMethodClient client, 3374 @NonNull IBinder windowToken, @StartInputFlags int startInputFlags, 3375 @SoftInputModeFlags int softInputMode, int windowFlags, EditorInfo attribute, 3376 IInputContext inputContext, @MissingMethodFlags int missingMethods, 3377 int unverifiedTargetSdkVersion, @UserIdInt int userId) { 3378 if (DEBUG) { 3379 Slog.v(TAG, "startInputOrWindowGainedFocusInternalLocked: reason=" 3380 + InputMethodDebug.startInputReasonToString(startInputReason) 3381 + " client=" + client.asBinder() 3382 + " inputContext=" + inputContext 3383 + " missingMethods=" 3384 + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods) 3385 + " attribute=" + attribute 3386 + " startInputFlags=" 3387 + InputMethodDebug.startInputFlagsToString(startInputFlags) 3388 + " softInputMode=" + InputMethodDebug.softInputModeToString(softInputMode) 3389 + " windowFlags=#" + Integer.toHexString(windowFlags) 3390 + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion); 3391 } 3392 3393 final int windowDisplayId = mWindowManagerInternal.getDisplayIdForWindow(windowToken); 3394 3395 final ClientState cs = mClients.get(client.asBinder()); 3396 if (cs == null) { 3397 throw new IllegalArgumentException("unknown client " + client.asBinder()); 3398 } 3399 if (cs.selfReportedDisplayId != windowDisplayId) { 3400 Slog.e(TAG, "startInputOrWindowGainedFocusInternal: display ID mismatch." 3401 + " from client:" + cs.selfReportedDisplayId 3402 + " from window:" + windowDisplayId); 3403 return InputBindResult.DISPLAY_ID_MISMATCH; 3404 } 3405 3406 if (!mWindowManagerInternal.isInputMethodClientFocus(cs.uid, cs.pid, 3407 cs.selfReportedDisplayId)) { 3408 // Check with the window manager to make sure this client actually 3409 // has a window with focus. If not, reject. This is thread safe 3410 // because if the focus changes some time before or after, the 3411 // next client receiving focus that has any interest in input will 3412 // be calling through here after that change happens. 3413 if (DEBUG) { 3414 Slog.w(TAG, "Focus gain on non-focused client " + cs.client 3415 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 3416 } 3417 return InputBindResult.NOT_IME_TARGET_WINDOW; 3418 } 3419 3420 if (mUserSwitchHandlerTask != null) { 3421 // There is already an on-going pending user switch task. 3422 final int nextUserId = mUserSwitchHandlerTask.mToUserId; 3423 if (userId == nextUserId) { 3424 scheduleSwitchUserTaskLocked(userId, cs.client); 3425 return InputBindResult.USER_SWITCHING; 3426 } 3427 for (int profileId : mUserManager.getProfileIdsWithDisabled(nextUserId)) { 3428 if (profileId == userId) { 3429 scheduleSwitchUserTaskLocked(userId, cs.client); 3430 return InputBindResult.USER_SWITCHING; 3431 } 3432 } 3433 return InputBindResult.INVALID_USER; 3434 } 3435 3436 // cross-profile access is always allowed here to allow profile-switching. 3437 if (!mSettings.isCurrentProfile(userId)) { 3438 Slog.w(TAG, "A background user is requesting window. Hiding IME."); 3439 Slog.w(TAG, "If you need to impersonate a foreground user/profile from" 3440 + " a background user, use EditorInfo.targetInputMethodUser with" 3441 + " INTERACT_ACROSS_USERS_FULL permission."); 3442 hideCurrentInputLocked( 3443 mCurFocusedWindow, 0, null, SoftInputShowHideReason.HIDE_INVALID_USER); 3444 return InputBindResult.INVALID_USER; 3445 } 3446 3447 if (userId != mSettings.getCurrentUserId()) { 3448 scheduleSwitchUserTaskLocked(userId, cs.client); 3449 return InputBindResult.USER_SWITCHING; 3450 } 3451 3452 // Master feature flag that overrides other conditions and forces IME preRendering. 3453 if (DEBUG) { 3454 Slog.v(TAG, "IME PreRendering MASTER flag: " 3455 + DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() + ", LowRam: " + mIsLowRam); 3456 } 3457 // pre-rendering not supported on low-ram devices. 3458 cs.shouldPreRenderIme = DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.value() && !mIsLowRam; 3459 3460 if (mCurFocusedWindow == windowToken) { 3461 if (DEBUG) { 3462 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client 3463 + " attribute=" + attribute + ", token = " + windowToken 3464 + ", startInputReason=" 3465 + InputMethodDebug.startInputReasonToString(startInputReason)); 3466 } 3467 if (attribute != null) { 3468 return startInputUncheckedLocked(cs, inputContext, missingMethods, 3469 attribute, startInputFlags, startInputReason); 3470 } 3471 return new InputBindResult( 3472 InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY, 3473 null, null, null, -1, null); 3474 } 3475 mCurFocusedWindow = windowToken; 3476 mCurFocusedWindowSoftInputMode = softInputMode; 3477 mCurFocusedWindowClient = cs; 3478 mCurPerceptible = true; 3479 3480 // Should we auto-show the IME even if the caller has not 3481 // specified what should be done with it? 3482 // We only do this automatically if the window can resize 3483 // to accommodate the IME (so what the user sees will give 3484 // them good context without input information being obscured 3485 // by the IME) or if running on a large screen where there 3486 // is more room for the target window + IME. 3487 final boolean doAutoShow = 3488 (softInputMode & LayoutParams.SOFT_INPUT_MASK_ADJUST) 3489 == LayoutParams.SOFT_INPUT_ADJUST_RESIZE 3490 || mRes.getConfiguration().isLayoutSizeAtLeast( 3491 Configuration.SCREENLAYOUT_SIZE_LARGE); 3492 final boolean isTextEditor = (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0; 3493 3494 // We want to start input before showing the IME, but after closing 3495 // it. We want to do this after closing it to help the IME disappear 3496 // more quickly (not get stuck behind it initializing itself for the 3497 // new focused input, even if its window wants to hide the IME). 3498 boolean didStart = false; 3499 3500 InputBindResult res = null; 3501 switch (softInputMode & LayoutParams.SOFT_INPUT_MASK_STATE) { 3502 case LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 3503 if (!isTextEditor || !doAutoShow) { 3504 if (LayoutParams.mayUseInputMethod(windowFlags)) { 3505 // There is no focus view, and this window will 3506 // be behind any soft input window, so hide the 3507 // soft input window if it is shown. 3508 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 3509 hideCurrentInputLocked( 3510 mCurFocusedWindow, InputMethodManager.HIDE_NOT_ALWAYS, null, 3511 SoftInputShowHideReason.HIDE_UNSPECIFIED_WINDOW); 3512 3513 // If focused display changed, we should unbind current method 3514 // to make app window in previous display relayout after Ime 3515 // window token removed. 3516 // Note that we can trust client's display ID as long as it matches 3517 // to the display ID obtained from the window. 3518 if (cs.selfReportedDisplayId != mCurTokenDisplayId) { 3519 unbindCurrentMethodLocked(); 3520 } 3521 } 3522 } else if (isTextEditor && doAutoShow 3523 && (softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 3524 // There is a focus view, and we are navigating forward 3525 // into the window, so show the input window for the user. 3526 // We only do this automatically if the window can resize 3527 // to accommodate the IME (so what the user sees will give 3528 // them good context without input information being obscured 3529 // by the IME) or if running on a large screen where there 3530 // is more room for the target window + IME. 3531 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 3532 if (attribute != null) { 3533 res = startInputUncheckedLocked(cs, inputContext, missingMethods, 3534 attribute, startInputFlags, startInputReason); 3535 didStart = true; 3536 } 3537 showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, 3538 SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV); 3539 } 3540 break; 3541 case LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 3542 // Do nothing. 3543 break; 3544 case LayoutParams.SOFT_INPUT_STATE_HIDDEN: 3545 if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 3546 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 3547 hideCurrentInputLocked(mCurFocusedWindow, 0, null, 3548 SoftInputShowHideReason.HIDE_STATE_HIDDEN_FORWARD_NAV); 3549 } 3550 break; 3551 case LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 3552 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 3553 hideCurrentInputLocked(mCurFocusedWindow, 0, null, 3554 SoftInputShowHideReason.HIDE_ALWAYS_HIDDEN_STATE); 3555 break; 3556 case LayoutParams.SOFT_INPUT_STATE_VISIBLE: 3557 if ((softInputMode & LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 3558 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 3559 if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( 3560 unverifiedTargetSdkVersion, startInputFlags)) { 3561 if (attribute != null) { 3562 res = startInputUncheckedLocked(cs, inputContext, missingMethods, 3563 attribute, startInputFlags, startInputReason); 3564 didStart = true; 3565 } 3566 showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, 3567 SoftInputShowHideReason.SHOW_STATE_VISIBLE_FORWARD_NAV); 3568 } else { 3569 Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because" 3570 + " there is no focused view that also returns true from" 3571 + " View#onCheckIsTextEditor()"); 3572 } 3573 } 3574 break; 3575 case LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 3576 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 3577 if (InputMethodUtils.isSoftInputModeStateVisibleAllowed( 3578 unverifiedTargetSdkVersion, startInputFlags)) { 3579 if (attribute != null) { 3580 res = startInputUncheckedLocked(cs, inputContext, missingMethods, 3581 attribute, startInputFlags, startInputReason); 3582 didStart = true; 3583 } 3584 showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null, 3585 SoftInputShowHideReason.SHOW_STATE_ALWAYS_VISIBLE); 3586 } else { 3587 Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because" 3588 + " there is no focused view that also returns true from" 3589 + " View#onCheckIsTextEditor()"); 3590 } 3591 break; 3592 } 3593 3594 if (!didStart) { 3595 if (attribute != null) { 3596 if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value() 3597 || (startInputFlags & StartInputFlags.IS_TEXT_EDITOR) != 0) { 3598 res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute, 3599 startInputFlags, startInputReason); 3600 } else { 3601 res = InputBindResult.NO_EDITOR; 3602 } 3603 } else { 3604 res = InputBindResult.NULL_EDITOR_INFO; 3605 } 3606 } 3607 return res; 3608 } 3609 canShowInputMethodPickerLocked(IInputMethodClient client)3610 private boolean canShowInputMethodPickerLocked(IInputMethodClient client) { 3611 // TODO(yukawa): multi-display support. 3612 final int uid = Binder.getCallingUid(); 3613 if (mCurFocusedWindowClient != null && client != null 3614 && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) { 3615 return true; 3616 } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid( 3617 mAppOpsManager, 3618 uid, 3619 mCurIntent.getComponent().getPackageName())) { 3620 return true; 3621 } 3622 return false; 3623 } 3624 3625 @Override showInputMethodPickerFromClient( IInputMethodClient client, int auxiliarySubtypeMode)3626 public void showInputMethodPickerFromClient( 3627 IInputMethodClient client, int auxiliarySubtypeMode) { 3628 synchronized (mMethodMap) { 3629 if (!calledFromValidUserLocked()) { 3630 return; 3631 } 3632 if(!canShowInputMethodPickerLocked(client)) { 3633 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 3634 + Binder.getCallingUid() + ": " + client); 3635 return; 3636 } 3637 3638 // Always call subtype picker, because subtype picker is a superset of input method 3639 // picker. 3640 mHandler.sendMessage(mCaller.obtainMessageII( 3641 MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, 3642 (mCurClient != null) ? mCurClient.selfReportedDisplayId : DEFAULT_DISPLAY)); 3643 } 3644 } 3645 3646 @Override showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode, int displayId)3647 public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode, 3648 int displayId) { 3649 if (mContext.checkCallingPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) 3650 != PackageManager.PERMISSION_GRANTED) { 3651 throw new SecurityException( 3652 "showInputMethodPickerFromSystem requires WRITE_SECURE_SETTINGS permission"); 3653 } 3654 // Always call subtype picker, because subtype picker is a superset of input method 3655 // picker. 3656 mHandler.sendMessage(mCaller.obtainMessageII( 3657 MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode, displayId)); 3658 } 3659 isInputMethodPickerShownForTest()3660 public boolean isInputMethodPickerShownForTest() { 3661 synchronized(mMethodMap) { 3662 if (mSwitchingDialog == null) { 3663 return false; 3664 } 3665 return mSwitchingDialog.isShowing(); 3666 } 3667 } 3668 3669 @BinderThread setInputMethod(@onNull IBinder token, String id)3670 private void setInputMethod(@NonNull IBinder token, String id) { 3671 synchronized (mMethodMap) { 3672 if (!calledWithValidTokenLocked(token)) { 3673 return; 3674 } 3675 setInputMethodWithSubtypeIdLocked(token, id, NOT_A_SUBTYPE_ID); 3676 } 3677 } 3678 3679 @BinderThread setInputMethodAndSubtype(@onNull IBinder token, String id, InputMethodSubtype subtype)3680 private void setInputMethodAndSubtype(@NonNull IBinder token, String id, 3681 InputMethodSubtype subtype) { 3682 synchronized (mMethodMap) { 3683 if (!calledWithValidTokenLocked(token)) { 3684 return; 3685 } 3686 if (subtype != null) { 3687 setInputMethodWithSubtypeIdLocked(token, id, 3688 InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id), 3689 subtype.hashCode())); 3690 } else { 3691 setInputMethod(token, id); 3692 } 3693 } 3694 } 3695 3696 @Override showInputMethodAndSubtypeEnablerFromClient( IInputMethodClient client, String inputMethodId)3697 public void showInputMethodAndSubtypeEnablerFromClient( 3698 IInputMethodClient client, String inputMethodId) { 3699 synchronized (mMethodMap) { 3700 // TODO(yukawa): Should we verify the display ID? 3701 if (!calledFromValidUserLocked()) { 3702 return; 3703 } 3704 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 3705 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); 3706 } 3707 } 3708 3709 @BinderThread switchToPreviousInputMethod(@onNull IBinder token)3710 private boolean switchToPreviousInputMethod(@NonNull IBinder token) { 3711 synchronized (mMethodMap) { 3712 if (!calledWithValidTokenLocked(token)) { 3713 return false; 3714 } 3715 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 3716 final InputMethodInfo lastImi; 3717 if (lastIme != null) { 3718 lastImi = mMethodMap.get(lastIme.first); 3719 } else { 3720 lastImi = null; 3721 } 3722 String targetLastImiId = null; 3723 int subtypeId = NOT_A_SUBTYPE_ID; 3724 if (lastIme != null && lastImi != null) { 3725 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId); 3726 final int lastSubtypeHash = Integer.parseInt(lastIme.second); 3727 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID 3728 : mCurrentSubtype.hashCode(); 3729 // If the last IME is the same as the current IME and the last subtype is not 3730 // defined, there is no need to switch to the last IME. 3731 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { 3732 targetLastImiId = lastIme.first; 3733 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 3734 } 3735 } 3736 3737 if (TextUtils.isEmpty(targetLastImiId) 3738 && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) { 3739 // This is a safety net. If the currentSubtype can't be added to the history 3740 // and the framework couldn't find the last ime, we will make the last ime be 3741 // the most applicable enabled keyboard subtype of the system imes. 3742 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 3743 if (enabled != null) { 3744 final int N = enabled.size(); 3745 final String locale = mCurrentSubtype == null 3746 ? mRes.getConfiguration().locale.toString() 3747 : mCurrentSubtype.getLocale(); 3748 for (int i = 0; i < N; ++i) { 3749 final InputMethodInfo imi = enabled.get(i); 3750 if (imi.getSubtypeCount() > 0 && imi.isSystem()) { 3751 InputMethodSubtype keyboardSubtype = 3752 InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes, 3753 InputMethodUtils.getSubtypes(imi), 3754 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true); 3755 if (keyboardSubtype != null) { 3756 targetLastImiId = imi.getId(); 3757 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 3758 imi, keyboardSubtype.hashCode()); 3759 if(keyboardSubtype.getLocale().equals(locale)) { 3760 break; 3761 } 3762 } 3763 } 3764 } 3765 } 3766 } 3767 3768 if (!TextUtils.isEmpty(targetLastImiId)) { 3769 if (DEBUG) { 3770 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second 3771 + ", from: " + mCurMethodId + ", " + subtypeId); 3772 } 3773 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId); 3774 return true; 3775 } else { 3776 return false; 3777 } 3778 } 3779 } 3780 3781 @BinderThread switchToNextInputMethod(@onNull IBinder token, boolean onlyCurrentIme)3782 private boolean switchToNextInputMethod(@NonNull IBinder token, boolean onlyCurrentIme) { 3783 synchronized (mMethodMap) { 3784 if (!calledWithValidTokenLocked(token)) { 3785 return false; 3786 } 3787 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( 3788 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype); 3789 if (nextSubtype == null) { 3790 return false; 3791 } 3792 setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(), 3793 nextSubtype.mSubtypeId); 3794 return true; 3795 } 3796 } 3797 3798 @BinderThread shouldOfferSwitchingToNextInputMethod(@onNull IBinder token)3799 private boolean shouldOfferSwitchingToNextInputMethod(@NonNull IBinder token) { 3800 synchronized (mMethodMap) { 3801 if (!calledWithValidTokenLocked(token)) { 3802 return false; 3803 } 3804 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked( 3805 false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype); 3806 if (nextSubtype == null) { 3807 return false; 3808 } 3809 return true; 3810 } 3811 } 3812 3813 @Override getLastInputMethodSubtype()3814 public InputMethodSubtype getLastInputMethodSubtype() { 3815 synchronized (mMethodMap) { 3816 if (!calledFromValidUserLocked()) { 3817 return null; 3818 } 3819 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 3820 // TODO: Handle the case of the last IME with no subtypes 3821 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 3822 || TextUtils.isEmpty(lastIme.second)) return null; 3823 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 3824 if (lastImi == null) return null; 3825 try { 3826 final int lastSubtypeHash = Integer.parseInt(lastIme.second); 3827 final int lastSubtypeId = 3828 InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 3829 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 3830 return null; 3831 } 3832 return lastImi.getSubtypeAt(lastSubtypeId); 3833 } catch (NumberFormatException e) { 3834 return null; 3835 } 3836 } 3837 } 3838 3839 @Override setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes)3840 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { 3841 // By this IPC call, only a process which shares the same uid with the IME can add 3842 // additional input method subtypes to the IME. 3843 if (TextUtils.isEmpty(imiId) || subtypes == null) return; 3844 final ArrayList<InputMethodSubtype> toBeAdded = new ArrayList<>(); 3845 for (InputMethodSubtype subtype : subtypes) { 3846 if (!toBeAdded.contains(subtype)) { 3847 toBeAdded.add(subtype); 3848 } else { 3849 Slog.w(TAG, "Duplicated subtype definition found: " 3850 + subtype.getLocale() + ", " + subtype.getMode()); 3851 } 3852 } 3853 synchronized (mMethodMap) { 3854 if (!calledFromValidUserLocked()) { 3855 return; 3856 } 3857 if (!mSystemReady) { 3858 return; 3859 } 3860 final InputMethodInfo imi = mMethodMap.get(imiId); 3861 if (imi == null) return; 3862 final String[] packageInfos; 3863 try { 3864 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid()); 3865 } catch (RemoteException e) { 3866 Slog.e(TAG, "Failed to get package infos"); 3867 return; 3868 } 3869 if (packageInfos != null) { 3870 final int packageNum = packageInfos.length; 3871 for (int i = 0; i < packageNum; ++i) { 3872 if (packageInfos[i].equals(imi.getPackageName())) { 3873 if (subtypes.length > 0) { 3874 mAdditionalSubtypeMap.put(imi.getId(), toBeAdded); 3875 } else { 3876 mAdditionalSubtypeMap.remove(imi.getId()); 3877 } 3878 AdditionalSubtypeUtils.save(mAdditionalSubtypeMap, mMethodMap, 3879 mSettings.getCurrentUserId()); 3880 final long ident = Binder.clearCallingIdentity(); 3881 try { 3882 buildInputMethodListLocked(false /* resetDefaultEnabledIme */); 3883 } finally { 3884 Binder.restoreCallingIdentity(ident); 3885 } 3886 return; 3887 } 3888 } 3889 } 3890 } 3891 return; 3892 } 3893 3894 /** 3895 * This is kept due to {@code @UnsupportedAppUsage} in 3896 * {@link InputMethodManager#getInputMethodWindowVisibleHeight()} and a dependency in 3897 * {@link InputMethodService#onCreate()}. 3898 * 3899 * <p>TODO(Bug 113914148): Check if we can remove this.</p> 3900 * @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight()} 3901 */ 3902 @Override getInputMethodWindowVisibleHeight()3903 public int getInputMethodWindowVisibleHeight() { 3904 // TODO(yukawa): Should we verify the display ID? 3905 return mWindowManagerInternal.getInputMethodWindowVisibleHeight(mCurTokenDisplayId); 3906 } 3907 3908 @Override reportActivityView(IInputMethodClient parentClient, int childDisplayId, float[] matrixValues)3909 public void reportActivityView(IInputMethodClient parentClient, int childDisplayId, 3910 float[] matrixValues) { 3911 final DisplayInfo displayInfo = mDisplayManagerInternal.getDisplayInfo(childDisplayId); 3912 if (displayInfo == null) { 3913 throw new IllegalArgumentException( 3914 "Cannot find display for non-existent displayId: " + childDisplayId); 3915 } 3916 final int callingUid = Binder.getCallingUid(); 3917 if (callingUid != displayInfo.ownerUid) { 3918 throw new SecurityException("The caller doesn't own the display."); 3919 } 3920 3921 synchronized (mMethodMap) { 3922 final ClientState cs = mClients.get(parentClient.asBinder()); 3923 if (cs == null) { 3924 return; 3925 } 3926 3927 // null matrixValues means that the entry needs to be removed. 3928 if (matrixValues == null) { 3929 final ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(childDisplayId); 3930 if (info == null) { 3931 return; 3932 } 3933 if (info.mParentClient != cs) { 3934 throw new SecurityException("Only the owner client can clear" 3935 + " ActivityViewGeometry for display #" + childDisplayId); 3936 } 3937 mActivityViewDisplayIdToParentMap.remove(childDisplayId); 3938 return; 3939 } 3940 3941 ActivityViewInfo info = mActivityViewDisplayIdToParentMap.get(childDisplayId); 3942 if (info != null && info.mParentClient != cs) { 3943 throw new InvalidParameterException("Display #" + childDisplayId 3944 + " is already registered by " + info.mParentClient); 3945 } 3946 if (info == null) { 3947 if (!mWindowManagerInternal.isUidAllowedOnDisplay(childDisplayId, cs.uid)) { 3948 throw new SecurityException(cs + " cannot access to display #" 3949 + childDisplayId); 3950 } 3951 info = new ActivityViewInfo(cs, new Matrix()); 3952 mActivityViewDisplayIdToParentMap.put(childDisplayId, info); 3953 } 3954 info.mMatrix.setValues(matrixValues); 3955 3956 if (mCurClient == null || mCurClient.curSession == null) { 3957 return; 3958 } 3959 3960 Matrix matrix = null; 3961 int displayId = mCurClient.selfReportedDisplayId; 3962 boolean needToNotify = false; 3963 while (true) { 3964 needToNotify |= (displayId == childDisplayId); 3965 final ActivityViewInfo next = mActivityViewDisplayIdToParentMap.get(displayId); 3966 if (next == null) { 3967 break; 3968 } 3969 if (matrix == null) { 3970 matrix = new Matrix(next.mMatrix); 3971 } else { 3972 matrix.postConcat(next.mMatrix); 3973 } 3974 if (next.mParentClient.selfReportedDisplayId == mCurTokenDisplayId) { 3975 if (needToNotify) { 3976 final float[] values = new float[9]; 3977 matrix.getValues(values); 3978 try { 3979 mCurClient.client.updateActivityViewToScreenMatrix(mCurSeq, values); 3980 } catch (RemoteException e) { 3981 } 3982 } 3983 break; 3984 } 3985 displayId = info.mParentClient.selfReportedDisplayId; 3986 } 3987 } 3988 } 3989 3990 @Override removeImeSurface()3991 public void removeImeSurface() { 3992 mContext.enforceCallingPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW, null); 3993 mHandler.sendMessage(mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); 3994 } 3995 3996 @Override removeImeSurfaceFromWindow(IBinder windowToken)3997 public void removeImeSurfaceFromWindow(IBinder windowToken) { 3998 // No permission check, because we'll only execute the request if the calling window is 3999 // also the current IME client. 4000 mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE_FROM_WINDOW, windowToken).sendToTarget(); 4001 } 4002 4003 @BinderThread notifyUserAction(@onNull IBinder token)4004 private void notifyUserAction(@NonNull IBinder token) { 4005 if (DEBUG) { 4006 Slog.d(TAG, "Got the notification of a user action."); 4007 } 4008 synchronized (mMethodMap) { 4009 if (mCurToken != token) { 4010 if (DEBUG) { 4011 Slog.d(TAG, "Ignoring the user action notification from IMEs that are no longer" 4012 + " active."); 4013 } 4014 return; 4015 } 4016 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 4017 if (imi != null) { 4018 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype); 4019 } 4020 } 4021 } 4022 4023 @BinderThread reportPreRendered(IBinder token, EditorInfo info)4024 private void reportPreRendered(IBinder token, EditorInfo info) { 4025 synchronized (mMethodMap) { 4026 if (!calledWithValidTokenLocked(token)) { 4027 return; 4028 } 4029 if (mCurClient != null && mCurClient.client != null) { 4030 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 4031 MSG_REPORT_PRE_RENDERED, info, mCurClient)); 4032 } 4033 } 4034 } 4035 4036 @BinderThread applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible)4037 private void applyImeVisibility(IBinder token, IBinder windowToken, boolean setVisible) { 4038 synchronized (mMethodMap) { 4039 if (!calledWithValidTokenLocked(token)) { 4040 return; 4041 } 4042 if (!setVisible) { 4043 if (mCurClient != null) { 4044 // IMMS only knows of focused window, not the actual IME target. 4045 // e.g. it isn't aware of any window that has both 4046 // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target. 4047 // Send it to window manager to hide IME from IME target window. 4048 // TODO(b/139861270): send to mCurClient.client once IMMS is aware of 4049 // actual IME target. 4050 mWindowManagerInternal.hideIme( 4051 mHideRequestWindowMap.get(windowToken), 4052 mCurClient.selfReportedDisplayId); 4053 } 4054 } else { 4055 // Send to window manager to show IME after IME layout finishes. 4056 mWindowManagerInternal.showImePostLayout(mShowRequestWindowMap.get(windowToken)); 4057 } 4058 } 4059 } 4060 setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId)4061 private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) { 4062 if (token == null) { 4063 if (mContext.checkCallingOrSelfPermission( 4064 android.Manifest.permission.WRITE_SECURE_SETTINGS) 4065 != PackageManager.PERMISSION_GRANTED) { 4066 throw new SecurityException( 4067 "Using null token requires permission " 4068 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 4069 } 4070 } else if (mCurToken != token) { 4071 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 4072 + " token: " + token); 4073 return; 4074 } 4075 4076 final long ident = Binder.clearCallingIdentity(); 4077 try { 4078 setInputMethodLocked(id, subtypeId); 4079 } finally { 4080 Binder.restoreCallingIdentity(ident); 4081 } 4082 } 4083 4084 @BinderThread hideMySoftInput(@onNull IBinder token, int flags)4085 private void hideMySoftInput(@NonNull IBinder token, int flags) { 4086 synchronized (mMethodMap) { 4087 if (!calledWithValidTokenLocked(token)) { 4088 return; 4089 } 4090 long ident = Binder.clearCallingIdentity(); 4091 try { 4092 hideCurrentInputLocked( 4093 mLastImeTargetWindow, flags, null, 4094 SoftInputShowHideReason.HIDE_MY_SOFT_INPUT); 4095 4096 } finally { 4097 Binder.restoreCallingIdentity(ident); 4098 } 4099 } 4100 } 4101 4102 @BinderThread showMySoftInput(@onNull IBinder token, int flags)4103 private void showMySoftInput(@NonNull IBinder token, int flags) { 4104 synchronized (mMethodMap) { 4105 if (!calledWithValidTokenLocked(token)) { 4106 return; 4107 } 4108 long ident = Binder.clearCallingIdentity(); 4109 try { 4110 showCurrentInputLocked(mLastImeTargetWindow, flags, null, 4111 SoftInputShowHideReason.SHOW_MY_SOFT_INPUT); 4112 } finally { 4113 Binder.restoreCallingIdentity(ident); 4114 } 4115 } 4116 } 4117 setEnabledSessionInMainThread(SessionState session)4118 void setEnabledSessionInMainThread(SessionState session) { 4119 if (mEnabledSession != session) { 4120 if (mEnabledSession != null && mEnabledSession.session != null) { 4121 try { 4122 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 4123 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false); 4124 } catch (RemoteException e) { 4125 } 4126 } 4127 mEnabledSession = session; 4128 if (mEnabledSession != null && mEnabledSession.session != null) { 4129 try { 4130 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 4131 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true); 4132 } catch (RemoteException e) { 4133 } 4134 } 4135 } 4136 } 4137 4138 @MainThread 4139 @Override handleMessage(Message msg)4140 public boolean handleMessage(Message msg) { 4141 SomeArgs args; 4142 switch (msg.what) { 4143 case MSG_SHOW_IM_SUBTYPE_PICKER: 4144 final boolean showAuxSubtypes; 4145 final int displayId = msg.arg2; 4146 switch (msg.arg1) { 4147 case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO: 4148 // This is undocumented so far, but IMM#showInputMethodPicker() has been 4149 // implemented so that auxiliary subtypes will be excluded when the soft 4150 // keyboard is invisible. 4151 showAuxSubtypes = mInputShown; 4152 break; 4153 case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES: 4154 showAuxSubtypes = true; 4155 break; 4156 case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES: 4157 showAuxSubtypes = false; 4158 break; 4159 default: 4160 Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1); 4161 return false; 4162 } 4163 showInputMethodMenu(showAuxSubtypes, displayId); 4164 return true; 4165 4166 case MSG_SHOW_IM_SUBTYPE_ENABLER: 4167 showInputMethodAndSubtypeEnabler((String)msg.obj); 4168 return true; 4169 4170 case MSG_SHOW_IM_CONFIG: 4171 showConfigureInputMethods(); 4172 return true; 4173 4174 // --------------------------------------------------------- 4175 4176 case MSG_UNBIND_INPUT: 4177 try { 4178 ((IInputMethod)msg.obj).unbindInput(); 4179 } catch (RemoteException e) { 4180 // There is nothing interesting about the method dying. 4181 } 4182 return true; 4183 case MSG_BIND_INPUT: 4184 args = (SomeArgs)msg.obj; 4185 try { 4186 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 4187 } catch (RemoteException e) { 4188 } 4189 args.recycle(); 4190 return true; 4191 case MSG_SHOW_SOFT_INPUT: 4192 args = (SomeArgs) msg.obj; 4193 try { 4194 final @SoftInputShowHideReason int reason = msg.arg2; 4195 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" 4196 + args.arg3 + ", " + msg.arg1 + ", " + args.arg2 + ") for reason: " 4197 + InputMethodDebug.softInputDisplayReasonToString(reason)); 4198 ((IInputMethod) args.arg1).showSoftInput( 4199 (IBinder) args.arg3, msg.arg1, (ResultReceiver) args.arg2); 4200 mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( 4201 mCurClient, mCurAttribute, 4202 mWindowManagerInternal.getWindowName(mCurFocusedWindow), 4203 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, 4204 mWindowManagerInternal.getWindowName( 4205 mShowRequestWindowMap.get(args.arg3)), 4206 mWindowManagerInternal.getImeControlTargetNameForLogging( 4207 mCurTokenDisplayId), 4208 mWindowManagerInternal.getImeTargetNameForLogging( 4209 mCurTokenDisplayId))); 4210 } catch (RemoteException e) { 4211 } 4212 args.recycle(); 4213 return true; 4214 case MSG_HIDE_SOFT_INPUT: 4215 args = (SomeArgs) msg.obj; 4216 try { 4217 final @SoftInputShowHideReason int reason = msg.arg1; 4218 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " 4219 + args.arg3 + ", " + args.arg2 + ") for reason: " 4220 + InputMethodDebug.softInputDisplayReasonToString(reason)); 4221 ((IInputMethod)args.arg1).hideSoftInput( 4222 (IBinder) args.arg3, 0, (ResultReceiver)args.arg2); 4223 mSoftInputShowHideHistory.addEntry(new SoftInputShowHideHistory.Entry( 4224 mCurClient, mCurAttribute, 4225 mWindowManagerInternal.getWindowName(mCurFocusedWindow), 4226 mCurFocusedWindowSoftInputMode, reason, mInFullscreenMode, 4227 mWindowManagerInternal.getWindowName( 4228 mHideRequestWindowMap.get(args.arg3)), 4229 mWindowManagerInternal.getImeControlTargetNameForLogging( 4230 mCurTokenDisplayId), 4231 mWindowManagerInternal.getImeTargetNameForLogging( 4232 mCurTokenDisplayId))); 4233 } catch (RemoteException e) { 4234 } 4235 args.recycle(); 4236 return true; 4237 case MSG_HIDE_CURRENT_INPUT_METHOD: 4238 synchronized (mMethodMap) { 4239 final @SoftInputShowHideReason int reason = (int) msg.obj; 4240 hideCurrentInputLocked(mCurFocusedWindow, 0, null, reason); 4241 4242 } 4243 return true; 4244 case MSG_INITIALIZE_IME: 4245 args = (SomeArgs)msg.obj; 4246 try { 4247 if (DEBUG) { 4248 Slog.v(TAG, "Sending attach of token: " + args.arg2 + " for display: " 4249 + msg.arg1); 4250 } 4251 final IBinder token = (IBinder) args.arg2; 4252 ((IInputMethod) args.arg1).initializeInternal(token, msg.arg1, 4253 new InputMethodPrivilegedOperationsImpl(this, token)); 4254 } catch (RemoteException e) { 4255 } 4256 args.recycle(); 4257 return true; 4258 case MSG_CREATE_SESSION: { 4259 args = (SomeArgs)msg.obj; 4260 IInputMethod method = (IInputMethod)args.arg1; 4261 InputChannel channel = (InputChannel)args.arg2; 4262 try { 4263 method.createSession(channel, (IInputSessionCallback)args.arg3); 4264 } catch (RemoteException e) { 4265 } finally { 4266 // Dispose the channel if the input method is not local to this process 4267 // because the remote proxy will get its own copy when unparceled. 4268 if (channel != null && Binder.isProxy(method)) { 4269 channel.dispose(); 4270 } 4271 } 4272 args.recycle(); 4273 return true; 4274 } 4275 case MSG_REMOVE_IME_SURFACE: { 4276 synchronized (mMethodMap) { 4277 try { 4278 if (mEnabledSession != null && mEnabledSession.session != null 4279 && !mShowRequested) { 4280 mEnabledSession.session.removeImeSurface(); 4281 } 4282 } catch (RemoteException e) { 4283 } 4284 } 4285 return true; 4286 } 4287 case MSG_REMOVE_IME_SURFACE_FROM_WINDOW: { 4288 IBinder windowToken = (IBinder) msg.obj; 4289 synchronized (mMethodMap) { 4290 try { 4291 if (windowToken == mCurFocusedWindow 4292 && mEnabledSession != null && mEnabledSession.session != null) { 4293 mEnabledSession.session.removeImeSurface(); 4294 } 4295 } catch (RemoteException e) { 4296 } 4297 } 4298 return true; 4299 } 4300 // --------------------------------------------------------- 4301 4302 case MSG_START_INPUT: { 4303 final int missingMethods = msg.arg1; 4304 final boolean restarting = msg.arg2 != 0; 4305 args = (SomeArgs) msg.obj; 4306 final IBinder startInputToken = (IBinder) args.arg1; 4307 final SessionState session = (SessionState) args.arg2; 4308 final IInputContext inputContext = (IInputContext) args.arg3; 4309 final EditorInfo editorInfo = (EditorInfo) args.arg4; 4310 try { 4311 setEnabledSessionInMainThread(session); 4312 session.method.startInput(startInputToken, inputContext, missingMethods, 4313 editorInfo, restarting, session.client.shouldPreRenderIme); 4314 } catch (RemoteException e) { 4315 } 4316 args.recycle(); 4317 return true; 4318 } 4319 4320 // --------------------------------------------------------- 4321 4322 case MSG_UNBIND_CLIENT: 4323 try { 4324 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2); 4325 } catch (RemoteException e) { 4326 // There is nothing interesting about the last client dying. 4327 } 4328 return true; 4329 case MSG_BIND_CLIENT: { 4330 args = (SomeArgs)msg.obj; 4331 IInputMethodClient client = (IInputMethodClient)args.arg1; 4332 InputBindResult res = (InputBindResult)args.arg2; 4333 try { 4334 client.onBindMethod(res); 4335 } catch (RemoteException e) { 4336 Slog.w(TAG, "Client died receiving input method " + args.arg2); 4337 } finally { 4338 // Dispose the channel if the input method is not local to this process 4339 // because the remote proxy will get its own copy when unparceled. 4340 if (res.channel != null && Binder.isProxy(client)) { 4341 res.channel.dispose(); 4342 } 4343 } 4344 args.recycle(); 4345 return true; 4346 } 4347 case MSG_SET_ACTIVE: 4348 try { 4349 ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0); 4350 } catch (RemoteException e) { 4351 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " 4352 + ((ClientState)msg.obj).pid + " uid " 4353 + ((ClientState)msg.obj).uid); 4354 } 4355 return true; 4356 case MSG_SET_INTERACTIVE: 4357 handleSetInteractive(msg.arg1 != 0); 4358 return true; 4359 case MSG_REPORT_FULLSCREEN_MODE: { 4360 final boolean fullscreen = msg.arg1 != 0; 4361 final ClientState clientState = (ClientState)msg.obj; 4362 try { 4363 clientState.client.reportFullscreenMode(fullscreen); 4364 } catch (RemoteException e) { 4365 Slog.w(TAG, "Got RemoteException sending " 4366 + "reportFullscreen(" + fullscreen + ") notification to pid=" 4367 + clientState.pid + " uid=" + clientState.uid); 4368 } 4369 return true; 4370 } 4371 case MSG_REPORT_PRE_RENDERED: { 4372 args = (SomeArgs) msg.obj; 4373 final EditorInfo info = (EditorInfo) args.arg1; 4374 final ClientState clientState = (ClientState) args.arg2; 4375 try { 4376 clientState.client.reportPreRendered(info); 4377 } catch (RemoteException e) { 4378 Slog.w(TAG, "Got RemoteException sending " 4379 + "reportPreRendered(" + info + ") notification to pid=" 4380 + clientState.pid + " uid=" + clientState.uid); 4381 } 4382 args.recycle(); 4383 return true; 4384 } 4385 case MSG_APPLY_IME_VISIBILITY: { 4386 final boolean setVisible = msg.arg1 != 0; 4387 final ClientState clientState = (ClientState) msg.obj; 4388 try { 4389 clientState.client.applyImeVisibility(setVisible); 4390 } catch (RemoteException e) { 4391 Slog.w(TAG, "Got RemoteException sending " 4392 + "applyImeVisibility(" + setVisible + ") notification to pid=" 4393 + clientState.pid + " uid=" + clientState.uid); 4394 } 4395 return true; 4396 } 4397 4398 // -------------------------------------------------------------- 4399 case MSG_HARD_KEYBOARD_SWITCH_CHANGED: 4400 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1); 4401 return true; 4402 case MSG_SYSTEM_UNLOCK_USER: { 4403 final int userId = msg.arg1; 4404 onUnlockUser(userId); 4405 return true; 4406 } 4407 case MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED: { 4408 final int userId = msg.arg1; 4409 final List<InputMethodInfo> imes = (List<InputMethodInfo>) msg.obj; 4410 mInputMethodListListeners.forEach( 4411 listener -> listener.onInputMethodListUpdated(imes, userId)); 4412 return true; 4413 } 4414 4415 // --------------------------------------------------------------- 4416 case MSG_INLINE_SUGGESTIONS_REQUEST: { 4417 args = (SomeArgs) msg.obj; 4418 final InlineSuggestionsRequestInfo requestInfo = 4419 (InlineSuggestionsRequestInfo) args.arg2; 4420 final IInlineSuggestionsRequestCallback callback = 4421 (IInlineSuggestionsRequestCallback) args.arg3; 4422 try { 4423 ((IInputMethod) args.arg1).onCreateInlineSuggestionsRequest(requestInfo, 4424 callback); 4425 } catch (RemoteException e) { 4426 Slog.w(TAG, "RemoteException calling onCreateInlineSuggestionsRequest(): " + e); 4427 } 4428 args.recycle(); 4429 return true; 4430 } 4431 4432 // --------------------------------------------------------------- 4433 case MSG_NOTIFY_IME_UID_TO_AUDIO_SERVICE: { 4434 if (mAudioManagerInternal == null) { 4435 mAudioManagerInternal = LocalServices.getService(AudioManagerInternal.class); 4436 } 4437 if (mAudioManagerInternal != null) { 4438 mAudioManagerInternal.setInputMethodServiceUid(msg.arg1 /* uid */); 4439 } 4440 return true; 4441 } 4442 } 4443 return false; 4444 } 4445 handleSetInteractive(final boolean interactive)4446 private void handleSetInteractive(final boolean interactive) { 4447 synchronized (mMethodMap) { 4448 mIsInteractive = interactive; 4449 updateSystemUiLocked(interactive ? mImeWindowVis : 0, mBackDisposition); 4450 4451 // Inform the current client of the change in active status 4452 if (mCurClient != null && mCurClient.client != null) { 4453 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO( 4454 MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0, 4455 mCurClient)); 4456 } 4457 } 4458 } 4459 chooseNewDefaultIMELocked()4460 private boolean chooseNewDefaultIMELocked() { 4461 final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( 4462 mSettings.getEnabledInputMethodListLocked()); 4463 if (imi != null) { 4464 if (DEBUG) { 4465 Slog.d(TAG, "New default IME was selected: " + imi.getId()); 4466 } 4467 resetSelectedInputMethodAndSubtypeLocked(imi.getId()); 4468 return true; 4469 } 4470 4471 return false; 4472 } 4473 queryInputMethodServicesInternal(Context context, @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList)4474 static void queryInputMethodServicesInternal(Context context, 4475 @UserIdInt int userId, ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap, 4476 ArrayMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { 4477 methodList.clear(); 4478 methodMap.clear(); 4479 4480 // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default 4481 // behavior of PackageManager is exactly what we want. It by default picks up appropriate 4482 // services depending on the unlock state for the specified user. 4483 final List<ResolveInfo> services = context.getPackageManager().queryIntentServicesAsUser( 4484 new Intent(InputMethod.SERVICE_INTERFACE), 4485 PackageManager.GET_META_DATA | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS, 4486 userId); 4487 4488 methodList.ensureCapacity(services.size()); 4489 methodMap.ensureCapacity(services.size()); 4490 4491 for (int i = 0; i < services.size(); ++i) { 4492 ResolveInfo ri = services.get(i); 4493 ServiceInfo si = ri.serviceInfo; 4494 final String imeId = InputMethodInfo.computeId(ri); 4495 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) { 4496 Slog.w(TAG, "Skipping input method " + imeId 4497 + ": it does not require the permission " 4498 + android.Manifest.permission.BIND_INPUT_METHOD); 4499 continue; 4500 } 4501 4502 if (DEBUG) Slog.d(TAG, "Checking " + imeId); 4503 4504 try { 4505 final InputMethodInfo imi = new InputMethodInfo(context, ri, 4506 additionalSubtypeMap.get(imeId)); 4507 if (imi.isVrOnly()) { 4508 continue; // Skip VR-only IME, which isn't supported for now. 4509 } 4510 methodList.add(imi); 4511 methodMap.put(imi.getId(), imi); 4512 if (DEBUG) { 4513 Slog.d(TAG, "Found an input method " + imi); 4514 } 4515 } catch (Exception e) { 4516 Slog.wtf(TAG, "Unable to load input method " + imeId, e); 4517 } 4518 } 4519 } 4520 4521 @GuardedBy("mMethodMap") buildInputMethodListLocked(boolean resetDefaultEnabledIme)4522 void buildInputMethodListLocked(boolean resetDefaultEnabledIme) { 4523 if (DEBUG) { 4524 Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme 4525 + " \n ------ caller=" + Debug.getCallers(10)); 4526 } 4527 if (!mSystemReady) { 4528 Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready"); 4529 return; 4530 } 4531 mMethodMapUpdateCount++; 4532 mMyPackageMonitor.clearKnownImePackageNamesLocked(); 4533 4534 queryInputMethodServicesInternal(mContext, mSettings.getCurrentUserId(), 4535 mAdditionalSubtypeMap, mMethodMap, mMethodList); 4536 4537 // Construct the set of possible IME packages for onPackageChanged() to avoid false 4538 // negatives when the package state remains to be the same but only the component state is 4539 // changed. 4540 { 4541 // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose 4542 // of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more 4543 // conservative, but it seems we cannot use it for now (Issue 35176630). 4544 final List<ResolveInfo> allInputMethodServices = 4545 mContext.getPackageManager().queryIntentServicesAsUser( 4546 new Intent(InputMethod.SERVICE_INTERFACE), 4547 PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId()); 4548 final int N = allInputMethodServices.size(); 4549 for (int i = 0; i < N; ++i) { 4550 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo; 4551 if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) { 4552 mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName); 4553 } 4554 } 4555 } 4556 4557 boolean reenableMinimumNonAuxSystemImes = false; 4558 // TODO: The following code should find better place to live. 4559 if (!resetDefaultEnabledIme) { 4560 boolean enabledImeFound = false; 4561 boolean enabledNonAuxImeFound = false; 4562 final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked(); 4563 final int N = enabledImes.size(); 4564 for (int i = 0; i < N; ++i) { 4565 final InputMethodInfo imi = enabledImes.get(i); 4566 if (mMethodList.contains(imi)) { 4567 enabledImeFound = true; 4568 if (!imi.isAuxiliaryIme()) { 4569 enabledNonAuxImeFound = true; 4570 break; 4571 } 4572 } 4573 } 4574 if (!enabledImeFound) { 4575 if (DEBUG) { 4576 Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs."); 4577 } 4578 resetDefaultEnabledIme = true; 4579 resetSelectedInputMethodAndSubtypeLocked(""); 4580 } else if (!enabledNonAuxImeFound) { 4581 if (DEBUG) { 4582 Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset."); 4583 } 4584 reenableMinimumNonAuxSystemImes = true; 4585 } 4586 } 4587 4588 if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) { 4589 final ArrayList<InputMethodInfo> defaultEnabledIme = 4590 InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList, 4591 reenableMinimumNonAuxSystemImes); 4592 final int N = defaultEnabledIme.size(); 4593 for (int i = 0; i < N; ++i) { 4594 final InputMethodInfo imi = defaultEnabledIme.get(i); 4595 if (DEBUG) { 4596 Slog.d(TAG, "--- enable ime = " + imi); 4597 } 4598 setInputMethodEnabledLocked(imi.getId(), true); 4599 } 4600 } 4601 4602 final String defaultImiId = mSettings.getSelectedInputMethod(); 4603 if (!TextUtils.isEmpty(defaultImiId)) { 4604 if (!mMethodMap.containsKey(defaultImiId)) { 4605 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); 4606 if (chooseNewDefaultIMELocked()) { 4607 updateInputMethodsFromSettingsLocked(true); 4608 } 4609 } else { 4610 // Double check that the default IME is certainly enabled. 4611 setInputMethodEnabledLocked(defaultImiId, true); 4612 } 4613 } 4614 // Here is not the perfect place to reset the switching controller. Ideally 4615 // mSwitchingController and mSettings should be able to share the same state. 4616 // TODO: Make sure that mSwitchingController and mSettings are sharing the 4617 // the same enabled IMEs list. 4618 mSwitchingController.resetCircularListLocked(mContext); 4619 4620 // Notify InputMethodListListeners of the new installed InputMethods. 4621 final List<InputMethodInfo> inputMethodList = new ArrayList<>(mMethodList); 4622 mHandler.obtainMessage(MSG_DISPATCH_ON_INPUT_METHOD_LIST_UPDATED, 4623 mSettings.getCurrentUserId(), 0 /* unused */, inputMethodList).sendToTarget(); 4624 } 4625 4626 // ---------------------------------------------------------------------- 4627 showInputMethodAndSubtypeEnabler(String inputMethodId)4628 private void showInputMethodAndSubtypeEnabler(String inputMethodId) { 4629 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 4630 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 4631 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 4632 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 4633 if (!TextUtils.isEmpty(inputMethodId)) { 4634 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); 4635 } 4636 final int userId; 4637 synchronized (mMethodMap) { 4638 userId = mSettings.getCurrentUserId(); 4639 } 4640 mContext.startActivityAsUser(intent, null, UserHandle.of(userId)); 4641 } 4642 showConfigureInputMethods()4643 private void showConfigureInputMethods() { 4644 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); 4645 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 4646 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 4647 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 4648 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); 4649 } 4650 isScreenLocked()4651 private boolean isScreenLocked() { 4652 return mKeyguardManager != null 4653 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); 4654 } 4655 showInputMethodMenu(boolean showAuxSubtypes, int displayId)4656 private void showInputMethodMenu(boolean showAuxSubtypes, int displayId) { 4657 if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes); 4658 4659 final boolean isScreenLocked = isScreenLocked(); 4660 4661 final String lastInputMethodId = mSettings.getSelectedInputMethod(); 4662 int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); 4663 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 4664 4665 synchronized (mMethodMap) { 4666 final List<ImeSubtypeListItem> imList = 4667 mSwitchingController.getSortedInputMethodAndSubtypeListLocked( 4668 showAuxSubtypes, isScreenLocked); 4669 if (imList.isEmpty()) { 4670 return; 4671 } 4672 4673 hideInputMethodMenuLocked(); 4674 4675 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { 4676 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked(); 4677 if (currentSubtype != null) { 4678 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 4679 lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 4680 currentImi, currentSubtype.hashCode()); 4681 } 4682 } 4683 4684 final int N = imList.size(); 4685 mIms = new InputMethodInfo[N]; 4686 mSubtypeIds = new int[N]; 4687 int checkedItem = 0; 4688 for (int i = 0; i < N; ++i) { 4689 final ImeSubtypeListItem item = imList.get(i); 4690 mIms[i] = item.mImi; 4691 mSubtypeIds[i] = item.mSubtypeId; 4692 if (mIms[i].getId().equals(lastInputMethodId)) { 4693 int subtypeId = mSubtypeIds[i]; 4694 if ((subtypeId == NOT_A_SUBTYPE_ID) 4695 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 4696 || (subtypeId == lastInputMethodSubtypeId)) { 4697 checkedItem = i; 4698 } 4699 } 4700 } 4701 4702 final ActivityThread currentThread = ActivityThread.currentActivityThread(); 4703 final Context settingsContext = new ContextThemeWrapper( 4704 displayId == DEFAULT_DISPLAY ? currentThread.getSystemUiContext() 4705 : currentThread.createSystemUiContext(displayId), 4706 com.android.internal.R.style.Theme_DeviceDefault_Settings); 4707 4708 mDialogBuilder = new AlertDialog.Builder(settingsContext); 4709 mDialogBuilder.setOnCancelListener(new OnCancelListener() { 4710 @Override 4711 public void onCancel(DialogInterface dialog) { 4712 hideInputMethodMenu(); 4713 } 4714 }); 4715 4716 final Context dialogContext = mDialogBuilder.getContext(); 4717 final TypedArray a = dialogContext.obtainStyledAttributes(null, 4718 com.android.internal.R.styleable.DialogPreference, 4719 com.android.internal.R.attr.alertDialogStyle, 0); 4720 final Drawable dialogIcon = a.getDrawable( 4721 com.android.internal.R.styleable.DialogPreference_dialogIcon); 4722 a.recycle(); 4723 4724 mDialogBuilder.setIcon(dialogIcon); 4725 4726 final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class); 4727 final View tv = inflater.inflate( 4728 com.android.internal.R.layout.input_method_switch_dialog_title, null); 4729 mDialogBuilder.setCustomTitle(tv); 4730 4731 // Setup layout for a toggle switch of the hardware keyboard 4732 mSwitchingDialogTitleView = tv; 4733 mSwitchingDialogTitleView 4734 .findViewById(com.android.internal.R.id.hard_keyboard_section) 4735 .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable() 4736 ? View.VISIBLE : View.GONE); 4737 final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById( 4738 com.android.internal.R.id.hard_keyboard_switch); 4739 hardKeySwitch.setChecked(mShowImeWithHardKeyboard); 4740 hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() { 4741 @Override 4742 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { 4743 mSettings.setShowImeWithHardKeyboard(isChecked); 4744 // Ensure that the input method dialog is dismissed when changing 4745 // the hardware keyboard state. 4746 hideInputMethodMenu(); 4747 } 4748 }); 4749 4750 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext, 4751 com.android.internal.R.layout.input_method_switch_item, imList, checkedItem); 4752 final OnClickListener choiceListener = new OnClickListener() { 4753 @Override 4754 public void onClick(final DialogInterface dialog, final int which) { 4755 synchronized (mMethodMap) { 4756 if (mIms == null || mIms.length <= which || mSubtypeIds == null 4757 || mSubtypeIds.length <= which) { 4758 return; 4759 } 4760 final InputMethodInfo im = mIms[which]; 4761 int subtypeId = mSubtypeIds[which]; 4762 adapter.mCheckedItem = which; 4763 adapter.notifyDataSetChanged(); 4764 hideInputMethodMenu(); 4765 if (im != null) { 4766 if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) { 4767 subtypeId = NOT_A_SUBTYPE_ID; 4768 } 4769 setInputMethodLocked(im.getId(), subtypeId); 4770 } 4771 } 4772 } 4773 }; 4774 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener); 4775 4776 mSwitchingDialog = mDialogBuilder.create(); 4777 mSwitchingDialog.setCanceledOnTouchOutside(true); 4778 final Window w = mSwitchingDialog.getWindow(); 4779 final LayoutParams attrs = w.getAttributes(); 4780 w.setType(LayoutParams.TYPE_INPUT_METHOD_DIALOG); 4781 // Use an alternate token for the dialog for that window manager can group the token 4782 // with other IME windows based on type vs. grouping based on whichever token happens 4783 // to get selected by the system later on. 4784 attrs.token = mSwitchingDialogToken; 4785 attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS; 4786 attrs.setTitle("Select input method"); 4787 w.setAttributes(attrs); 4788 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 4789 mSwitchingDialog.show(); 4790 } 4791 } 4792 4793 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> { 4794 private final LayoutInflater mInflater; 4795 private final int mTextViewResourceId; 4796 private final List<ImeSubtypeListItem> mItemsList; 4797 public int mCheckedItem; ImeSubtypeListAdapter(Context context, int textViewResourceId, List<ImeSubtypeListItem> itemsList, int checkedItem)4798 public ImeSubtypeListAdapter(Context context, int textViewResourceId, 4799 List<ImeSubtypeListItem> itemsList, int checkedItem) { 4800 super(context, textViewResourceId, itemsList); 4801 4802 mTextViewResourceId = textViewResourceId; 4803 mItemsList = itemsList; 4804 mCheckedItem = checkedItem; 4805 mInflater = context.getSystemService(LayoutInflater.class); 4806 } 4807 4808 @Override getView(int position, View convertView, ViewGroup parent)4809 public View getView(int position, View convertView, ViewGroup parent) { 4810 final View view = convertView != null ? convertView 4811 : mInflater.inflate(mTextViewResourceId, null); 4812 if (position < 0 || position >= mItemsList.size()) return view; 4813 final ImeSubtypeListItem item = mItemsList.get(position); 4814 final CharSequence imeName = item.mImeName; 4815 final CharSequence subtypeName = item.mSubtypeName; 4816 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1); 4817 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2); 4818 if (TextUtils.isEmpty(subtypeName)) { 4819 firstTextView.setText(imeName); 4820 secondTextView.setVisibility(View.GONE); 4821 } else { 4822 firstTextView.setText(subtypeName); 4823 secondTextView.setText(imeName); 4824 secondTextView.setVisibility(View.VISIBLE); 4825 } 4826 final RadioButton radioButton = 4827 (RadioButton)view.findViewById(com.android.internal.R.id.radio); 4828 radioButton.setChecked(position == mCheckedItem); 4829 return view; 4830 } 4831 } 4832 hideInputMethodMenu()4833 void hideInputMethodMenu() { 4834 synchronized (mMethodMap) { 4835 hideInputMethodMenuLocked(); 4836 } 4837 } 4838 hideInputMethodMenuLocked()4839 void hideInputMethodMenuLocked() { 4840 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 4841 4842 if (mSwitchingDialog != null) { 4843 mSwitchingDialog.dismiss(); 4844 mSwitchingDialog = null; 4845 mSwitchingDialogTitleView = null; 4846 } 4847 4848 updateSystemUiLocked(mImeWindowVis, mBackDisposition); 4849 mDialogBuilder = null; 4850 mIms = null; 4851 } 4852 4853 // ---------------------------------------------------------------------- 4854 4855 /** 4856 * Enable or disable the given IME by updating {@link Settings.Secure#ENABLED_INPUT_METHODS}. 4857 * 4858 * @param id ID of the IME is to be manipulated. It is OK to pass IME ID that is currently not 4859 * recognized by the system. 4860 * @param enabled {@code true} if {@code id} needs to be enabled. 4861 * @return {@code true} if the IME was previously enabled. {@code false} otherwise. 4862 */ setInputMethodEnabledLocked(String id, boolean enabled)4863 private boolean setInputMethodEnabledLocked(String id, boolean enabled) { 4864 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 4865 .getEnabledInputMethodsAndSubtypeListLocked(); 4866 4867 if (enabled) { 4868 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 4869 if (pair.first.equals(id)) { 4870 // We are enabling this input method, but it is already enabled. 4871 // Nothing to do. The previous state was enabled. 4872 return true; 4873 } 4874 } 4875 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 4876 // Previous state was disabled. 4877 return false; 4878 } else { 4879 StringBuilder builder = new StringBuilder(); 4880 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 4881 builder, enabledInputMethodsList, id)) { 4882 // Disabled input method is currently selected, switch to another one. 4883 final String selId = mSettings.getSelectedInputMethod(); 4884 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 4885 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 4886 resetSelectedInputMethodAndSubtypeLocked(""); 4887 } 4888 // Previous state was enabled. 4889 return true; 4890 } else { 4891 // We are disabling the input method but it is already disabled. 4892 // Nothing to do. The previous state was disabled. 4893 return false; 4894 } 4895 } 4896 } 4897 setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, boolean setSubtypeOnly)4898 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 4899 boolean setSubtypeOnly) { 4900 mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); 4901 4902 // Set Subtype here 4903 if (imi == null || subtypeId < 0) { 4904 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 4905 mCurrentSubtype = null; 4906 } else { 4907 if (subtypeId < imi.getSubtypeCount()) { 4908 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); 4909 mSettings.putSelectedSubtype(subtype.hashCode()); 4910 mCurrentSubtype = subtype; 4911 } else { 4912 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 4913 // If the subtype is not specified, choose the most applicable one 4914 mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); 4915 } 4916 } 4917 4918 if (!setSubtypeOnly) { 4919 // Set InputMethod here 4920 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 4921 } 4922 } 4923 resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme)4924 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 4925 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 4926 int lastSubtypeId = NOT_A_SUBTYPE_ID; 4927 // newDefaultIme is empty when there is no candidate for the selected IME. 4928 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 4929 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 4930 if (subtypeHashCode != null) { 4931 try { 4932 lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 4933 imi, Integer.parseInt(subtypeHashCode)); 4934 } catch (NumberFormatException e) { 4935 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 4936 } 4937 } 4938 } 4939 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 4940 } 4941 4942 /** 4943 * @return Return the current subtype of this input method. 4944 */ 4945 @Override getCurrentInputMethodSubtype()4946 public InputMethodSubtype getCurrentInputMethodSubtype() { 4947 synchronized (mMethodMap) { 4948 // TODO: Make this work even for non-current users? 4949 if (!calledFromValidUserLocked()) { 4950 return null; 4951 } 4952 return getCurrentInputMethodSubtypeLocked(); 4953 } 4954 } 4955 getCurrentInputMethodSubtypeLocked()4956 private InputMethodSubtype getCurrentInputMethodSubtypeLocked() { 4957 if (mCurMethodId == null) { 4958 return null; 4959 } 4960 final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); 4961 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 4962 if (imi == null || imi.getSubtypeCount() == 0) { 4963 return null; 4964 } 4965 if (!subtypeIsSelected || mCurrentSubtype == null 4966 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { 4967 int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId); 4968 if (subtypeId == NOT_A_SUBTYPE_ID) { 4969 // If there are no selected subtypes, the framework will try to find 4970 // the most applicable subtype from explicitly or implicitly enabled 4971 // subtypes. 4972 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 4973 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 4974 // If there is only one explicitly or implicitly enabled subtype, 4975 // just returns it. 4976 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 4977 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); 4978 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { 4979 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 4980 mRes, explicitlyOrImplicitlyEnabledSubtypes, 4981 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true); 4982 if (mCurrentSubtype == null) { 4983 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 4984 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, 4985 true); 4986 } 4987 } 4988 } else { 4989 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId); 4990 } 4991 } 4992 return mCurrentSubtype; 4993 } 4994 getInputMethodListAsUser(@serIdInt int userId)4995 private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) { 4996 synchronized (mMethodMap) { 4997 return getInputMethodListLocked(userId); 4998 } 4999 } 5000 getEnabledInputMethodListAsUser(@serIdInt int userId)5001 private List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) { 5002 synchronized (mMethodMap) { 5003 return getEnabledInputMethodListLocked(userId); 5004 } 5005 } 5006 onCreateInlineSuggestionsRequest(@serIdInt int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback callback)5007 private void onCreateInlineSuggestionsRequest(@UserIdInt int userId, 5008 InlineSuggestionsRequestInfo requestInfo, 5009 IInlineSuggestionsRequestCallback callback) { 5010 synchronized (mMethodMap) { 5011 onCreateInlineSuggestionsRequestLocked(userId, requestInfo, callback); 5012 } 5013 } 5014 switchToInputMethod(String imeId, @UserIdInt int userId)5015 private boolean switchToInputMethod(String imeId, @UserIdInt int userId) { 5016 synchronized (mMethodMap) { 5017 if (userId == mSettings.getCurrentUserId()) { 5018 if (!mMethodMap.containsKey(imeId) 5019 || !mSettings.getEnabledInputMethodListLocked() 5020 .contains(mMethodMap.get(imeId))) { 5021 return false; // IME is not is found or not enabled. 5022 } 5023 setInputMethodLocked(imeId, NOT_A_SUBTYPE_ID); 5024 return true; 5025 } 5026 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 5027 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); 5028 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 5029 new ArrayMap<>(); 5030 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 5031 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, 5032 methodMap, methodList); 5033 final InputMethodSettings settings = new InputMethodSettings( 5034 mContext.getResources(), mContext.getContentResolver(), methodMap, 5035 userId, false); 5036 if (!methodMap.containsKey(imeId) 5037 || !settings.getEnabledInputMethodListLocked() 5038 .contains(methodMap.get(imeId))) { 5039 return false; // IME is not is found or not enabled. 5040 } 5041 settings.putSelectedInputMethod(imeId); 5042 settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 5043 return true; 5044 } 5045 } 5046 transferTouchFocusToImeWindow(@onNull IBinder sourceInputToken, int displayId)5047 private boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, 5048 int displayId) { 5049 //TODO(b/150843766): Check if Input Token is valid. 5050 final IBinder curHostInputToken; 5051 synchronized (mMethodMap) { 5052 if (displayId != mCurTokenDisplayId || mCurHostInputToken == null) { 5053 return false; 5054 } 5055 curHostInputToken = mCurHostInputToken; 5056 } 5057 return mInputManagerInternal.transferTouchFocus(sourceInputToken, curHostInputToken); 5058 } 5059 reportImeControl(@ullable IBinder windowToken)5060 private void reportImeControl(@Nullable IBinder windowToken) { 5061 synchronized (mMethodMap) { 5062 if (mCurFocusedWindow != windowToken) { 5063 // mCurPerceptible was set by the focused window, but it is no longer in control, 5064 // so we reset mCurPerceptible. 5065 mCurPerceptible = true; 5066 } 5067 } 5068 } 5069 5070 private static final class LocalServiceImpl extends InputMethodManagerInternal { 5071 @NonNull 5072 private final InputMethodManagerService mService; 5073 LocalServiceImpl(@onNull InputMethodManagerService service)5074 LocalServiceImpl(@NonNull InputMethodManagerService service) { 5075 mService = service; 5076 } 5077 5078 @Override setInteractive(boolean interactive)5079 public void setInteractive(boolean interactive) { 5080 // Do everything in handler so as not to block the caller. 5081 mService.mHandler.obtainMessage(MSG_SET_INTERACTIVE, interactive ? 1 : 0, 0) 5082 .sendToTarget(); 5083 } 5084 5085 @Override hideCurrentInputMethod(@oftInputShowHideReason int reason)5086 public void hideCurrentInputMethod(@SoftInputShowHideReason int reason) { 5087 mService.mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD); 5088 mService.mHandler.obtainMessage(MSG_HIDE_CURRENT_INPUT_METHOD, reason).sendToTarget(); 5089 } 5090 5091 @Override getInputMethodListAsUser(int userId)5092 public List<InputMethodInfo> getInputMethodListAsUser(int userId) { 5093 return mService.getInputMethodListAsUser(userId); 5094 } 5095 5096 @Override getEnabledInputMethodListAsUser(int userId)5097 public List<InputMethodInfo> getEnabledInputMethodListAsUser(int userId) { 5098 return mService.getEnabledInputMethodListAsUser(userId); 5099 } 5100 5101 @Override onCreateInlineSuggestionsRequest(int userId, InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb)5102 public void onCreateInlineSuggestionsRequest(int userId, 5103 InlineSuggestionsRequestInfo requestInfo, IInlineSuggestionsRequestCallback cb) { 5104 mService.onCreateInlineSuggestionsRequest(userId, requestInfo, cb); 5105 } 5106 5107 @Override switchToInputMethod(String imeId, int userId)5108 public boolean switchToInputMethod(String imeId, int userId) { 5109 return mService.switchToInputMethod(imeId, userId); 5110 } 5111 5112 @Override registerInputMethodListListener(InputMethodListListener listener)5113 public void registerInputMethodListListener(InputMethodListListener listener) { 5114 mService.mInputMethodListListeners.addIfAbsent(listener); 5115 } 5116 5117 @Override transferTouchFocusToImeWindow(@onNull IBinder sourceInputToken, int displayId)5118 public boolean transferTouchFocusToImeWindow(@NonNull IBinder sourceInputToken, 5119 int displayId) { 5120 return mService.transferTouchFocusToImeWindow(sourceInputToken, displayId); 5121 } 5122 5123 @Override reportImeControl(@ullable IBinder windowToken)5124 public void reportImeControl(@Nullable IBinder windowToken) { 5125 mService.reportImeControl(windowToken); 5126 } 5127 5128 @Override removeImeSurface()5129 public void removeImeSurface() { 5130 mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_REMOVE_IME_SURFACE)); 5131 } 5132 } 5133 5134 @BinderThread createInputContentUriToken(@ullable IBinder token, @Nullable Uri contentUri, @Nullable String packageName)5135 private IInputContentUriToken createInputContentUriToken(@Nullable IBinder token, 5136 @Nullable Uri contentUri, @Nullable String packageName) { 5137 if (token == null) { 5138 throw new NullPointerException("token"); 5139 } 5140 if (packageName == null) { 5141 throw new NullPointerException("packageName"); 5142 } 5143 if (contentUri == null) { 5144 throw new NullPointerException("contentUri"); 5145 } 5146 final String contentUriScheme = contentUri.getScheme(); 5147 if (!"content".equals(contentUriScheme)) { 5148 throw new InvalidParameterException("contentUri must have content scheme"); 5149 } 5150 5151 synchronized (mMethodMap) { 5152 final int uid = Binder.getCallingUid(); 5153 if (mCurMethodId == null) { 5154 return null; 5155 } 5156 if (mCurToken != token) { 5157 Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken 5158 + " token=" + token); 5159 return null; 5160 } 5161 // We cannot simply distinguish a bad IME that reports an arbitrary package name from 5162 // an unfortunate IME whose internal state is already obsolete due to the asynchronous 5163 // nature of our system. Let's compare it with our internal record. 5164 if (!TextUtils.equals(mCurAttribute.packageName, packageName)) { 5165 Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName=" 5166 + mCurAttribute.packageName + " packageName=" + packageName); 5167 return null; 5168 } 5169 // This user ID can never bee spoofed. 5170 final int imeUserId = UserHandle.getUserId(uid); 5171 // This user ID can never bee spoofed. 5172 final int appUserId = UserHandle.getUserId(mCurClient.uid); 5173 // This user ID may be invalid if "contentUri" embedded an invalid user ID. 5174 final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri, 5175 imeUserId); 5176 final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri); 5177 // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid") 5178 // actually has the right to grant a read permission for "contentUriWithoutUserId" that 5179 // is claimed to belong to "contentUriOwnerUserId". For example, specifying random 5180 // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown 5181 // from InputContentUriTokenHandler.take() and can never be allowed beyond what is 5182 // actually allowed to "uid", which is guaranteed to be the IME's one. 5183 return new InputContentUriTokenHandler(contentUriWithoutUserId, uid, 5184 packageName, contentUriOwnerUserId, appUserId); 5185 } 5186 } 5187 5188 @BinderThread reportFullscreenMode(@onNull IBinder token, boolean fullscreen)5189 private void reportFullscreenMode(@NonNull IBinder token, boolean fullscreen) { 5190 synchronized (mMethodMap) { 5191 if (!calledWithValidTokenLocked(token)) { 5192 return; 5193 } 5194 if (mCurClient != null && mCurClient.client != null) { 5195 mInFullscreenMode = fullscreen; 5196 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 5197 MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient)); 5198 } 5199 } 5200 } 5201 5202 @Override dump(FileDescriptor fd, PrintWriter pw, String[] args)5203 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 5204 if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return; 5205 5206 IInputMethod method; 5207 ClientState client; 5208 ClientState focusedWindowClient; 5209 5210 final Printer p = new PrintWriterPrinter(pw); 5211 5212 synchronized (mMethodMap) { 5213 p.println("Current Input Method Manager state:"); 5214 int N = mMethodList.size(); 5215 p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount); 5216 for (int i=0; i<N; i++) { 5217 InputMethodInfo info = mMethodList.get(i); 5218 p.println(" InputMethod #" + i + ":"); 5219 info.dump(p, " "); 5220 } 5221 p.println(" Clients:"); 5222 final int numClients = mClients.size(); 5223 for (int i = 0; i < numClients; ++i) { 5224 final ClientState ci = mClients.valueAt(i); 5225 p.println(" Client " + ci + ":"); 5226 p.println(" client=" + ci.client); 5227 p.println(" inputContext=" + ci.inputContext); 5228 p.println(" sessionRequested=" + ci.sessionRequested); 5229 p.println(" curSession=" + ci.curSession); 5230 } 5231 p.println(" mCurMethodId=" + mCurMethodId); 5232 client = mCurClient; 5233 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 5234 p.println(" mCurPerceptible=" + mCurPerceptible); 5235 p.println(" mCurFocusedWindow=" + mCurFocusedWindow 5236 + " softInputMode=" + 5237 InputMethodDebug.softInputModeToString(mCurFocusedWindowSoftInputMode) 5238 + " client=" + mCurFocusedWindowClient); 5239 focusedWindowClient = mCurFocusedWindowClient; 5240 p.println(" mCurId=" + mCurId + " mHaveConnection=" + mHaveConnection 5241 + " mBoundToMethod=" + mBoundToMethod + " mVisibleBound=" + mVisibleBound); 5242 p.println(" mCurToken=" + mCurToken); 5243 p.println(" mCurTokenDisplayId=" + mCurTokenDisplayId); 5244 p.println(" mCurHostInputToken=" + mCurHostInputToken); 5245 p.println(" mCurIntent=" + mCurIntent); 5246 method = mCurMethod; 5247 p.println(" mCurMethod=" + mCurMethod); 5248 p.println(" mEnabledSession=" + mEnabledSession); 5249 p.println(" mShowRequested=" + mShowRequested 5250 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 5251 + " mShowForced=" + mShowForced 5252 + " mInputShown=" + mInputShown); 5253 p.println(" mInFullscreenMode=" + mInFullscreenMode); 5254 p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive); 5255 p.println(" mSettingsObserver=" + mSettingsObserver); 5256 p.println(" mSwitchingController:"); 5257 mSwitchingController.dump(p); 5258 p.println(" mSettings:"); 5259 mSettings.dumpLocked(p, " "); 5260 5261 p.println(" mStartInputHistory:"); 5262 mStartInputHistory.dump(pw, " "); 5263 5264 p.println(" mSoftInputShowHideHistory:"); 5265 mSoftInputShowHideHistory.dump(pw, " "); 5266 } 5267 5268 p.println(" "); 5269 if (client != null) { 5270 pw.flush(); 5271 try { 5272 TransferPipe.dumpAsync(client.client.asBinder(), fd, args); 5273 } catch (IOException | RemoteException e) { 5274 p.println("Failed to dump input method client: " + e); 5275 } 5276 } else { 5277 p.println("No input method client."); 5278 } 5279 5280 if (focusedWindowClient != null && client != focusedWindowClient) { 5281 p.println(" "); 5282 p.println("Warning: Current input method client doesn't match the last focused. " 5283 + "window."); 5284 p.println("Dumping input method client in the last focused window just in case."); 5285 p.println(" "); 5286 pw.flush(); 5287 try { 5288 TransferPipe.dumpAsync(focusedWindowClient.client.asBinder(), fd, args); 5289 } catch (IOException | RemoteException e) { 5290 p.println("Failed to dump input method client in focused window: " + e); 5291 } 5292 } 5293 5294 p.println(" "); 5295 if (method != null) { 5296 pw.flush(); 5297 try { 5298 TransferPipe.dumpAsync(method.asBinder(), fd, args); 5299 } catch (IOException | RemoteException e) { 5300 p.println("Failed to dump input method service: " + e); 5301 } 5302 } else { 5303 p.println("No input method service."); 5304 } 5305 } 5306 5307 @BinderThread 5308 @Override onShellCommand(@ullable FileDescriptor in, @Nullable FileDescriptor out, @Nullable FileDescriptor err, @NonNull String[] args, @Nullable ShellCallback callback, @NonNull ResultReceiver resultReceiver)5309 public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out, 5310 @Nullable FileDescriptor err, 5311 @NonNull String[] args, @Nullable ShellCallback callback, 5312 @NonNull ResultReceiver resultReceiver) throws RemoteException { 5313 final int callingUid = Binder.getCallingUid(); 5314 // Reject any incoming calls from non-shell users, including ones from the system user. 5315 if (callingUid != Process.ROOT_UID && callingUid != Process.SHELL_UID) { 5316 // Note that Binder#onTransact() will automatically close "in", "out", and "err" when 5317 // returned from this method, hence there is no need to close those FDs. 5318 // "resultReceiver" is the only thing that needs to be taken care of here. 5319 if (resultReceiver != null) { 5320 resultReceiver.send(ShellCommandResult.FAILURE, null); 5321 } 5322 final String errorMsg = "InputMethodManagerService does not support shell commands from" 5323 + " non-shell users. callingUid=" + callingUid 5324 + " args=" + Arrays.toString(args); 5325 if (Process.isCoreUid(callingUid)) { 5326 // Let's not crash the calling process if the caller is one of core components. 5327 Slog.e(TAG, errorMsg); 5328 return; 5329 } 5330 throw new SecurityException(errorMsg); 5331 } 5332 new ShellCommandImpl(this).exec( 5333 this, in, out, err, args, callback, resultReceiver); 5334 } 5335 5336 private static final class ShellCommandImpl extends ShellCommand { 5337 @NonNull 5338 final InputMethodManagerService mService; 5339 ShellCommandImpl(InputMethodManagerService service)5340 ShellCommandImpl(InputMethodManagerService service) { 5341 mService = service; 5342 } 5343 5344 @RequiresPermission(allOf = { 5345 Manifest.permission.DUMP, 5346 Manifest.permission.INTERACT_ACROSS_USERS_FULL, 5347 Manifest.permission.WRITE_SECURE_SETTINGS, 5348 }) 5349 @BinderThread 5350 @ShellCommandResult 5351 @Override onCommand(@ullable String cmd)5352 public int onCommand(@Nullable String cmd) { 5353 // For shell command, require all the permissions here in favor of code simplicity. 5354 Arrays.asList( 5355 Manifest.permission.DUMP, 5356 Manifest.permission.INTERACT_ACROSS_USERS_FULL, 5357 Manifest.permission.WRITE_SECURE_SETTINGS 5358 ).forEach(permission -> mService.mContext.enforceCallingPermission(permission, null)); 5359 5360 final long identity = Binder.clearCallingIdentity(); 5361 try { 5362 return onCommandWithSystemIdentity(cmd); 5363 } finally { 5364 Binder.restoreCallingIdentity(identity); 5365 } 5366 } 5367 5368 @BinderThread 5369 @ShellCommandResult onCommandWithSystemIdentity(@ullable String cmd)5370 private int onCommandWithSystemIdentity(@Nullable String cmd) { 5371 if ("refresh_debug_properties".equals(cmd)) { 5372 return refreshDebugProperties(); 5373 } 5374 5375 if ("get-last-switch-user-id".equals(cmd)) { 5376 return mService.getLastSwitchUserId(this); 5377 } 5378 5379 // For existing "adb shell ime <command>". 5380 if ("ime".equals(cmd)) { 5381 final String imeCommand = getNextArg(); 5382 if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) { 5383 onImeCommandHelp(); 5384 return ShellCommandResult.SUCCESS; 5385 } 5386 switch (imeCommand) { 5387 case "list": 5388 return mService.handleShellCommandListInputMethods(this); 5389 case "enable": 5390 return mService.handleShellCommandEnableDisableInputMethod(this, true); 5391 case "disable": 5392 return mService.handleShellCommandEnableDisableInputMethod(this, false); 5393 case "set": 5394 return mService.handleShellCommandSetInputMethod(this); 5395 case "reset": 5396 return mService.handleShellCommandResetInputMethod(this); 5397 default: 5398 getOutPrintWriter().println("Unknown command: " + imeCommand); 5399 return ShellCommandResult.FAILURE; 5400 } 5401 } 5402 5403 return handleDefaultCommands(cmd); 5404 } 5405 5406 @BinderThread 5407 @ShellCommandResult refreshDebugProperties()5408 private int refreshDebugProperties() { 5409 DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh(); 5410 DebugFlags.FLAG_PRE_RENDER_IME_VIEWS.refresh(); 5411 return ShellCommandResult.SUCCESS; 5412 } 5413 5414 @BinderThread 5415 @Override onHelp()5416 public void onHelp() { 5417 try (PrintWriter pw = getOutPrintWriter()) { 5418 pw.println("InputMethodManagerService commands:"); 5419 pw.println(" help"); 5420 pw.println(" Prints this help text."); 5421 pw.println(" dump [options]"); 5422 pw.println(" Synonym of dumpsys."); 5423 pw.println(" ime <command> [options]"); 5424 pw.println(" Manipulate IMEs. Run \"ime help\" for details."); 5425 } 5426 } 5427 onImeCommandHelp()5428 private void onImeCommandHelp() { 5429 try (IndentingPrintWriter pw = 5430 new IndentingPrintWriter(getOutPrintWriter(), " ", 100)) { 5431 pw.println("ime <command>:"); 5432 pw.increaseIndent(); 5433 5434 pw.println("list [-a] [-s]"); 5435 pw.increaseIndent(); 5436 pw.println("prints all enabled input methods."); 5437 pw.increaseIndent(); 5438 pw.println("-a: see all input methods"); 5439 pw.println("-s: only a single summary line of each"); 5440 pw.decreaseIndent(); 5441 pw.decreaseIndent(); 5442 5443 pw.println("enable [--user <USER_ID>] <ID>"); 5444 pw.increaseIndent(); 5445 pw.println("allows the given input method ID to be used."); 5446 pw.increaseIndent(); 5447 pw.print("--user <USER_ID>: Specify which user to enable."); 5448 pw.println(" Assumes the current user if not specified."); 5449 pw.decreaseIndent(); 5450 pw.decreaseIndent(); 5451 5452 pw.println("disable [--user <USER_ID>] <ID>"); 5453 pw.increaseIndent(); 5454 pw.println("disallows the given input method ID to be used."); 5455 pw.increaseIndent(); 5456 pw.print("--user <USER_ID>: Specify which user to disable."); 5457 pw.println(" Assumes the current user if not specified."); 5458 pw.decreaseIndent(); 5459 pw.decreaseIndent(); 5460 5461 pw.println("set [--user <USER_ID>] <ID>"); 5462 pw.increaseIndent(); 5463 pw.println("switches to the given input method ID."); 5464 pw.increaseIndent(); 5465 pw.print("--user <USER_ID>: Specify which user to enable."); 5466 pw.println(" Assumes the current user if not specified."); 5467 pw.decreaseIndent(); 5468 pw.decreaseIndent(); 5469 5470 pw.println("reset [--user <USER_ID>]"); 5471 pw.increaseIndent(); 5472 pw.println("reset currently selected/enabled IMEs to the default ones as if " 5473 + "the device is initially booted with the current locale."); 5474 pw.increaseIndent(); 5475 pw.print("--user <USER_ID>: Specify which user to reset."); 5476 pw.println(" Assumes the current user if not specified."); 5477 pw.decreaseIndent(); 5478 5479 pw.decreaseIndent(); 5480 5481 pw.decreaseIndent(); 5482 } 5483 } 5484 } 5485 5486 // ---------------------------------------------------------------------- 5487 // Shell command handlers: 5488 5489 @BinderThread 5490 @ShellCommandResult getLastSwitchUserId(@onNull ShellCommand shellCommand)5491 private int getLastSwitchUserId(@NonNull ShellCommand shellCommand) { 5492 synchronized (mMethodMap) { 5493 shellCommand.getOutPrintWriter().println(mLastSwitchUserId); 5494 return ShellCommandResult.SUCCESS; 5495 } 5496 } 5497 5498 /** 5499 * Handles {@code adb shell ime list}. 5500 * @param shellCommand {@link ShellCommand} object that is handling this command. 5501 * @return Exit code of the command. 5502 */ 5503 @BinderThread 5504 @ShellCommandResult handleShellCommandListInputMethods(@onNull ShellCommand shellCommand)5505 private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) { 5506 boolean all = false; 5507 boolean brief = false; 5508 int userIdToBeResolved = UserHandle.USER_CURRENT; 5509 while (true) { 5510 final String nextOption = shellCommand.getNextOption(); 5511 if (nextOption == null) { 5512 break; 5513 } 5514 switch (nextOption) { 5515 case "-a": 5516 all = true; 5517 break; 5518 case "-s": 5519 brief = true; 5520 break; 5521 case "-u": 5522 case "--user": 5523 userIdToBeResolved = UserHandle.parseUserArg(shellCommand.getNextArgRequired()); 5524 break; 5525 } 5526 } 5527 synchronized (mMethodMap) { 5528 final PrintWriter pr = shellCommand.getOutPrintWriter(); 5529 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 5530 mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); 5531 for (int userId : userIds) { 5532 final List<InputMethodInfo> methods = all 5533 ? getInputMethodListLocked(userId) 5534 : getEnabledInputMethodListLocked(userId); 5535 if (userIds.length > 1) { 5536 pr.print("User #"); 5537 pr.print(userId); 5538 pr.println(":"); 5539 } 5540 for (InputMethodInfo info : methods) { 5541 if (brief) { 5542 pr.println(info.getId()); 5543 } else { 5544 pr.print(info.getId()); 5545 pr.println(":"); 5546 info.dump(pr::println, " "); 5547 } 5548 } 5549 } 5550 } 5551 return ShellCommandResult.SUCCESS; 5552 } 5553 5554 /** 5555 * Handles {@code adb shell ime enable} and {@code adb shell ime disable}. 5556 * @param shellCommand {@link ShellCommand} object that is handling this command. 5557 * @param enabled {@code true} if the command was {@code adb shell ime enable}. 5558 * @return Exit code of the command. 5559 */ 5560 @BinderThread 5561 @ShellCommandResult handleShellCommandEnableDisableInputMethod( @onNull ShellCommand shellCommand, boolean enabled)5562 private int handleShellCommandEnableDisableInputMethod( 5563 @NonNull ShellCommand shellCommand, boolean enabled) { 5564 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 5565 final String imeId = shellCommand.getNextArgRequired(); 5566 final PrintWriter out = shellCommand.getOutPrintWriter(); 5567 final PrintWriter error = shellCommand.getErrPrintWriter(); 5568 synchronized (mMethodMap) { 5569 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 5570 mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); 5571 for (int userId : userIds) { 5572 if (!userHasDebugPriv(userId, shellCommand)) { 5573 continue; 5574 } 5575 handleShellCommandEnableDisableInputMethodInternalLocked(userId, imeId, enabled, 5576 out, error); 5577 } 5578 } 5579 return ShellCommandResult.SUCCESS; 5580 } 5581 5582 /** 5583 * A special helper method for commands that only have {@code -u} and {@code --user} options. 5584 * 5585 * <p>You cannot use this helper method if the command has other options.</p> 5586 * 5587 * <p>CAVEAT: This method must be called only once before any other 5588 * {@link ShellCommand#getNextArg()} and {@link ShellCommand#getNextArgRequired()} for the 5589 * main arguments.</p> 5590 * 5591 * @param shellCommand {@link ShellCommand} from which options should be obtained. 5592 * @return User ID to be resolved. {@link UserHandle#CURRENT} if not specified. 5593 */ 5594 @BinderThread 5595 @UserIdInt handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand)5596 private static int handleOptionsForCommandsThatOnlyHaveUserOption(ShellCommand shellCommand) { 5597 while (true) { 5598 final String nextOption = shellCommand.getNextOption(); 5599 if (nextOption == null) { 5600 break; 5601 } 5602 switch (nextOption) { 5603 case "-u": 5604 case "--user": 5605 return UserHandle.parseUserArg(shellCommand.getNextArgRequired()); 5606 } 5607 } 5608 return UserHandle.USER_CURRENT; 5609 } 5610 5611 @BinderThread handleShellCommandEnableDisableInputMethodInternalLocked( @serIdInt int userId, String imeId, boolean enabled, PrintWriter out, PrintWriter error)5612 private void handleShellCommandEnableDisableInputMethodInternalLocked( 5613 @UserIdInt int userId, String imeId, boolean enabled, PrintWriter out, 5614 PrintWriter error) { 5615 boolean failedToEnableUnknownIme = false; 5616 boolean previouslyEnabled = false; 5617 if (userId == mSettings.getCurrentUserId()) { 5618 if (enabled && !mMethodMap.containsKey(imeId)) { 5619 failedToEnableUnknownIme = true; 5620 } else { 5621 previouslyEnabled = setInputMethodEnabledLocked(imeId, enabled); 5622 } 5623 } else { 5624 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 5625 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); 5626 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 5627 new ArrayMap<>(); 5628 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 5629 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, 5630 methodMap, methodList); 5631 final InputMethodSettings settings = new InputMethodSettings(mContext.getResources(), 5632 mContext.getContentResolver(), methodMap, userId, false); 5633 if (enabled) { 5634 if (!methodMap.containsKey(imeId)) { 5635 failedToEnableUnknownIme = true; 5636 } else { 5637 for (InputMethodInfo imi : settings.getEnabledInputMethodListLocked()) { 5638 if (TextUtils.equals(imi.getId(), imeId)) { 5639 previouslyEnabled = true; 5640 break; 5641 } 5642 } 5643 if (!previouslyEnabled) { 5644 settings.appendAndPutEnabledInputMethodLocked(imeId, false); 5645 } 5646 } 5647 } else { 5648 previouslyEnabled = 5649 settings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 5650 new StringBuilder(), 5651 settings.getEnabledInputMethodsAndSubtypeListLocked(), imeId); 5652 } 5653 } 5654 if (failedToEnableUnknownIme) { 5655 error.print("Unknown input method "); 5656 error.print(imeId); 5657 error.println(" cannot be enabled for user #" + userId); 5658 } else { 5659 out.print("Input method "); 5660 out.print(imeId); 5661 out.print(": "); 5662 out.print((enabled == previouslyEnabled) ? "already " : "now "); 5663 out.print(enabled ? "enabled" : "disabled"); 5664 out.print(" for user #"); 5665 out.println(userId); 5666 } 5667 } 5668 5669 /** 5670 * Handles {@code adb shell ime set}. 5671 * @param shellCommand {@link ShellCommand} object that is handling this command. 5672 * @return Exit code of the command. 5673 */ 5674 @BinderThread 5675 @ShellCommandResult handleShellCommandSetInputMethod(@onNull ShellCommand shellCommand)5676 private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) { 5677 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 5678 final String imeId = shellCommand.getNextArgRequired(); 5679 final PrintWriter out = shellCommand.getOutPrintWriter(); 5680 final PrintWriter error = shellCommand.getErrPrintWriter(); 5681 synchronized (mMethodMap) { 5682 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 5683 mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); 5684 for (int userId : userIds) { 5685 if (!userHasDebugPriv(userId, shellCommand)) { 5686 continue; 5687 } 5688 boolean failedToSelectUnknownIme = !switchToInputMethod(imeId, userId); 5689 if (failedToSelectUnknownIme) { 5690 error.print("Unknown input method "); 5691 error.print(imeId); 5692 error.print(" cannot be selected for user #"); 5693 error.println(userId); 5694 } else { 5695 out.print("Input method "); 5696 out.print(imeId); 5697 out.print(" selected for user #"); 5698 out.println(userId); 5699 } 5700 } 5701 } 5702 return ShellCommandResult.SUCCESS; 5703 } 5704 5705 /** 5706 * Handles {@code adb shell ime reset-ime}. 5707 * @param shellCommand {@link ShellCommand} object that is handling this command. 5708 * @return Exit code of the command. 5709 */ 5710 @BinderThread 5711 @ShellCommandResult handleShellCommandResetInputMethod(@onNull ShellCommand shellCommand)5712 private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) { 5713 final PrintWriter out = shellCommand.getOutPrintWriter(); 5714 final int userIdToBeResolved = handleOptionsForCommandsThatOnlyHaveUserOption(shellCommand); 5715 synchronized (mMethodMap) { 5716 final int[] userIds = InputMethodUtils.resolveUserId(userIdToBeResolved, 5717 mSettings.getCurrentUserId(), shellCommand.getErrPrintWriter()); 5718 for (int userId : userIds) { 5719 if (!userHasDebugPriv(userId, shellCommand)) { 5720 continue; 5721 } 5722 final String nextIme; 5723 final List<InputMethodInfo> nextEnabledImes; 5724 if (userId == mSettings.getCurrentUserId()) { 5725 hideCurrentInputLocked(mCurFocusedWindow, 0, null, 5726 SoftInputShowHideReason.HIDE_RESET_SHELL_COMMAND); 5727 unbindCurrentMethodLocked(); 5728 // Reset the current IME 5729 resetSelectedInputMethodAndSubtypeLocked(null); 5730 // Also reset the settings of the current IME 5731 mSettings.putSelectedInputMethod(null); 5732 // Disable all enabled IMEs. 5733 mSettings.getEnabledInputMethodListLocked().forEach( 5734 imi -> setInputMethodEnabledLocked(imi.getId(), false)); 5735 // Re-enable with default enabled IMEs. 5736 InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList).forEach( 5737 imi -> setInputMethodEnabledLocked(imi.getId(), true)); 5738 updateInputMethodsFromSettingsLocked(true /* enabledMayChange */); 5739 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager, 5740 mSettings.getEnabledInputMethodListLocked(), 5741 mSettings.getCurrentUserId(), 5742 mContext.getBasePackageName()); 5743 nextIme = mSettings.getSelectedInputMethod(); 5744 nextEnabledImes = mSettings.getEnabledInputMethodListLocked(); 5745 } else { 5746 final ArrayMap<String, InputMethodInfo> methodMap = new ArrayMap<>(); 5747 final ArrayList<InputMethodInfo> methodList = new ArrayList<>(); 5748 final ArrayMap<String, List<InputMethodSubtype>> additionalSubtypeMap = 5749 new ArrayMap<>(); 5750 AdditionalSubtypeUtils.load(additionalSubtypeMap, userId); 5751 queryInputMethodServicesInternal(mContext, userId, additionalSubtypeMap, 5752 methodMap, methodList); 5753 final InputMethodSettings settings = new InputMethodSettings( 5754 mContext.getResources(), mContext.getContentResolver(), methodMap, 5755 userId, false); 5756 5757 nextEnabledImes = InputMethodUtils.getDefaultEnabledImes(mContext, methodList); 5758 nextIme = InputMethodUtils.getMostApplicableDefaultIME(nextEnabledImes).getId(); 5759 5760 // Reset enabled IMEs. 5761 settings.putEnabledInputMethodsStr(""); 5762 nextEnabledImes.forEach(imi -> settings.appendAndPutEnabledInputMethodLocked( 5763 imi.getId(), false)); 5764 5765 // Reset selected IME. 5766 settings.putSelectedInputMethod(nextIme); 5767 settings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 5768 } 5769 out.println("Reset current and enabled IMEs for user #" + userId); 5770 out.println(" Selected: " + nextIme); 5771 nextEnabledImes.forEach(ime -> out.println(" Enabled: " + ime.getId())); 5772 } 5773 } 5774 return ShellCommandResult.SUCCESS; 5775 } 5776 5777 /** 5778 * @param userId the actual user handle obtained by {@link UserHandle#getIdentifier()} 5779 * and *not* pseudo ids like {@link UserHandle#USER_ALL etc}. 5780 * @return {@code true} if userId has debugging privileges. 5781 * i.e. {@link UserManager#DISALLOW_DEBUGGING_FEATURES} is {@code false}. 5782 */ userHasDebugPriv(int userId, final ShellCommand shellCommand)5783 private boolean userHasDebugPriv(int userId, final ShellCommand shellCommand) { 5784 if (mUserManager.hasUserRestriction( 5785 UserManager.DISALLOW_DEBUGGING_FEATURES, UserHandle.of(userId))) { 5786 shellCommand.getErrPrintWriter().println("User #" + userId 5787 + " is restricted with DISALLOW_DEBUGGING_FEATURES."); 5788 return false; 5789 } 5790 return true; 5791 } 5792 5793 private static final class InputMethodPrivilegedOperationsImpl 5794 extends IInputMethodPrivilegedOperations.Stub { 5795 private final InputMethodManagerService mImms; 5796 @NonNull 5797 private final IBinder mToken; InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, @NonNull IBinder token)5798 InputMethodPrivilegedOperationsImpl(InputMethodManagerService imms, 5799 @NonNull IBinder token) { 5800 mImms = imms; 5801 mToken = token; 5802 } 5803 5804 @BinderThread 5805 @Override setImeWindowStatus(int vis, int backDisposition)5806 public void setImeWindowStatus(int vis, int backDisposition) { 5807 mImms.setImeWindowStatus(mToken, vis, backDisposition); 5808 } 5809 5810 @BinderThread 5811 @Override reportStartInput(IBinder startInputToken)5812 public void reportStartInput(IBinder startInputToken) { 5813 mImms.reportStartInput(mToken, startInputToken); 5814 } 5815 5816 @BinderThread 5817 @Override createInputContentUriToken(Uri contentUri, String packageName)5818 public IInputContentUriToken createInputContentUriToken(Uri contentUri, 5819 String packageName) { 5820 return mImms.createInputContentUriToken(mToken, contentUri, packageName); 5821 } 5822 5823 @BinderThread 5824 @Override reportFullscreenMode(boolean fullscreen)5825 public void reportFullscreenMode(boolean fullscreen) { 5826 mImms.reportFullscreenMode(mToken, fullscreen); 5827 } 5828 5829 @BinderThread 5830 @Override setInputMethod(String id)5831 public void setInputMethod(String id) { 5832 mImms.setInputMethod(mToken, id); 5833 } 5834 5835 @BinderThread 5836 @Override setInputMethodAndSubtype(String id, InputMethodSubtype subtype)5837 public void setInputMethodAndSubtype(String id, InputMethodSubtype subtype) { 5838 mImms.setInputMethodAndSubtype(mToken, id, subtype); 5839 } 5840 5841 @BinderThread 5842 @Override hideMySoftInput(int flags)5843 public void hideMySoftInput(int flags) { 5844 mImms.hideMySoftInput(mToken, flags); 5845 } 5846 5847 @BinderThread 5848 @Override showMySoftInput(int flags)5849 public void showMySoftInput(int flags) { 5850 mImms.showMySoftInput(mToken, flags); 5851 } 5852 5853 @BinderThread 5854 @Override updateStatusIcon(String packageName, @DrawableRes int iconId)5855 public void updateStatusIcon(String packageName, @DrawableRes int iconId) { 5856 mImms.updateStatusIcon(mToken, packageName, iconId); 5857 } 5858 5859 @BinderThread 5860 @Override switchToPreviousInputMethod()5861 public boolean switchToPreviousInputMethod() { 5862 return mImms.switchToPreviousInputMethod(mToken); 5863 } 5864 5865 @BinderThread 5866 @Override switchToNextInputMethod(boolean onlyCurrentIme)5867 public boolean switchToNextInputMethod(boolean onlyCurrentIme) { 5868 return mImms.switchToNextInputMethod(mToken, onlyCurrentIme); 5869 } 5870 5871 @BinderThread 5872 @Override shouldOfferSwitchingToNextInputMethod()5873 public boolean shouldOfferSwitchingToNextInputMethod() { 5874 return mImms.shouldOfferSwitchingToNextInputMethod(mToken); 5875 } 5876 5877 @BinderThread 5878 @Override notifyUserAction()5879 public void notifyUserAction() { 5880 mImms.notifyUserAction(mToken); 5881 } 5882 5883 @BinderThread 5884 @Override reportPreRendered(EditorInfo info)5885 public void reportPreRendered(EditorInfo info) { 5886 mImms.reportPreRendered(mToken, info); 5887 } 5888 5889 @BinderThread 5890 @Override applyImeVisibility(IBinder windowToken, boolean setVisible)5891 public void applyImeVisibility(IBinder windowToken, boolean setVisible) { 5892 mImms.applyImeVisibility(mToken, windowToken, setVisible); 5893 } 5894 } 5895 } 5896