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