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