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