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