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 com.android.internal.content.PackageMonitor; 19 import com.android.internal.inputmethod.InputMethodUtils; 20 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings; 21 import com.android.internal.os.HandlerCaller; 22 import com.android.internal.os.SomeArgs; 23 import com.android.internal.util.FastXmlSerializer; 24 import com.android.internal.view.IInputContext; 25 import com.android.internal.view.IInputMethod; 26 import com.android.internal.view.IInputSessionCallback; 27 import com.android.internal.view.IInputMethodClient; 28 import com.android.internal.view.IInputMethodManager; 29 import com.android.internal.view.IInputMethodSession; 30 import com.android.internal.view.InputBindResult; 31 import com.android.server.EventLogTags; 32 import com.android.server.wm.WindowManagerService; 33 34 import org.xmlpull.v1.XmlPullParser; 35 import org.xmlpull.v1.XmlPullParserException; 36 import org.xmlpull.v1.XmlSerializer; 37 38 import android.app.ActivityManagerNative; 39 import android.app.AppGlobals; 40 import android.app.AlertDialog; 41 import android.app.IUserSwitchObserver; 42 import android.app.KeyguardManager; 43 import android.app.Notification; 44 import android.app.NotificationManager; 45 import android.app.PendingIntent; 46 import android.content.BroadcastReceiver; 47 import android.content.ComponentName; 48 import android.content.ContentResolver; 49 import android.content.Context; 50 import android.content.DialogInterface; 51 import android.content.DialogInterface.OnCancelListener; 52 import android.content.Intent; 53 import android.content.IntentFilter; 54 import android.content.ServiceConnection; 55 import android.content.pm.ApplicationInfo; 56 import android.content.pm.IPackageManager; 57 import android.content.pm.PackageManager; 58 import android.content.pm.ResolveInfo; 59 import android.content.pm.ServiceInfo; 60 import android.content.res.Configuration; 61 import android.content.res.Resources; 62 import android.content.res.TypedArray; 63 import android.database.ContentObserver; 64 import android.inputmethodservice.InputMethodService; 65 import android.os.Binder; 66 import android.os.Environment; 67 import android.os.Handler; 68 import android.os.IBinder; 69 import android.os.IInterface; 70 import android.os.IRemoteCallback; 71 import android.os.Message; 72 import android.os.Process; 73 import android.os.Parcel; 74 import android.os.RemoteException; 75 import android.os.ResultReceiver; 76 import android.os.ServiceManager; 77 import android.os.SystemClock; 78 import android.os.UserHandle; 79 import android.provider.Settings; 80 import android.text.TextUtils; 81 import android.text.style.SuggestionSpan; 82 import android.util.AtomicFile; 83 import android.util.EventLog; 84 import android.util.LruCache; 85 import android.util.Pair; 86 import android.util.PrintWriterPrinter; 87 import android.util.Printer; 88 import android.util.Slog; 89 import android.util.Xml; 90 import android.view.IWindowManager; 91 import android.view.InputChannel; 92 import android.view.LayoutInflater; 93 import android.view.View; 94 import android.view.ViewGroup; 95 import android.view.WindowManager; 96 import android.view.inputmethod.EditorInfo; 97 import android.view.inputmethod.InputBinding; 98 import android.view.inputmethod.InputMethod; 99 import android.view.inputmethod.InputMethodInfo; 100 import android.view.inputmethod.InputMethodManager; 101 import android.view.inputmethod.InputMethodSubtype; 102 import android.widget.ArrayAdapter; 103 import android.widget.CompoundButton; 104 import android.widget.CompoundButton.OnCheckedChangeListener; 105 import android.widget.RadioButton; 106 import android.widget.Switch; 107 import android.widget.TextView; 108 109 import java.io.File; 110 import java.io.FileDescriptor; 111 import java.io.FileInputStream; 112 import java.io.FileOutputStream; 113 import java.io.IOException; 114 import java.io.PrintWriter; 115 import java.util.ArrayList; 116 import java.util.Collections; 117 import java.util.Comparator; 118 import java.util.HashMap; 119 import java.util.HashSet; 120 import java.util.List; 121 import java.util.Locale; 122 import java.util.TreeMap; 123 124 /** 125 * This class provides a system service that manages input methods. 126 */ 127 public class InputMethodManagerService extends IInputMethodManager.Stub 128 implements ServiceConnection, Handler.Callback { 129 static final boolean DEBUG = false; 130 static final String TAG = "InputMethodManagerService"; 131 132 static final int MSG_SHOW_IM_PICKER = 1; 133 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; 134 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; 135 static final int MSG_SHOW_IM_CONFIG = 4; 136 137 static final int MSG_UNBIND_INPUT = 1000; 138 static final int MSG_BIND_INPUT = 1010; 139 static final int MSG_SHOW_SOFT_INPUT = 1020; 140 static final int MSG_HIDE_SOFT_INPUT = 1030; 141 static final int MSG_ATTACH_TOKEN = 1040; 142 static final int MSG_CREATE_SESSION = 1050; 143 144 static final int MSG_START_INPUT = 2000; 145 static final int MSG_RESTART_INPUT = 2010; 146 147 static final int MSG_UNBIND_METHOD = 3000; 148 static final int MSG_BIND_METHOD = 3010; 149 static final int MSG_SET_ACTIVE = 3020; 150 151 static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; 152 153 static final long TIME_TO_RECONNECT = 10*1000; 154 155 static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; 156 157 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; 158 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; 159 160 161 final Context mContext; 162 final Resources mRes; 163 final Handler mHandler; 164 final InputMethodSettings mSettings; 165 final SettingsObserver mSettingsObserver; 166 final IWindowManager mIWindowManager; 167 final HandlerCaller mCaller; 168 final boolean mHasFeature; 169 private InputMethodFileManager mFileManager; 170 private InputMethodAndSubtypeListManager mImListManager; 171 private final HardKeyboardListener mHardKeyboardListener; 172 private final WindowManagerService mWindowManagerService; 173 174 final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1); 175 176 // All known input methods. mMethodMap also serves as the global 177 // lock for this class. 178 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); 179 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); 180 private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = 181 new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); 182 183 // Used to bring IME service up to visible adjustment while it is being shown. 184 final ServiceConnection mVisibleConnection = new ServiceConnection() { 185 @Override public void onServiceConnected(ComponentName name, IBinder service) { 186 } 187 188 @Override public void onServiceDisconnected(ComponentName name) { 189 } 190 }; 191 boolean mVisibleBound = false; 192 193 // Ongoing notification 194 private NotificationManager mNotificationManager; 195 private KeyguardManager mKeyguardManager; 196 private StatusBarManagerService mStatusBar; 197 private Notification mImeSwitcherNotification; 198 private PendingIntent mImeSwitchPendingIntent; 199 private boolean mShowOngoingImeSwitcherForPhones; 200 private boolean mNotificationShown; 201 private final boolean mImeSelectedOnBoot; 202 203 class SessionState { 204 final ClientState client; 205 final IInputMethod method; 206 207 IInputMethodSession session; 208 InputChannel channel; 209 210 @Override 211 public String toString() { 212 return "SessionState{uid " + client.uid + " pid " + client.pid 213 + " method " + Integer.toHexString( 214 System.identityHashCode(method)) 215 + " session " + Integer.toHexString( 216 System.identityHashCode(session)) 217 + " channel " + channel 218 + "}"; 219 } 220 221 SessionState(ClientState _client, IInputMethod _method, 222 IInputMethodSession _session, InputChannel _channel) { 223 client = _client; 224 method = _method; 225 session = _session; 226 channel = _channel; 227 } 228 } 229 230 static final class ClientState { 231 final IInputMethodClient client; 232 final IInputContext inputContext; 233 final int uid; 234 final int pid; 235 final InputBinding binding; 236 237 boolean sessionRequested; 238 SessionState curSession; 239 240 @Override 241 public String toString() { 242 return "ClientState{" + Integer.toHexString( 243 System.identityHashCode(this)) + " uid " + uid 244 + " pid " + pid + "}"; 245 } 246 247 ClientState(IInputMethodClient _client, IInputContext _inputContext, 248 int _uid, int _pid) { 249 client = _client; 250 inputContext = _inputContext; 251 uid = _uid; 252 pid = _pid; 253 binding = new InputBinding(null, inputContext.asBinder(), uid, pid); 254 } 255 } 256 257 final HashMap<IBinder, ClientState> mClients 258 = new HashMap<IBinder, ClientState>(); 259 260 /** 261 * Set once the system is ready to run third party code. 262 */ 263 boolean mSystemReady; 264 265 /** 266 * Id of the currently selected input method. 267 */ 268 String mCurMethodId; 269 270 /** 271 * The current binding sequence number, incremented every time there is 272 * a new bind performed. 273 */ 274 int mCurSeq; 275 276 /** 277 * The client that is currently bound to an input method. 278 */ 279 ClientState mCurClient; 280 281 /** 282 * The last window token that gained focus. 283 */ 284 IBinder mCurFocusedWindow; 285 286 /** 287 * The input context last provided by the current client. 288 */ 289 IInputContext mCurInputContext; 290 291 /** 292 * The attributes last provided by the current client. 293 */ 294 EditorInfo mCurAttribute; 295 296 /** 297 * The input method ID of the input method service that we are currently 298 * connected to or in the process of connecting to. 299 */ 300 String mCurId; 301 302 /** 303 * The current subtype of the current input method. 304 */ 305 private InputMethodSubtype mCurrentSubtype; 306 307 // This list contains the pairs of InputMethodInfo and InputMethodSubtype. 308 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> 309 mShortcutInputMethodsAndSubtypes = 310 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); 311 312 // Was the keyguard locked when this client became current? 313 private boolean mCurClientInKeyguard; 314 315 /** 316 * Set to true if our ServiceConnection is currently actively bound to 317 * a service (whether or not we have gotten its IBinder back yet). 318 */ 319 boolean mHaveConnection; 320 321 /** 322 * Set if the client has asked for the input method to be shown. 323 */ 324 boolean mShowRequested; 325 326 /** 327 * Set if we were explicitly told to show the input method. 328 */ 329 boolean mShowExplicitlyRequested; 330 331 /** 332 * Set if we were forced to be shown. 333 */ 334 boolean mShowForced; 335 336 /** 337 * Set if we last told the input method to show itself. 338 */ 339 boolean mInputShown; 340 341 /** 342 * The Intent used to connect to the current input method. 343 */ 344 Intent mCurIntent; 345 346 /** 347 * The token we have made for the currently active input method, to 348 * identify it in the future. 349 */ 350 IBinder mCurToken; 351 352 /** 353 * If non-null, this is the input method service we are currently connected 354 * to. 355 */ 356 IInputMethod mCurMethod; 357 358 /** 359 * Time that we last initiated a bind to the input method, to determine 360 * if we should try to disconnect and reconnect to it. 361 */ 362 long mLastBindTime; 363 364 /** 365 * Have we called mCurMethod.bindInput()? 366 */ 367 boolean mBoundToMethod; 368 369 /** 370 * Currently enabled session. Only touched by service thread, not 371 * protected by a lock. 372 */ 373 SessionState mEnabledSession; 374 375 /** 376 * True if the screen is on. The value is true initially. 377 */ 378 boolean mScreenOn = true; 379 380 int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; 381 int mImeWindowVis; 382 383 private AlertDialog.Builder mDialogBuilder; 384 private AlertDialog mSwitchingDialog; 385 private View mSwitchingDialogTitleView; 386 private InputMethodInfo[] mIms; 387 private int[] mSubtypeIds; 388 private Locale mLastSystemLocale; 389 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); 390 private final IPackageManager mIPackageManager; 391 392 class SettingsObserver extends ContentObserver { 393 String mLastEnabled = ""; 394 395 SettingsObserver(Handler handler) { 396 super(handler); 397 ContentResolver resolver = mContext.getContentResolver(); 398 resolver.registerContentObserver(Settings.Secure.getUriFor( 399 Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 400 resolver.registerContentObserver(Settings.Secure.getUriFor( 401 Settings.Secure.ENABLED_INPUT_METHODS), false, this); 402 resolver.registerContentObserver(Settings.Secure.getUriFor( 403 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 404 } 405 406 @Override public void onChange(boolean selfChange) { 407 synchronized (mMethodMap) { 408 boolean enabledChanged = false; 409 String newEnabled = mSettings.getEnabledInputMethodsStr(); 410 if (!mLastEnabled.equals(newEnabled)) { 411 mLastEnabled = newEnabled; 412 enabledChanged = true; 413 } 414 updateFromSettingsLocked(enabledChanged); 415 } 416 } 417 } 418 419 class ImmsBroadcastReceiver extends android.content.BroadcastReceiver { 420 private void updateActive() { 421 // Inform the current client of the change in active status 422 if (mCurClient != null && mCurClient.client != null) { 423 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 424 MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient)); 425 } 426 } 427 428 @Override 429 public void onReceive(Context context, Intent intent) { 430 final String action = intent.getAction(); 431 if (Intent.ACTION_SCREEN_ON.equals(action)) { 432 mScreenOn = true; 433 refreshImeWindowVisibilityLocked(); 434 updateActive(); 435 return; 436 } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { 437 mScreenOn = false; 438 setImeWindowVisibilityStatusHiddenLocked(); 439 updateActive(); 440 return; 441 } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 442 hideInputMethodMenu(); 443 // No need to updateActive 444 return; 445 } else { 446 Slog.w(TAG, "Unexpected intent " + intent); 447 } 448 } 449 } 450 451 class MyPackageMonitor extends PackageMonitor { 452 private boolean isChangingPackagesOfCurrentUser() { 453 final int userId = getChangingUserId(); 454 final boolean retval = userId == mSettings.getCurrentUserId(); 455 if (DEBUG) { 456 if (!retval) { 457 Slog.d(TAG, "--- ignore this call back from a background user: " + userId); 458 } 459 } 460 return retval; 461 } 462 463 @Override 464 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 465 if (!isChangingPackagesOfCurrentUser()) { 466 return false; 467 } 468 synchronized (mMethodMap) { 469 String curInputMethodId = mSettings.getSelectedInputMethod(); 470 final int N = mMethodList.size(); 471 if (curInputMethodId != null) { 472 for (int i=0; i<N; i++) { 473 InputMethodInfo imi = mMethodList.get(i); 474 if (imi.getId().equals(curInputMethodId)) { 475 for (String pkg : packages) { 476 if (imi.getPackageName().equals(pkg)) { 477 if (!doit) { 478 return true; 479 } 480 resetSelectedInputMethodAndSubtypeLocked(""); 481 chooseNewDefaultIMELocked(); 482 return true; 483 } 484 } 485 } 486 } 487 } 488 } 489 return false; 490 } 491 492 @Override 493 public void onSomePackagesChanged() { 494 if (!isChangingPackagesOfCurrentUser()) { 495 return; 496 } 497 synchronized (mMethodMap) { 498 InputMethodInfo curIm = null; 499 String curInputMethodId = mSettings.getSelectedInputMethod(); 500 final int N = mMethodList.size(); 501 if (curInputMethodId != null) { 502 for (int i=0; i<N; i++) { 503 InputMethodInfo imi = mMethodList.get(i); 504 final String imiId = imi.getId(); 505 if (imiId.equals(curInputMethodId)) { 506 curIm = imi; 507 } 508 509 int change = isPackageDisappearing(imi.getPackageName()); 510 if (isPackageModified(imi.getPackageName())) { 511 mFileManager.deleteAllInputMethodSubtypes(imiId); 512 } 513 if (change == PACKAGE_TEMPORARY_CHANGE 514 || change == PACKAGE_PERMANENT_CHANGE) { 515 Slog.i(TAG, "Input method uninstalled, disabling: " 516 + imi.getComponent()); 517 setInputMethodEnabledLocked(imi.getId(), false); 518 } 519 } 520 } 521 522 buildInputMethodListLocked( 523 mMethodList, mMethodMap, false /* resetDefaultEnabledIme */); 524 525 boolean changed = false; 526 527 if (curIm != null) { 528 int change = isPackageDisappearing(curIm.getPackageName()); 529 if (change == PACKAGE_TEMPORARY_CHANGE 530 || change == PACKAGE_PERMANENT_CHANGE) { 531 ServiceInfo si = null; 532 try { 533 si = mIPackageManager.getServiceInfo( 534 curIm.getComponent(), 0, mSettings.getCurrentUserId()); 535 } catch (RemoteException ex) { 536 } 537 if (si == null) { 538 // Uh oh, current input method is no longer around! 539 // Pick another one... 540 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 541 setImeWindowVisibilityStatusHiddenLocked(); 542 if (!chooseNewDefaultIMELocked()) { 543 changed = true; 544 curIm = null; 545 Slog.i(TAG, "Unsetting current input method"); 546 resetSelectedInputMethodAndSubtypeLocked(""); 547 } 548 } 549 } 550 } 551 552 if (curIm == null) { 553 // We currently don't have a default input method... is 554 // one now available? 555 changed = chooseNewDefaultIMELocked(); 556 } 557 558 if (changed) { 559 updateFromSettingsLocked(false); 560 } 561 } 562 } 563 } 564 565 private static final class MethodCallback extends IInputSessionCallback.Stub { 566 private final InputMethodManagerService mParentIMMS; 567 private final IInputMethod mMethod; 568 private final InputChannel mChannel; 569 570 MethodCallback(InputMethodManagerService imms, IInputMethod method, 571 InputChannel channel) { 572 mParentIMMS = imms; 573 mMethod = method; 574 mChannel = channel; 575 } 576 577 @Override 578 public void sessionCreated(IInputMethodSession session) { 579 mParentIMMS.onSessionCreated(mMethod, session, mChannel); 580 } 581 } 582 583 private class HardKeyboardListener 584 implements WindowManagerService.OnHardKeyboardStatusChangeListener { 585 @Override 586 public void onHardKeyboardStatusChange(boolean available, boolean enabled) { 587 mHandler.sendMessage(mHandler.obtainMessage( 588 MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0)); 589 } 590 591 public void handleHardKeyboardStatusChange(boolean available, boolean enabled) { 592 if (DEBUG) { 593 Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = " 594 + enabled); 595 } 596 synchronized(mMethodMap) { 597 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null 598 && mSwitchingDialog.isShowing()) { 599 mSwitchingDialogTitleView.findViewById( 600 com.android.internal.R.id.hard_keyboard_section).setVisibility( 601 available ? View.VISIBLE : View.GONE); 602 } 603 } 604 } 605 } 606 607 public InputMethodManagerService(Context context, WindowManagerService windowManager) { 608 mIPackageManager = AppGlobals.getPackageManager(); 609 mContext = context; 610 mRes = context.getResources(); 611 mHandler = new Handler(this); 612 mIWindowManager = IWindowManager.Stub.asInterface( 613 ServiceManager.getService(Context.WINDOW_SERVICE)); 614 mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() { 615 @Override 616 public void executeMessage(Message msg) { 617 handleMessage(msg); 618 } 619 }, true /*asyncHandler*/); 620 mWindowManagerService = windowManager; 621 mHardKeyboardListener = new HardKeyboardListener(); 622 mHasFeature = context.getPackageManager().hasSystemFeature( 623 PackageManager.FEATURE_INPUT_METHODS); 624 625 mImeSwitcherNotification = new Notification(); 626 mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default; 627 mImeSwitcherNotification.when = 0; 628 mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT; 629 mImeSwitcherNotification.tickerText = null; 630 mImeSwitcherNotification.defaults = 0; // please be quiet 631 mImeSwitcherNotification.sound = null; 632 mImeSwitcherNotification.vibrate = null; 633 634 // Tag this notification specially so SystemUI knows it's important 635 mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" }; 636 637 Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER); 638 mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 639 640 mShowOngoingImeSwitcherForPhones = false; 641 642 final IntentFilter broadcastFilter = new IntentFilter(); 643 broadcastFilter.addAction(Intent.ACTION_SCREEN_ON); 644 broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF); 645 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 646 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); 647 648 mNotificationShown = false; 649 int userId = 0; 650 try { 651 ActivityManagerNative.getDefault().registerUserSwitchObserver( 652 new IUserSwitchObserver.Stub() { 653 @Override 654 public void onUserSwitching(int newUserId, IRemoteCallback reply) { 655 synchronized(mMethodMap) { 656 switchUserLocked(newUserId); 657 } 658 if (reply != null) { 659 try { 660 reply.sendResult(null); 661 } catch (RemoteException e) { 662 } 663 } 664 } 665 666 @Override 667 public void onUserSwitchComplete(int newUserId) throws RemoteException { 668 } 669 }); 670 userId = ActivityManagerNative.getDefault().getCurrentUser().id; 671 } catch (RemoteException e) { 672 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); 673 } 674 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); 675 676 // mSettings should be created before buildInputMethodListLocked 677 mSettings = new InputMethodSettings( 678 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId); 679 mFileManager = new InputMethodFileManager(mMethodMap, userId); 680 mImListManager = new InputMethodAndSubtypeListManager(context, this); 681 682 // Just checking if defaultImiId is empty or not 683 final String defaultImiId = mSettings.getSelectedInputMethod(); 684 if (DEBUG) { 685 Slog.d(TAG, "Initial default ime = " + defaultImiId); 686 } 687 mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); 688 689 buildInputMethodListLocked(mMethodList, mMethodMap, 690 !mImeSelectedOnBoot /* resetDefaultEnabledIme */); 691 mSettings.enableAllIMEsIfThereIsNoEnabledIME(); 692 693 if (!mImeSelectedOnBoot) { 694 Slog.w(TAG, "No IME selected. Choose the most applicable IME."); 695 resetDefaultImeLocked(context); 696 } 697 698 mSettingsObserver = new SettingsObserver(mHandler); 699 updateFromSettingsLocked(true); 700 701 // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME 702 // according to the new system locale. 703 final IntentFilter filter = new IntentFilter(); 704 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 705 mContext.registerReceiver( 706 new BroadcastReceiver() { 707 @Override 708 public void onReceive(Context context, Intent intent) { 709 synchronized(mMethodMap) { 710 resetStateIfCurrentLocaleChangedLocked(); 711 } 712 } 713 }, filter); 714 } 715 716 private void resetDefaultImeLocked(Context context) { 717 // Do not reset the default (current) IME when it is a 3rd-party IME 718 if (mCurMethodId != null 719 && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) { 720 return; 721 } 722 723 InputMethodInfo defIm = null; 724 for (InputMethodInfo imi : mMethodList) { 725 if (defIm == null) { 726 if (InputMethodUtils.isValidSystemDefaultIme( 727 mSystemReady, imi, context)) { 728 defIm = imi; 729 Slog.i(TAG, "Selected default: " + imi.getId()); 730 } 731 } 732 } 733 if (defIm == null && mMethodList.size() > 0) { 734 defIm = InputMethodUtils.getMostApplicableDefaultIME( 735 mSettings.getEnabledInputMethodListLocked()); 736 Slog.i(TAG, "No default found, using " + defIm.getId()); 737 } 738 if (defIm != null) { 739 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); 740 } 741 } 742 743 private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged, 744 final boolean resetDefaultEnabledIme) { 745 if (!mSystemReady) { 746 // not system ready 747 return; 748 } 749 final Locale newLocale = mRes.getConfiguration().locale; 750 if (!updateOnlyWhenLocaleChanged 751 || (newLocale != null && !newLocale.equals(mLastSystemLocale))) { 752 if (!updateOnlyWhenLocaleChanged) { 753 hideCurrentInputLocked(0, null); 754 mCurMethodId = null; 755 unbindCurrentMethodLocked(true, false); 756 } 757 if (DEBUG) { 758 Slog.i(TAG, "Locale has been changed to " + newLocale); 759 } 760 // InputMethodAndSubtypeListManager should be reset when the locale is changed. 761 mImListManager = new InputMethodAndSubtypeListManager(mContext, this); 762 buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme); 763 if (!updateOnlyWhenLocaleChanged) { 764 final String selectedImiId = mSettings.getSelectedInputMethod(); 765 if (TextUtils.isEmpty(selectedImiId)) { 766 // This is the first time of the user switch and 767 // set the current ime to the proper one. 768 resetDefaultImeLocked(mContext); 769 } 770 } else { 771 // If the locale is changed, needs to reset the default ime 772 resetDefaultImeLocked(mContext); 773 } 774 updateFromSettingsLocked(true); 775 mLastSystemLocale = newLocale; 776 if (!updateOnlyWhenLocaleChanged) { 777 try { 778 startInputInnerLocked(); 779 } catch (RuntimeException e) { 780 Slog.w(TAG, "Unexpected exception", e); 781 } 782 } 783 } 784 } 785 786 private void resetStateIfCurrentLocaleChangedLocked() { 787 resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */, 788 true /* resetDefaultImeLocked */); 789 } 790 791 private void switchUserLocked(int newUserId) { 792 mSettings.setCurrentUserId(newUserId); 793 // InputMethodFileManager should be reset when the user is changed 794 mFileManager = new InputMethodFileManager(mMethodMap, newUserId); 795 final String defaultImiId = mSettings.getSelectedInputMethod(); 796 // For secondary users, the list of enabled IMEs may not have been updated since the 797 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may 798 // not be empty even if the IME has been uninstalled by the primary user. 799 // Even in such cases, IMMS works fine because it will find the most applicable 800 // IME for that user. 801 final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); 802 if (DEBUG) { 803 Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId); 804 } 805 resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */, 806 initialUserSwitch /* needsToResetDefaultIme */); 807 if (initialUserSwitch) { 808 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mContext.getPackageManager(), 809 mSettings.getEnabledInputMethodListLocked()); 810 } 811 } 812 813 @Override 814 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 815 throws RemoteException { 816 try { 817 return super.onTransact(code, data, reply, flags); 818 } catch (RuntimeException e) { 819 // The input method manager only throws security exceptions, so let's 820 // log all others. 821 if (!(e instanceof SecurityException)) { 822 Slog.wtf(TAG, "Input Method Manager Crash", e); 823 } 824 throw e; 825 } 826 } 827 828 public void systemRunning(StatusBarManagerService statusBar) { 829 synchronized (mMethodMap) { 830 if (DEBUG) { 831 Slog.d(TAG, "--- systemReady"); 832 } 833 if (!mSystemReady) { 834 mSystemReady = true; 835 mKeyguardManager = 836 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 837 mNotificationManager = (NotificationManager) 838 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 839 mStatusBar = statusBar; 840 statusBar.setIconVisibility("ime", false); 841 updateImeWindowStatusLocked(); 842 mShowOngoingImeSwitcherForPhones = mRes.getBoolean( 843 com.android.internal.R.bool.show_ongoing_ime_switcher); 844 if (mShowOngoingImeSwitcherForPhones) { 845 mWindowManagerService.setOnHardKeyboardStatusChangeListener( 846 mHardKeyboardListener); 847 } 848 buildInputMethodListLocked(mMethodList, mMethodMap, 849 !mImeSelectedOnBoot /* resetDefaultEnabledIme */); 850 if (!mImeSelectedOnBoot) { 851 Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here."); 852 resetStateIfCurrentLocaleChangedLocked(); 853 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed( 854 mContext.getPackageManager(), 855 mSettings.getEnabledInputMethodListLocked()); 856 } 857 mLastSystemLocale = mRes.getConfiguration().locale; 858 try { 859 startInputInnerLocked(); 860 } catch (RuntimeException e) { 861 Slog.w(TAG, "Unexpected exception", e); 862 } 863 } 864 } 865 } 866 867 private void setImeWindowVisibilityStatusHiddenLocked() { 868 mImeWindowVis = 0; 869 updateImeWindowStatusLocked(); 870 } 871 872 private void refreshImeWindowVisibilityLocked() { 873 final Configuration conf = mRes.getConfiguration(); 874 final boolean haveHardKeyboard = conf.keyboard 875 != Configuration.KEYBOARD_NOKEYS; 876 final boolean hardKeyShown = haveHardKeyboard 877 && conf.hardKeyboardHidden 878 != Configuration.HARDKEYBOARDHIDDEN_YES; 879 880 final boolean isScreenLocked = isKeyguardLocked(); 881 final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown); 882 // We assume the softkeyboard is shown when the input is active as long as the 883 // hard keyboard is not shown. 884 final boolean inputVisible = inputActive && !hardKeyShown; 885 mImeWindowVis = (inputActive ? InputMethodService.IME_ACTIVE : 0) 886 | (inputVisible ? InputMethodService.IME_VISIBLE : 0); 887 updateImeWindowStatusLocked(); 888 } 889 890 private void updateImeWindowStatusLocked() { 891 setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition); 892 } 893 894 // --------------------------------------------------------------------------------------- 895 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either 896 // 1) it comes from the system process 897 // 2) the calling process' user id is identical to the current user id IMMS thinks. 898 private boolean calledFromValidUser() { 899 final int uid = Binder.getCallingUid(); 900 final int userId = UserHandle.getUserId(uid); 901 if (DEBUG) { 902 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " 903 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID 904 + " calling userId = " + userId + ", foreground user id = " 905 + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid() 906 + InputMethodUtils.getApiCallStack()); 907 } 908 if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) { 909 return true; 910 } 911 912 // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the 913 // foreground user, not for the user of that process. Accordingly InputMethodManagerService 914 // must not manage background users' states in any functions. 915 // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded 916 // by a token. 917 if (mContext.checkCallingOrSelfPermission( 918 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) 919 == PackageManager.PERMISSION_GRANTED) { 920 if (DEBUG) { 921 Slog.d(TAG, "--- Access granted because the calling process has " 922 + "the INTERACT_ACROSS_USERS_FULL permission"); 923 } 924 return true; 925 } 926 Slog.w(TAG, "--- IPC called from background users. Ignore. \n" 927 + InputMethodUtils.getStackTrace()); 928 return false; 929 } 930 931 private boolean bindCurrentInputMethodService( 932 Intent service, ServiceConnection conn, int flags) { 933 if (service == null || conn == null) { 934 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); 935 return false; 936 } 937 return mContext.bindServiceAsUser(service, conn, flags, 938 new UserHandle(mSettings.getCurrentUserId())); 939 } 940 941 @Override 942 public List<InputMethodInfo> getInputMethodList() { 943 // TODO: Make this work even for non-current users? 944 if (!calledFromValidUser()) { 945 return Collections.emptyList(); 946 } 947 synchronized (mMethodMap) { 948 return new ArrayList<InputMethodInfo>(mMethodList); 949 } 950 } 951 952 @Override 953 public List<InputMethodInfo> getEnabledInputMethodList() { 954 // TODO: Make this work even for non-current users? 955 if (!calledFromValidUser()) { 956 return Collections.emptyList(); 957 } 958 synchronized (mMethodMap) { 959 return mSettings.getEnabledInputMethodListLocked(); 960 } 961 } 962 963 private HashMap<InputMethodInfo, List<InputMethodSubtype>> 964 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() { 965 HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = 966 new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); 967 for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) { 968 enabledInputMethodAndSubtypes.put( 969 imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true)); 970 } 971 return enabledInputMethodAndSubtypes; 972 } 973 974 /** 975 * @param imiId if null, returns enabled subtypes for the current imi 976 * @return enabled subtypes of the specified imi 977 */ 978 @Override 979 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, 980 boolean allowsImplicitlySelectedSubtypes) { 981 // TODO: Make this work even for non-current users? 982 if (!calledFromValidUser()) { 983 return Collections.<InputMethodSubtype>emptyList(); 984 } 985 synchronized (mMethodMap) { 986 final InputMethodInfo imi; 987 if (imiId == null && mCurMethodId != null) { 988 imi = mMethodMap.get(mCurMethodId); 989 } else { 990 imi = mMethodMap.get(imiId); 991 } 992 if (imi == null) { 993 return Collections.<InputMethodSubtype>emptyList(); 994 } 995 return mSettings.getEnabledInputMethodSubtypeListLocked( 996 mContext, imi, allowsImplicitlySelectedSubtypes); 997 } 998 } 999 1000 @Override 1001 public void addClient(IInputMethodClient client, 1002 IInputContext inputContext, int uid, int pid) { 1003 if (!calledFromValidUser()) { 1004 return; 1005 } 1006 synchronized (mMethodMap) { 1007 mClients.put(client.asBinder(), new ClientState(client, 1008 inputContext, uid, pid)); 1009 } 1010 } 1011 1012 @Override 1013 public void removeClient(IInputMethodClient client) { 1014 if (!calledFromValidUser()) { 1015 return; 1016 } 1017 synchronized (mMethodMap) { 1018 ClientState cs = mClients.remove(client.asBinder()); 1019 if (cs != null) { 1020 clearClientSessionLocked(cs); 1021 } 1022 } 1023 } 1024 1025 void executeOrSendMessage(IInterface target, Message msg) { 1026 if (target.asBinder() instanceof Binder) { 1027 mCaller.sendMessage(msg); 1028 } else { 1029 handleMessage(msg); 1030 msg.recycle(); 1031 } 1032 } 1033 1034 void unbindCurrentClientLocked() { 1035 if (mCurClient != null) { 1036 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = " 1037 + mCurClient.client.asBinder()); 1038 if (mBoundToMethod) { 1039 mBoundToMethod = false; 1040 if (mCurMethod != null) { 1041 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 1042 MSG_UNBIND_INPUT, mCurMethod)); 1043 } 1044 } 1045 1046 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1047 MSG_SET_ACTIVE, 0, mCurClient)); 1048 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1049 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1050 mCurClient.sessionRequested = false; 1051 mCurClient = null; 1052 1053 hideInputMethodMenuLocked(); 1054 } 1055 } 1056 1057 private int getImeShowFlags() { 1058 int flags = 0; 1059 if (mShowForced) { 1060 flags |= InputMethod.SHOW_FORCED 1061 | InputMethod.SHOW_EXPLICIT; 1062 } else if (mShowExplicitlyRequested) { 1063 flags |= InputMethod.SHOW_EXPLICIT; 1064 } 1065 return flags; 1066 } 1067 1068 private int getAppShowFlags() { 1069 int flags = 0; 1070 if (mShowForced) { 1071 flags |= InputMethodManager.SHOW_FORCED; 1072 } else if (!mShowExplicitlyRequested) { 1073 flags |= InputMethodManager.SHOW_IMPLICIT; 1074 } 1075 return flags; 1076 } 1077 1078 InputBindResult attachNewInputLocked(boolean initial) { 1079 if (!mBoundToMethod) { 1080 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1081 MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); 1082 mBoundToMethod = true; 1083 } 1084 final SessionState session = mCurClient.curSession; 1085 if (initial) { 1086 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 1087 MSG_START_INPUT, session, mCurInputContext, mCurAttribute)); 1088 } else { 1089 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 1090 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute)); 1091 } 1092 if (mShowRequested) { 1093 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); 1094 showCurrentInputLocked(getAppShowFlags(), null); 1095 } 1096 return new InputBindResult(session.session, 1097 session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq); 1098 } 1099 1100 InputBindResult startInputLocked(IInputMethodClient client, 1101 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1102 // If no method is currently selected, do nothing. 1103 if (mCurMethodId == null) { 1104 return mNoBinding; 1105 } 1106 1107 ClientState cs = mClients.get(client.asBinder()); 1108 if (cs == null) { 1109 throw new IllegalArgumentException("unknown client " 1110 + client.asBinder()); 1111 } 1112 1113 try { 1114 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 1115 // Check with the window manager to make sure this client actually 1116 // has a window with focus. If not, reject. This is thread safe 1117 // because if the focus changes some time before or after, the 1118 // next client receiving focus that has any interest in input will 1119 // be calling through here after that change happens. 1120 Slog.w(TAG, "Starting input on non-focused client " + cs.client 1121 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 1122 return null; 1123 } 1124 } catch (RemoteException e) { 1125 } 1126 1127 return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags); 1128 } 1129 1130 InputBindResult startInputUncheckedLocked(ClientState cs, 1131 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1132 // If no method is currently selected, do nothing. 1133 if (mCurMethodId == null) { 1134 return mNoBinding; 1135 } 1136 1137 if (mCurClient != cs) { 1138 // Was the keyguard locked when switching over to the new client? 1139 mCurClientInKeyguard = isKeyguardLocked(); 1140 // If the client is changing, we need to switch over to the new 1141 // one. 1142 unbindCurrentClientLocked(); 1143 if (DEBUG) Slog.v(TAG, "switching to client: client = " 1144 + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard); 1145 1146 // If the screen is on, inform the new client it is active 1147 if (mScreenOn) { 1148 executeOrSendMessage(cs.client, mCaller.obtainMessageIO( 1149 MSG_SET_ACTIVE, mScreenOn ? 1 : 0, cs)); 1150 } 1151 } 1152 1153 // Bump up the sequence for this client and attach it. 1154 mCurSeq++; 1155 if (mCurSeq <= 0) mCurSeq = 1; 1156 mCurClient = cs; 1157 mCurInputContext = inputContext; 1158 mCurAttribute = attribute; 1159 1160 // Check if the input method is changing. 1161 if (mCurId != null && mCurId.equals(mCurMethodId)) { 1162 if (cs.curSession != null) { 1163 // Fast case: if we are already connected to the input method, 1164 // then just return it. 1165 return attachNewInputLocked( 1166 (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0); 1167 } 1168 if (mHaveConnection) { 1169 if (mCurMethod != null) { 1170 // Return to client, and we will get back with it when 1171 // we have had a session made for it. 1172 requestClientSessionLocked(cs); 1173 return new InputBindResult(null, null, mCurId, mCurSeq); 1174 } else if (SystemClock.uptimeMillis() 1175 < (mLastBindTime+TIME_TO_RECONNECT)) { 1176 // In this case we have connected to the service, but 1177 // don't yet have its interface. If it hasn't been too 1178 // long since we did the connection, we'll return to 1179 // the client and wait to get the service interface so 1180 // we can report back. If it has been too long, we want 1181 // to fall through so we can try a disconnect/reconnect 1182 // to see if we can get back in touch with the service. 1183 return new InputBindResult(null, null, mCurId, mCurSeq); 1184 } else { 1185 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 1186 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0); 1187 } 1188 } 1189 } 1190 1191 return startInputInnerLocked(); 1192 } 1193 1194 InputBindResult startInputInnerLocked() { 1195 if (mCurMethodId == null) { 1196 return mNoBinding; 1197 } 1198 1199 if (!mSystemReady) { 1200 // If the system is not yet ready, we shouldn't be running third 1201 // party code. 1202 return new InputBindResult(null, null, mCurMethodId, mCurSeq); 1203 } 1204 1205 InputMethodInfo info = mMethodMap.get(mCurMethodId); 1206 if (info == null) { 1207 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1208 } 1209 1210 unbindCurrentMethodLocked(false, true); 1211 1212 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); 1213 mCurIntent.setComponent(info.getComponent()); 1214 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1215 com.android.internal.R.string.input_method_binding_label); 1216 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 1217 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); 1218 if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE 1219 | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) { 1220 mLastBindTime = SystemClock.uptimeMillis(); 1221 mHaveConnection = true; 1222 mCurId = info.getId(); 1223 mCurToken = new Binder(); 1224 try { 1225 if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); 1226 mIWindowManager.addWindowToken(mCurToken, 1227 WindowManager.LayoutParams.TYPE_INPUT_METHOD); 1228 } catch (RemoteException e) { 1229 } 1230 return new InputBindResult(null, null, mCurId, mCurSeq); 1231 } else { 1232 mCurIntent = null; 1233 Slog.w(TAG, "Failure connecting to input method service: " 1234 + mCurIntent); 1235 } 1236 return null; 1237 } 1238 1239 @Override 1240 public InputBindResult startInput(IInputMethodClient client, 1241 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1242 if (!calledFromValidUser()) { 1243 return null; 1244 } 1245 synchronized (mMethodMap) { 1246 final long ident = Binder.clearCallingIdentity(); 1247 try { 1248 return startInputLocked(client, inputContext, attribute, controlFlags); 1249 } finally { 1250 Binder.restoreCallingIdentity(ident); 1251 } 1252 } 1253 } 1254 1255 @Override 1256 public void finishInput(IInputMethodClient client) { 1257 } 1258 1259 @Override 1260 public void onServiceConnected(ComponentName name, IBinder service) { 1261 synchronized (mMethodMap) { 1262 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 1263 mCurMethod = IInputMethod.Stub.asInterface(service); 1264 if (mCurToken == null) { 1265 Slog.w(TAG, "Service connected without a token!"); 1266 unbindCurrentMethodLocked(false, false); 1267 return; 1268 } 1269 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 1270 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1271 MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); 1272 if (mCurClient != null) { 1273 clearClientSessionLocked(mCurClient); 1274 requestClientSessionLocked(mCurClient); 1275 } 1276 } 1277 } 1278 } 1279 1280 void onSessionCreated(IInputMethod method, IInputMethodSession session, 1281 InputChannel channel) { 1282 synchronized (mMethodMap) { 1283 if (mCurMethod != null && method != null 1284 && mCurMethod.asBinder() == method.asBinder()) { 1285 if (mCurClient != null) { 1286 clearClientSessionLocked(mCurClient); 1287 mCurClient.curSession = new SessionState(mCurClient, 1288 method, session, channel); 1289 InputBindResult res = attachNewInputLocked(true); 1290 if (res.method != null) { 1291 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 1292 MSG_BIND_METHOD, mCurClient.client, res)); 1293 } 1294 return; 1295 } 1296 } 1297 } 1298 1299 // Session abandoned. Close its associated input channel. 1300 channel.dispose(); 1301 } 1302 1303 void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) { 1304 if (mVisibleBound) { 1305 mContext.unbindService(mVisibleConnection); 1306 mVisibleBound = false; 1307 } 1308 1309 if (mHaveConnection) { 1310 mContext.unbindService(this); 1311 mHaveConnection = false; 1312 } 1313 1314 if (mCurToken != null) { 1315 try { 1316 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken); 1317 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) { 1318 // The current IME is shown. Hence an IME switch (transition) is happening. 1319 mWindowManagerService.saveLastInputMethodWindowForTransition(); 1320 } 1321 mIWindowManager.removeWindowToken(mCurToken); 1322 } catch (RemoteException e) { 1323 } 1324 mCurToken = null; 1325 } 1326 1327 mCurId = null; 1328 clearCurMethodLocked(); 1329 1330 if (reportToClient && mCurClient != null) { 1331 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1332 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1333 } 1334 } 1335 1336 void requestClientSessionLocked(ClientState cs) { 1337 if (!cs.sessionRequested) { 1338 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); 1339 InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString()); 1340 cs.sessionRequested = true; 1341 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO( 1342 MSG_CREATE_SESSION, mCurMethod, channels[1], 1343 new MethodCallback(this, mCurMethod, channels[0]))); 1344 } 1345 } 1346 1347 void clearClientSessionLocked(ClientState cs) { 1348 finishSessionLocked(cs.curSession); 1349 cs.curSession = null; 1350 cs.sessionRequested = false; 1351 } 1352 1353 private void finishSessionLocked(SessionState sessionState) { 1354 if (sessionState != null) { 1355 if (sessionState.session != null) { 1356 try { 1357 sessionState.session.finishSession(); 1358 } catch (RemoteException e) { 1359 Slog.w(TAG, "Session failed to close due to remote exception", e); 1360 setImeWindowVisibilityStatusHiddenLocked(); 1361 } 1362 sessionState.session = null; 1363 } 1364 if (sessionState.channel != null) { 1365 sessionState.channel.dispose(); 1366 sessionState.channel = null; 1367 } 1368 } 1369 } 1370 1371 void clearCurMethodLocked() { 1372 if (mCurMethod != null) { 1373 for (ClientState cs : mClients.values()) { 1374 clearClientSessionLocked(cs); 1375 } 1376 1377 finishSessionLocked(mEnabledSession); 1378 mEnabledSession = null; 1379 mCurMethod = null; 1380 } 1381 if (mStatusBar != null) { 1382 mStatusBar.setIconVisibility("ime", false); 1383 } 1384 } 1385 1386 @Override 1387 public void onServiceDisconnected(ComponentName name) { 1388 synchronized (mMethodMap) { 1389 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 1390 + " mCurIntent=" + mCurIntent); 1391 if (mCurMethod != null && mCurIntent != null 1392 && name.equals(mCurIntent.getComponent())) { 1393 clearCurMethodLocked(); 1394 // We consider this to be a new bind attempt, since the system 1395 // should now try to restart the service for us. 1396 mLastBindTime = SystemClock.uptimeMillis(); 1397 mShowRequested = mInputShown; 1398 mInputShown = false; 1399 if (mCurClient != null) { 1400 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1401 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1402 } 1403 } 1404 } 1405 } 1406 1407 @Override 1408 public void updateStatusIcon(IBinder token, String packageName, int iconId) { 1409 int uid = Binder.getCallingUid(); 1410 long ident = Binder.clearCallingIdentity(); 1411 try { 1412 if (token == null || mCurToken != token) { 1413 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); 1414 return; 1415 } 1416 1417 synchronized (mMethodMap) { 1418 if (iconId == 0) { 1419 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 1420 if (mStatusBar != null) { 1421 mStatusBar.setIconVisibility("ime", false); 1422 } 1423 } else if (packageName != null) { 1424 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 1425 CharSequence contentDescription = null; 1426 try { 1427 // Use PackageManager to load label 1428 final PackageManager packageManager = mContext.getPackageManager(); 1429 contentDescription = packageManager.getApplicationLabel( 1430 mIPackageManager.getApplicationInfo(packageName, 0, 1431 mSettings.getCurrentUserId())); 1432 } catch (RemoteException e) { 1433 /* ignore */ 1434 } 1435 if (mStatusBar != null) { 1436 mStatusBar.setIcon("ime", packageName, iconId, 0, 1437 contentDescription != null 1438 ? contentDescription.toString() : null); 1439 mStatusBar.setIconVisibility("ime", true); 1440 } 1441 } 1442 } 1443 } finally { 1444 Binder.restoreCallingIdentity(ident); 1445 } 1446 } 1447 1448 private boolean needsToShowImeSwitchOngoingNotification() { 1449 if (!mShowOngoingImeSwitcherForPhones) return false; 1450 if (isScreenLocked()) return false; 1451 synchronized (mMethodMap) { 1452 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 1453 final int N = imis.size(); 1454 if (N > 2) return true; 1455 if (N < 1) return false; 1456 int nonAuxCount = 0; 1457 int auxCount = 0; 1458 InputMethodSubtype nonAuxSubtype = null; 1459 InputMethodSubtype auxSubtype = null; 1460 for(int i = 0; i < N; ++i) { 1461 final InputMethodInfo imi = imis.get(i); 1462 final List<InputMethodSubtype> subtypes = 1463 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 1464 final int subtypeCount = subtypes.size(); 1465 if (subtypeCount == 0) { 1466 ++nonAuxCount; 1467 } else { 1468 for (int j = 0; j < subtypeCount; ++j) { 1469 final InputMethodSubtype subtype = subtypes.get(j); 1470 if (!subtype.isAuxiliary()) { 1471 ++nonAuxCount; 1472 nonAuxSubtype = subtype; 1473 } else { 1474 ++auxCount; 1475 auxSubtype = subtype; 1476 } 1477 } 1478 } 1479 } 1480 if (nonAuxCount > 1 || auxCount > 1) { 1481 return true; 1482 } else if (nonAuxCount == 1 && auxCount == 1) { 1483 if (nonAuxSubtype != null && auxSubtype != null 1484 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale()) 1485 || auxSubtype.overridesImplicitlyEnabledSubtype() 1486 || nonAuxSubtype.overridesImplicitlyEnabledSubtype()) 1487 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) { 1488 return false; 1489 } 1490 return true; 1491 } 1492 return false; 1493 } 1494 } 1495 1496 private boolean isKeyguardLocked() { 1497 return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 1498 } 1499 1500 // Caution! This method is called in this class. Handle multi-user carefully 1501 @SuppressWarnings("deprecation") 1502 @Override 1503 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1504 final long ident = Binder.clearCallingIdentity(); 1505 try { 1506 if (token == null || mCurToken != token) { 1507 int uid = Binder.getCallingUid(); 1508 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); 1509 return; 1510 } 1511 synchronized (mMethodMap) { 1512 // apply policy for binder calls 1513 if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) { 1514 vis = 0; 1515 } 1516 mImeWindowVis = vis; 1517 mBackDisposition = backDisposition; 1518 if (mStatusBar != null) { 1519 mStatusBar.setImeWindowStatus(token, vis, backDisposition); 1520 } 1521 final boolean iconVisibility = ((vis & (InputMethodService.IME_ACTIVE)) != 0) 1522 && (mWindowManagerService.isHardKeyboardAvailable() 1523 || (vis & (InputMethodService.IME_VISIBLE)) != 0); 1524 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 1525 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) { 1526 // Used to load label 1527 final CharSequence title = mRes.getText( 1528 com.android.internal.R.string.select_input_method); 1529 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName( 1530 mContext, imi, mCurrentSubtype); 1531 1532 mImeSwitcherNotification.setLatestEventInfo( 1533 mContext, title, summary, mImeSwitchPendingIntent); 1534 if (mNotificationManager != null) { 1535 if (DEBUG) { 1536 Slog.d(TAG, "--- show notification: label = " + summary); 1537 } 1538 mNotificationManager.notifyAsUser(null, 1539 com.android.internal.R.string.select_input_method, 1540 mImeSwitcherNotification, UserHandle.ALL); 1541 mNotificationShown = true; 1542 } 1543 } else { 1544 if (mNotificationShown && mNotificationManager != null) { 1545 if (DEBUG) { 1546 Slog.d(TAG, "--- hide notification"); 1547 } 1548 mNotificationManager.cancelAsUser(null, 1549 com.android.internal.R.string.select_input_method, UserHandle.ALL); 1550 mNotificationShown = false; 1551 } 1552 } 1553 } 1554 } finally { 1555 Binder.restoreCallingIdentity(ident); 1556 } 1557 } 1558 1559 @Override 1560 public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { 1561 if (!calledFromValidUser()) { 1562 return; 1563 } 1564 synchronized (mMethodMap) { 1565 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 1566 for (int i = 0; i < spans.length; ++i) { 1567 SuggestionSpan ss = spans[i]; 1568 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) { 1569 mSecureSuggestionSpans.put(ss, currentImi); 1570 } 1571 } 1572 } 1573 } 1574 1575 @Override 1576 public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { 1577 if (!calledFromValidUser()) { 1578 return false; 1579 } 1580 synchronized (mMethodMap) { 1581 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); 1582 // TODO: Do not send the intent if the process of the targetImi is already dead. 1583 if (targetImi != null) { 1584 final String[] suggestions = span.getSuggestions(); 1585 if (index < 0 || index >= suggestions.length) return false; 1586 final String className = span.getNotificationTargetClassName(); 1587 final Intent intent = new Intent(); 1588 // Ensures that only a class in the original IME package will receive the 1589 // notification. 1590 intent.setClassName(targetImi.getPackageName(), className); 1591 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); 1592 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); 1593 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); 1594 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); 1595 final long ident = Binder.clearCallingIdentity(); 1596 try { 1597 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 1598 } finally { 1599 Binder.restoreCallingIdentity(ident); 1600 } 1601 return true; 1602 } 1603 } 1604 return false; 1605 } 1606 1607 void updateFromSettingsLocked(boolean enabledMayChange) { 1608 if (enabledMayChange) { 1609 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 1610 for (int i=0; i<enabled.size(); i++) { 1611 // We allow the user to select "disabled until used" apps, so if they 1612 // are enabling one of those here we now need to make it enabled. 1613 InputMethodInfo imm = enabled.get(i); 1614 try { 1615 ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(), 1616 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 1617 mSettings.getCurrentUserId()); 1618 if (ai != null && ai.enabledSetting 1619 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 1620 if (DEBUG) { 1621 Slog.d(TAG, "Update state(" + imm.getId() 1622 + "): DISABLED_UNTIL_USED -> DEFAULT"); 1623 } 1624 mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(), 1625 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 1626 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(), 1627 mContext.getBasePackageName()); 1628 } 1629 } catch (RemoteException e) { 1630 } 1631 } 1632 } 1633 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 1634 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 1635 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 1636 // enabled. 1637 String id = mSettings.getSelectedInputMethod(); 1638 // There is no input method selected, try to choose new applicable input method. 1639 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 1640 id = mSettings.getSelectedInputMethod(); 1641 } 1642 if (!TextUtils.isEmpty(id)) { 1643 try { 1644 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); 1645 } catch (IllegalArgumentException e) { 1646 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 1647 mCurMethodId = null; 1648 unbindCurrentMethodLocked(true, false); 1649 } 1650 mShortcutInputMethodsAndSubtypes.clear(); 1651 } else { 1652 // There is no longer an input method set, so stop any current one. 1653 mCurMethodId = null; 1654 unbindCurrentMethodLocked(true, false); 1655 } 1656 } 1657 1658 /* package */ void setInputMethodLocked(String id, int subtypeId) { 1659 InputMethodInfo info = mMethodMap.get(id); 1660 if (info == null) { 1661 throw new IllegalArgumentException("Unknown id: " + id); 1662 } 1663 1664 // See if we need to notify a subtype change within the same IME. 1665 if (id.equals(mCurMethodId)) { 1666 final int subtypeCount = info.getSubtypeCount(); 1667 if (subtypeCount <= 0) { 1668 return; 1669 } 1670 final InputMethodSubtype oldSubtype = mCurrentSubtype; 1671 final InputMethodSubtype newSubtype; 1672 if (subtypeId >= 0 && subtypeId < subtypeCount) { 1673 newSubtype = info.getSubtypeAt(subtypeId); 1674 } else { 1675 // If subtype is null, try to find the most applicable one from 1676 // getCurrentInputMethodSubtype. 1677 newSubtype = getCurrentInputMethodSubtypeLocked(); 1678 } 1679 if (newSubtype == null || oldSubtype == null) { 1680 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype 1681 + ", new subtype = " + newSubtype); 1682 return; 1683 } 1684 if (newSubtype != oldSubtype) { 1685 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 1686 if (mCurMethod != null) { 1687 try { 1688 refreshImeWindowVisibilityLocked(); 1689 mCurMethod.changeInputMethodSubtype(newSubtype); 1690 } catch (RemoteException e) { 1691 Slog.w(TAG, "Failed to call changeInputMethodSubtype"); 1692 } 1693 } 1694 } 1695 return; 1696 } 1697 1698 // Changing to a different IME. 1699 final long ident = Binder.clearCallingIdentity(); 1700 try { 1701 // Set a subtype to this input method. 1702 // subtypeId the name of a subtype which will be set. 1703 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); 1704 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 1705 // because mCurMethodId is stored as a history in 1706 // setSelectedInputMethodAndSubtypeLocked(). 1707 mCurMethodId = id; 1708 1709 if (ActivityManagerNative.isSystemReady()) { 1710 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 1711 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1712 intent.putExtra("input_method_id", id); 1713 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 1714 } 1715 unbindCurrentClientLocked(); 1716 } finally { 1717 Binder.restoreCallingIdentity(ident); 1718 } 1719 } 1720 1721 @Override 1722 public boolean showSoftInput(IInputMethodClient client, int flags, 1723 ResultReceiver resultReceiver) { 1724 if (!calledFromValidUser()) { 1725 return false; 1726 } 1727 int uid = Binder.getCallingUid(); 1728 long ident = Binder.clearCallingIdentity(); 1729 try { 1730 synchronized (mMethodMap) { 1731 if (mCurClient == null || client == null 1732 || mCurClient.client.asBinder() != client.asBinder()) { 1733 try { 1734 // We need to check if this is the current client with 1735 // focus in the window manager, to allow this call to 1736 // be made before input is started in it. 1737 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1738 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 1739 return false; 1740 } 1741 } catch (RemoteException e) { 1742 return false; 1743 } 1744 } 1745 1746 if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); 1747 return showCurrentInputLocked(flags, resultReceiver); 1748 } 1749 } finally { 1750 Binder.restoreCallingIdentity(ident); 1751 } 1752 } 1753 1754 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1755 mShowRequested = true; 1756 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 1757 mShowExplicitlyRequested = true; 1758 } 1759 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 1760 mShowExplicitlyRequested = true; 1761 mShowForced = true; 1762 } 1763 1764 if (!mSystemReady) { 1765 return false; 1766 } 1767 1768 boolean res = false; 1769 if (mCurMethod != null) { 1770 if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken); 1771 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 1772 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, 1773 resultReceiver)); 1774 mInputShown = true; 1775 if (mHaveConnection && !mVisibleBound) { 1776 bindCurrentInputMethodService( 1777 mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE); 1778 mVisibleBound = true; 1779 } 1780 res = true; 1781 } else if (mHaveConnection && SystemClock.uptimeMillis() 1782 >= (mLastBindTime+TIME_TO_RECONNECT)) { 1783 // The client has asked to have the input method shown, but 1784 // we have been sitting here too long with a connection to the 1785 // service and no interface received, so let's disconnect/connect 1786 // to try to prod things along. 1787 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 1788 SystemClock.uptimeMillis()-mLastBindTime,1); 1789 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); 1790 mContext.unbindService(this); 1791 bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE 1792 | Context.BIND_NOT_VISIBLE); 1793 } else { 1794 if (DEBUG) { 1795 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = " 1796 + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis())); 1797 } 1798 } 1799 1800 return res; 1801 } 1802 1803 @Override 1804 public boolean hideSoftInput(IInputMethodClient client, int flags, 1805 ResultReceiver resultReceiver) { 1806 if (!calledFromValidUser()) { 1807 return false; 1808 } 1809 int uid = Binder.getCallingUid(); 1810 long ident = Binder.clearCallingIdentity(); 1811 try { 1812 synchronized (mMethodMap) { 1813 if (mCurClient == null || client == null 1814 || mCurClient.client.asBinder() != client.asBinder()) { 1815 try { 1816 // We need to check if this is the current client with 1817 // focus in the window manager, to allow this call to 1818 // be made before input is started in it. 1819 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1820 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " 1821 + uid + ": " + client); 1822 setImeWindowVisibilityStatusHiddenLocked(); 1823 return false; 1824 } 1825 } catch (RemoteException e) { 1826 setImeWindowVisibilityStatusHiddenLocked(); 1827 return false; 1828 } 1829 } 1830 1831 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 1832 return hideCurrentInputLocked(flags, resultReceiver); 1833 } 1834 } finally { 1835 Binder.restoreCallingIdentity(ident); 1836 } 1837 } 1838 1839 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1840 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 1841 && (mShowExplicitlyRequested || mShowForced)) { 1842 if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide"); 1843 return false; 1844 } 1845 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 1846 if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide"); 1847 return false; 1848 } 1849 boolean res; 1850 if (mInputShown && mCurMethod != null) { 1851 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1852 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver)); 1853 res = true; 1854 } else { 1855 res = false; 1856 } 1857 if (mHaveConnection && mVisibleBound) { 1858 mContext.unbindService(mVisibleConnection); 1859 mVisibleBound = false; 1860 } 1861 mInputShown = false; 1862 mShowRequested = false; 1863 mShowExplicitlyRequested = false; 1864 mShowForced = false; 1865 return res; 1866 } 1867 1868 @Override 1869 public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken, 1870 int controlFlags, int softInputMode, int windowFlags, 1871 EditorInfo attribute, IInputContext inputContext) { 1872 // Needs to check the validity before clearing calling identity 1873 final boolean calledFromValidUser = calledFromValidUser(); 1874 1875 InputBindResult res = null; 1876 long ident = Binder.clearCallingIdentity(); 1877 try { 1878 synchronized (mMethodMap) { 1879 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder() 1880 + " controlFlags=#" + Integer.toHexString(controlFlags) 1881 + " softInputMode=#" + Integer.toHexString(softInputMode) 1882 + " windowFlags=#" + Integer.toHexString(windowFlags)); 1883 1884 ClientState cs = mClients.get(client.asBinder()); 1885 if (cs == null) { 1886 throw new IllegalArgumentException("unknown client " 1887 + client.asBinder()); 1888 } 1889 1890 try { 1891 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 1892 // Check with the window manager to make sure this client actually 1893 // has a window with focus. If not, reject. This is thread safe 1894 // because if the focus changes some time before or after, the 1895 // next client receiving focus that has any interest in input will 1896 // be calling through here after that change happens. 1897 Slog.w(TAG, "Focus gain on non-focused client " + cs.client 1898 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 1899 return null; 1900 } 1901 } catch (RemoteException e) { 1902 } 1903 1904 if (!calledFromValidUser) { 1905 Slog.w(TAG, "A background user is requesting window. Hiding IME."); 1906 Slog.w(TAG, "If you want to interect with IME, you need " 1907 + "android.permission.INTERACT_ACROSS_USERS_FULL"); 1908 hideCurrentInputLocked(0, null); 1909 return null; 1910 } 1911 1912 if (mCurFocusedWindow == windowToken) { 1913 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client 1914 + " attribute=" + attribute + ", token = " + windowToken); 1915 if (attribute != null) { 1916 return startInputUncheckedLocked(cs, inputContext, attribute, 1917 controlFlags); 1918 } 1919 return null; 1920 } 1921 mCurFocusedWindow = windowToken; 1922 1923 // Should we auto-show the IME even if the caller has not 1924 // specified what should be done with it? 1925 // We only do this automatically if the window can resize 1926 // to accommodate the IME (so what the user sees will give 1927 // them good context without input information being obscured 1928 // by the IME) or if running on a large screen where there 1929 // is more room for the target window + IME. 1930 final boolean doAutoShow = 1931 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1932 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 1933 || mRes.getConfiguration().isLayoutSizeAtLeast( 1934 Configuration.SCREENLAYOUT_SIZE_LARGE); 1935 final boolean isTextEditor = 1936 (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0; 1937 1938 // We want to start input before showing the IME, but after closing 1939 // it. We want to do this after closing it to help the IME disappear 1940 // more quickly (not get stuck behind it initializing itself for the 1941 // new focused input, even if its window wants to hide the IME). 1942 boolean didStart = false; 1943 1944 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 1945 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 1946 if (!isTextEditor || !doAutoShow) { 1947 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { 1948 // There is no focus view, and this window will 1949 // be behind any soft input window, so hide the 1950 // soft input window if it is shown. 1951 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 1952 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); 1953 } 1954 } else if (isTextEditor && doAutoShow && (softInputMode & 1955 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1956 // There is a focus view, and we are navigating forward 1957 // into the window, so show the input window for the user. 1958 // We only do this automatically if the window can resize 1959 // to accommodate the IME (so what the user sees will give 1960 // them good context without input information being obscured 1961 // by the IME) or if running on a large screen where there 1962 // is more room for the target window + IME. 1963 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 1964 if (attribute != null) { 1965 res = startInputUncheckedLocked(cs, inputContext, attribute, 1966 controlFlags); 1967 didStart = true; 1968 } 1969 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1970 } 1971 break; 1972 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 1973 // Do nothing. 1974 break; 1975 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 1976 if ((softInputMode & 1977 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1978 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 1979 hideCurrentInputLocked(0, null); 1980 } 1981 break; 1982 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 1983 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 1984 hideCurrentInputLocked(0, null); 1985 break; 1986 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 1987 if ((softInputMode & 1988 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1989 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 1990 if (attribute != null) { 1991 res = startInputUncheckedLocked(cs, inputContext, attribute, 1992 controlFlags); 1993 didStart = true; 1994 } 1995 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1996 } 1997 break; 1998 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 1999 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 2000 if (attribute != null) { 2001 res = startInputUncheckedLocked(cs, inputContext, attribute, 2002 controlFlags); 2003 didStart = true; 2004 } 2005 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 2006 break; 2007 } 2008 2009 if (!didStart && attribute != null) { 2010 res = startInputUncheckedLocked(cs, inputContext, attribute, 2011 controlFlags); 2012 } 2013 } 2014 } finally { 2015 Binder.restoreCallingIdentity(ident); 2016 } 2017 2018 return res; 2019 } 2020 2021 @Override 2022 public void showInputMethodPickerFromClient(IInputMethodClient client) { 2023 if (!calledFromValidUser()) { 2024 return; 2025 } 2026 synchronized (mMethodMap) { 2027 if (mCurClient == null || client == null 2028 || mCurClient.client.asBinder() != client.asBinder()) { 2029 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 2030 + Binder.getCallingUid() + ": " + client); 2031 } 2032 2033 // Always call subtype picker, because subtype picker is a superset of input method 2034 // picker. 2035 mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); 2036 } 2037 } 2038 2039 @Override 2040 public void setInputMethod(IBinder token, String id) { 2041 if (!calledFromValidUser()) { 2042 return; 2043 } 2044 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); 2045 } 2046 2047 @Override 2048 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { 2049 if (!calledFromValidUser()) { 2050 return; 2051 } 2052 synchronized (mMethodMap) { 2053 if (subtype != null) { 2054 setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode( 2055 mMethodMap.get(id), subtype.hashCode())); 2056 } else { 2057 setInputMethod(token, id); 2058 } 2059 } 2060 } 2061 2062 @Override 2063 public void showInputMethodAndSubtypeEnablerFromClient( 2064 IInputMethodClient client, String inputMethodId) { 2065 if (!calledFromValidUser()) { 2066 return; 2067 } 2068 synchronized (mMethodMap) { 2069 if (mCurClient == null || client == null 2070 || mCurClient.client.asBinder() != client.asBinder()) { 2071 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); 2072 } 2073 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 2074 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); 2075 } 2076 } 2077 2078 @Override 2079 public boolean switchToLastInputMethod(IBinder token) { 2080 if (!calledFromValidUser()) { 2081 return false; 2082 } 2083 synchronized (mMethodMap) { 2084 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 2085 final InputMethodInfo lastImi; 2086 if (lastIme != null) { 2087 lastImi = mMethodMap.get(lastIme.first); 2088 } else { 2089 lastImi = null; 2090 } 2091 String targetLastImiId = null; 2092 int subtypeId = NOT_A_SUBTYPE_ID; 2093 if (lastIme != null && lastImi != null) { 2094 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId); 2095 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 2096 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID 2097 : mCurrentSubtype.hashCode(); 2098 // If the last IME is the same as the current IME and the last subtype is not 2099 // defined, there is no need to switch to the last IME. 2100 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { 2101 targetLastImiId = lastIme.first; 2102 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 2103 } 2104 } 2105 2106 if (TextUtils.isEmpty(targetLastImiId) 2107 && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) { 2108 // This is a safety net. If the currentSubtype can't be added to the history 2109 // and the framework couldn't find the last ime, we will make the last ime be 2110 // the most applicable enabled keyboard subtype of the system imes. 2111 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 2112 if (enabled != null) { 2113 final int N = enabled.size(); 2114 final String locale = mCurrentSubtype == null 2115 ? mRes.getConfiguration().locale.toString() 2116 : mCurrentSubtype.getLocale(); 2117 for (int i = 0; i < N; ++i) { 2118 final InputMethodInfo imi = enabled.get(i); 2119 if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) { 2120 InputMethodSubtype keyboardSubtype = 2121 InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes, 2122 InputMethodUtils.getSubtypes(imi), 2123 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true); 2124 if (keyboardSubtype != null) { 2125 targetLastImiId = imi.getId(); 2126 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2127 imi, keyboardSubtype.hashCode()); 2128 if(keyboardSubtype.getLocale().equals(locale)) { 2129 break; 2130 } 2131 } 2132 } 2133 } 2134 } 2135 } 2136 2137 if (!TextUtils.isEmpty(targetLastImiId)) { 2138 if (DEBUG) { 2139 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second 2140 + ", from: " + mCurMethodId + ", " + subtypeId); 2141 } 2142 setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId); 2143 return true; 2144 } else { 2145 return false; 2146 } 2147 } 2148 } 2149 2150 @Override 2151 public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { 2152 if (!calledFromValidUser()) { 2153 return false; 2154 } 2155 synchronized (mMethodMap) { 2156 final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( 2157 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype); 2158 if (nextSubtype == null) { 2159 return false; 2160 } 2161 setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId); 2162 return true; 2163 } 2164 } 2165 2166 @Override 2167 public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) { 2168 if (!calledFromValidUser()) { 2169 return false; 2170 } 2171 synchronized (mMethodMap) { 2172 final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( 2173 false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype); 2174 if (nextSubtype == null) { 2175 return false; 2176 } 2177 return true; 2178 } 2179 } 2180 2181 @Override 2182 public InputMethodSubtype getLastInputMethodSubtype() { 2183 if (!calledFromValidUser()) { 2184 return null; 2185 } 2186 synchronized (mMethodMap) { 2187 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 2188 // TODO: Handle the case of the last IME with no subtypes 2189 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 2190 || TextUtils.isEmpty(lastIme.second)) return null; 2191 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 2192 if (lastImi == null) return null; 2193 try { 2194 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 2195 final int lastSubtypeId = 2196 InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 2197 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 2198 return null; 2199 } 2200 return lastImi.getSubtypeAt(lastSubtypeId); 2201 } catch (NumberFormatException e) { 2202 return null; 2203 } 2204 } 2205 } 2206 2207 @Override 2208 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { 2209 if (!calledFromValidUser()) { 2210 return; 2211 } 2212 // By this IPC call, only a process which shares the same uid with the IME can add 2213 // additional input method subtypes to the IME. 2214 if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return; 2215 synchronized (mMethodMap) { 2216 final InputMethodInfo imi = mMethodMap.get(imiId); 2217 if (imi == null) return; 2218 final String[] packageInfos; 2219 try { 2220 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid()); 2221 } catch (RemoteException e) { 2222 Slog.e(TAG, "Failed to get package infos"); 2223 return; 2224 } 2225 if (packageInfos != null) { 2226 final int packageNum = packageInfos.length; 2227 for (int i = 0; i < packageNum; ++i) { 2228 if (packageInfos[i].equals(imi.getPackageName())) { 2229 mFileManager.addInputMethodSubtypes(imi, subtypes); 2230 final long ident = Binder.clearCallingIdentity(); 2231 try { 2232 buildInputMethodListLocked(mMethodList, mMethodMap, 2233 false /* resetDefaultEnabledIme */); 2234 } finally { 2235 Binder.restoreCallingIdentity(ident); 2236 } 2237 return; 2238 } 2239 } 2240 } 2241 } 2242 return; 2243 } 2244 2245 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { 2246 synchronized (mMethodMap) { 2247 if (token == null) { 2248 if (mContext.checkCallingOrSelfPermission( 2249 android.Manifest.permission.WRITE_SECURE_SETTINGS) 2250 != PackageManager.PERMISSION_GRANTED) { 2251 throw new SecurityException( 2252 "Using null token requires permission " 2253 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 2254 } 2255 } else if (mCurToken != token) { 2256 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 2257 + " token: " + token); 2258 return; 2259 } 2260 2261 final long ident = Binder.clearCallingIdentity(); 2262 try { 2263 setInputMethodLocked(id, subtypeId); 2264 } finally { 2265 Binder.restoreCallingIdentity(ident); 2266 } 2267 } 2268 } 2269 2270 @Override 2271 public void hideMySoftInput(IBinder token, int flags) { 2272 if (!calledFromValidUser()) { 2273 return; 2274 } 2275 synchronized (mMethodMap) { 2276 if (token == null || mCurToken != token) { 2277 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " 2278 + Binder.getCallingUid() + " token: " + token); 2279 return; 2280 } 2281 long ident = Binder.clearCallingIdentity(); 2282 try { 2283 hideCurrentInputLocked(flags, null); 2284 } finally { 2285 Binder.restoreCallingIdentity(ident); 2286 } 2287 } 2288 } 2289 2290 @Override 2291 public void showMySoftInput(IBinder token, int flags) { 2292 if (!calledFromValidUser()) { 2293 return; 2294 } 2295 synchronized (mMethodMap) { 2296 if (token == null || mCurToken != token) { 2297 Slog.w(TAG, "Ignoring showMySoftInput of uid " 2298 + Binder.getCallingUid() + " token: " + token); 2299 return; 2300 } 2301 long ident = Binder.clearCallingIdentity(); 2302 try { 2303 showCurrentInputLocked(flags, null); 2304 } finally { 2305 Binder.restoreCallingIdentity(ident); 2306 } 2307 } 2308 } 2309 2310 void setEnabledSessionInMainThread(SessionState session) { 2311 if (mEnabledSession != session) { 2312 if (mEnabledSession != null) { 2313 try { 2314 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 2315 mEnabledSession.method.setSessionEnabled( 2316 mEnabledSession.session, false); 2317 } catch (RemoteException e) { 2318 } 2319 } 2320 mEnabledSession = session; 2321 try { 2322 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 2323 session.method.setSessionEnabled( 2324 session.session, true); 2325 } catch (RemoteException e) { 2326 } 2327 } 2328 } 2329 2330 @Override 2331 public boolean handleMessage(Message msg) { 2332 SomeArgs args; 2333 switch (msg.what) { 2334 case MSG_SHOW_IM_PICKER: 2335 showInputMethodMenu(); 2336 return true; 2337 2338 case MSG_SHOW_IM_SUBTYPE_PICKER: 2339 showInputMethodSubtypeMenu(); 2340 return true; 2341 2342 case MSG_SHOW_IM_SUBTYPE_ENABLER: 2343 args = (SomeArgs)msg.obj; 2344 showInputMethodAndSubtypeEnabler((String)args.arg1); 2345 args.recycle(); 2346 return true; 2347 2348 case MSG_SHOW_IM_CONFIG: 2349 showConfigureInputMethods(); 2350 return true; 2351 2352 // --------------------------------------------------------- 2353 2354 case MSG_UNBIND_INPUT: 2355 try { 2356 ((IInputMethod)msg.obj).unbindInput(); 2357 } catch (RemoteException e) { 2358 // There is nothing interesting about the method dying. 2359 } 2360 return true; 2361 case MSG_BIND_INPUT: 2362 args = (SomeArgs)msg.obj; 2363 try { 2364 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 2365 } catch (RemoteException e) { 2366 } 2367 args.recycle(); 2368 return true; 2369 case MSG_SHOW_SOFT_INPUT: 2370 args = (SomeArgs)msg.obj; 2371 try { 2372 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" 2373 + msg.arg1 + ", " + args.arg2 + ")"); 2374 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2); 2375 } catch (RemoteException e) { 2376 } 2377 args.recycle(); 2378 return true; 2379 case MSG_HIDE_SOFT_INPUT: 2380 args = (SomeArgs)msg.obj; 2381 try { 2382 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " 2383 + args.arg2 + ")"); 2384 ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2); 2385 } catch (RemoteException e) { 2386 } 2387 args.recycle(); 2388 return true; 2389 case MSG_ATTACH_TOKEN: 2390 args = (SomeArgs)msg.obj; 2391 try { 2392 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); 2393 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); 2394 } catch (RemoteException e) { 2395 } 2396 args.recycle(); 2397 return true; 2398 case MSG_CREATE_SESSION: { 2399 args = (SomeArgs)msg.obj; 2400 IInputMethod method = (IInputMethod)args.arg1; 2401 InputChannel channel = (InputChannel)args.arg2; 2402 try { 2403 method.createSession(channel, (IInputSessionCallback)args.arg3); 2404 } catch (RemoteException e) { 2405 } finally { 2406 // Dispose the channel if the input method is not local to this process 2407 // because the remote proxy will get its own copy when unparceled. 2408 if (channel != null && Binder.isProxy(method)) { 2409 channel.dispose(); 2410 } 2411 } 2412 args.recycle(); 2413 return true; 2414 } 2415 // --------------------------------------------------------- 2416 2417 case MSG_START_INPUT: 2418 args = (SomeArgs)msg.obj; 2419 try { 2420 SessionState session = (SessionState)args.arg1; 2421 setEnabledSessionInMainThread(session); 2422 session.method.startInput((IInputContext)args.arg2, 2423 (EditorInfo)args.arg3); 2424 } catch (RemoteException e) { 2425 } 2426 args.recycle(); 2427 return true; 2428 case MSG_RESTART_INPUT: 2429 args = (SomeArgs)msg.obj; 2430 try { 2431 SessionState session = (SessionState)args.arg1; 2432 setEnabledSessionInMainThread(session); 2433 session.method.restartInput((IInputContext)args.arg2, 2434 (EditorInfo)args.arg3); 2435 } catch (RemoteException e) { 2436 } 2437 args.recycle(); 2438 return true; 2439 2440 // --------------------------------------------------------- 2441 2442 case MSG_UNBIND_METHOD: 2443 try { 2444 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1); 2445 } catch (RemoteException e) { 2446 // There is nothing interesting about the last client dying. 2447 } 2448 return true; 2449 case MSG_BIND_METHOD: { 2450 args = (SomeArgs)msg.obj; 2451 IInputMethodClient client = (IInputMethodClient)args.arg1; 2452 InputBindResult res = (InputBindResult)args.arg2; 2453 try { 2454 client.onBindMethod(res); 2455 } catch (RemoteException e) { 2456 Slog.w(TAG, "Client died receiving input method " + args.arg2); 2457 } finally { 2458 // Dispose the channel if the input method is not local to this process 2459 // because the remote proxy will get its own copy when unparceled. 2460 if (res.channel != null && Binder.isProxy(client)) { 2461 res.channel.dispose(); 2462 } 2463 } 2464 args.recycle(); 2465 return true; 2466 } 2467 case MSG_SET_ACTIVE: 2468 try { 2469 ((ClientState)msg.obj).client.setActive(msg.arg1 != 0); 2470 } catch (RemoteException e) { 2471 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " 2472 + ((ClientState)msg.obj).pid + " uid " 2473 + ((ClientState)msg.obj).uid); 2474 } 2475 return true; 2476 2477 // -------------------------------------------------------------- 2478 case MSG_HARD_KEYBOARD_SWITCH_CHANGED: 2479 mHardKeyboardListener.handleHardKeyboardStatusChange( 2480 msg.arg1 == 1, msg.arg2 == 1); 2481 return true; 2482 } 2483 return false; 2484 } 2485 2486 private boolean chooseNewDefaultIMELocked() { 2487 final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( 2488 mSettings.getEnabledInputMethodListLocked()); 2489 if (imi != null) { 2490 if (DEBUG) { 2491 Slog.d(TAG, "New default IME was selected: " + imi.getId()); 2492 } 2493 resetSelectedInputMethodAndSubtypeLocked(imi.getId()); 2494 return true; 2495 } 2496 2497 return false; 2498 } 2499 2500 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, 2501 HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) { 2502 if (DEBUG) { 2503 Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme 2504 + " \n ------ \n" + InputMethodUtils.getStackTrace()); 2505 } 2506 list.clear(); 2507 map.clear(); 2508 2509 // Use for queryIntentServicesAsUser 2510 final PackageManager pm = mContext.getPackageManager(); 2511 String disabledSysImes = mSettings.getDisabledSystemInputMethods(); 2512 if (disabledSysImes == null) disabledSysImes = ""; 2513 2514 final List<ResolveInfo> services = pm.queryIntentServicesAsUser( 2515 new Intent(InputMethod.SERVICE_INTERFACE), 2516 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 2517 mSettings.getCurrentUserId()); 2518 2519 final HashMap<String, List<InputMethodSubtype>> additionalSubtypes = 2520 mFileManager.getAllAdditionalInputMethodSubtypes(); 2521 for (int i = 0; i < services.size(); ++i) { 2522 ResolveInfo ri = services.get(i); 2523 ServiceInfo si = ri.serviceInfo; 2524 ComponentName compName = new ComponentName(si.packageName, si.name); 2525 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals( 2526 si.permission)) { 2527 Slog.w(TAG, "Skipping input method " + compName 2528 + ": it does not require the permission " 2529 + android.Manifest.permission.BIND_INPUT_METHOD); 2530 continue; 2531 } 2532 2533 if (DEBUG) Slog.d(TAG, "Checking " + compName); 2534 2535 try { 2536 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes); 2537 list.add(p); 2538 final String id = p.getId(); 2539 map.put(id, p); 2540 2541 if (DEBUG) { 2542 Slog.d(TAG, "Found an input method " + p); 2543 } 2544 2545 } catch (XmlPullParserException e) { 2546 Slog.w(TAG, "Unable to load input method " + compName, e); 2547 } catch (IOException e) { 2548 Slog.w(TAG, "Unable to load input method " + compName, e); 2549 } 2550 } 2551 2552 if (resetDefaultEnabledIme) { 2553 final ArrayList<InputMethodInfo> defaultEnabledIme = 2554 InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list); 2555 for (int i = 0; i < defaultEnabledIme.size(); ++i) { 2556 final InputMethodInfo imi = defaultEnabledIme.get(i); 2557 if (DEBUG) { 2558 Slog.d(TAG, "--- enable ime = " + imi); 2559 } 2560 setInputMethodEnabledLocked(imi.getId(), true); 2561 } 2562 } 2563 2564 final String defaultImiId = mSettings.getSelectedInputMethod(); 2565 if (!TextUtils.isEmpty(defaultImiId)) { 2566 if (!map.containsKey(defaultImiId)) { 2567 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); 2568 if (chooseNewDefaultIMELocked()) { 2569 updateFromSettingsLocked(true); 2570 } 2571 } else { 2572 // Double check that the default IME is certainly enabled. 2573 setInputMethodEnabledLocked(defaultImiId, true); 2574 } 2575 } 2576 } 2577 2578 // ---------------------------------------------------------------------- 2579 2580 private void showInputMethodMenu() { 2581 showInputMethodMenuInternal(false); 2582 } 2583 2584 private void showInputMethodSubtypeMenu() { 2585 showInputMethodMenuInternal(true); 2586 } 2587 2588 private void showInputMethodAndSubtypeEnabler(String inputMethodId) { 2589 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 2590 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2591 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2592 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2593 if (!TextUtils.isEmpty(inputMethodId)) { 2594 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); 2595 } 2596 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); 2597 } 2598 2599 private void showConfigureInputMethods() { 2600 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); 2601 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2602 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2603 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2604 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); 2605 } 2606 2607 private boolean isScreenLocked() { 2608 return mKeyguardManager != null 2609 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); 2610 } 2611 private void showInputMethodMenuInternal(boolean showSubtypes) { 2612 if (DEBUG) Slog.v(TAG, "Show switching menu"); 2613 2614 final Context context = mContext; 2615 final boolean isScreenLocked = isScreenLocked(); 2616 2617 final String lastInputMethodId = mSettings.getSelectedInputMethod(); 2618 int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); 2619 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 2620 2621 synchronized (mMethodMap) { 2622 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 2623 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 2624 if (immis == null || immis.size() == 0) { 2625 return; 2626 } 2627 2628 hideInputMethodMenuLocked(); 2629 2630 final List<ImeSubtypeListItem> imList = 2631 mImListManager.getSortedInputMethodAndSubtypeList( 2632 showSubtypes, mInputShown, isScreenLocked); 2633 2634 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { 2635 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked(); 2636 if (currentSubtype != null) { 2637 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 2638 lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2639 currentImi, currentSubtype.hashCode()); 2640 } 2641 } 2642 2643 final int N = imList.size(); 2644 mIms = new InputMethodInfo[N]; 2645 mSubtypeIds = new int[N]; 2646 int checkedItem = 0; 2647 for (int i = 0; i < N; ++i) { 2648 final ImeSubtypeListItem item = imList.get(i); 2649 mIms[i] = item.mImi; 2650 mSubtypeIds[i] = item.mSubtypeId; 2651 if (mIms[i].getId().equals(lastInputMethodId)) { 2652 int subtypeId = mSubtypeIds[i]; 2653 if ((subtypeId == NOT_A_SUBTYPE_ID) 2654 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 2655 || (subtypeId == lastInputMethodSubtypeId)) { 2656 checkedItem = i; 2657 } 2658 } 2659 } 2660 final TypedArray a = context.obtainStyledAttributes(null, 2661 com.android.internal.R.styleable.DialogPreference, 2662 com.android.internal.R.attr.alertDialogStyle, 0); 2663 mDialogBuilder = new AlertDialog.Builder(context) 2664 .setOnCancelListener(new OnCancelListener() { 2665 @Override 2666 public void onCancel(DialogInterface dialog) { 2667 hideInputMethodMenu(); 2668 } 2669 }) 2670 .setIcon(a.getDrawable( 2671 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 2672 a.recycle(); 2673 final LayoutInflater inflater = 2674 (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2675 final View tv = inflater.inflate( 2676 com.android.internal.R.layout.input_method_switch_dialog_title, null); 2677 mDialogBuilder.setCustomTitle(tv); 2678 2679 // Setup layout for a toggle switch of the hardware keyboard 2680 mSwitchingDialogTitleView = tv; 2681 mSwitchingDialogTitleView.findViewById( 2682 com.android.internal.R.id.hard_keyboard_section).setVisibility( 2683 mWindowManagerService.isHardKeyboardAvailable() ? 2684 View.VISIBLE : View.GONE); 2685 final Switch hardKeySwitch = ((Switch)mSwitchingDialogTitleView.findViewById( 2686 com.android.internal.R.id.hard_keyboard_switch)); 2687 hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled()); 2688 hardKeySwitch.setOnCheckedChangeListener( 2689 new OnCheckedChangeListener() { 2690 @Override 2691 public void onCheckedChanged( 2692 CompoundButton buttonView, boolean isChecked) { 2693 mWindowManagerService.setHardKeyboardEnabled(isChecked); 2694 // Ensure that the input method dialog is dismissed when changing 2695 // the hardware keyboard state. 2696 hideInputMethodMenu(); 2697 } 2698 }); 2699 2700 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context, 2701 com.android.internal.R.layout.simple_list_item_2_single_choice, imList, 2702 checkedItem); 2703 2704 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, 2705 new AlertDialog.OnClickListener() { 2706 @Override 2707 public void onClick(DialogInterface dialog, int which) { 2708 synchronized (mMethodMap) { 2709 if (mIms == null || mIms.length <= which 2710 || mSubtypeIds == null || mSubtypeIds.length <= which) { 2711 return; 2712 } 2713 InputMethodInfo im = mIms[which]; 2714 int subtypeId = mSubtypeIds[which]; 2715 adapter.mCheckedItem = which; 2716 adapter.notifyDataSetChanged(); 2717 hideInputMethodMenu(); 2718 if (im != null) { 2719 if ((subtypeId < 0) 2720 || (subtypeId >= im.getSubtypeCount())) { 2721 subtypeId = NOT_A_SUBTYPE_ID; 2722 } 2723 setInputMethodLocked(im.getId(), subtypeId); 2724 } 2725 } 2726 } 2727 }); 2728 2729 if (showSubtypes && !isScreenLocked) { 2730 mDialogBuilder.setPositiveButton( 2731 com.android.internal.R.string.configure_input_methods, 2732 new DialogInterface.OnClickListener() { 2733 @Override 2734 public void onClick(DialogInterface dialog, int whichButton) { 2735 showConfigureInputMethods(); 2736 } 2737 }); 2738 } 2739 mSwitchingDialog = mDialogBuilder.create(); 2740 mSwitchingDialog.setCanceledOnTouchOutside(true); 2741 mSwitchingDialog.getWindow().setType( 2742 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 2743 mSwitchingDialog.getWindow().getAttributes().privateFlags |= 2744 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 2745 mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method"); 2746 mSwitchingDialog.show(); 2747 } 2748 } 2749 2750 private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { 2751 public final CharSequence mImeName; 2752 public final CharSequence mSubtypeName; 2753 public final InputMethodInfo mImi; 2754 public final int mSubtypeId; 2755 private final boolean mIsSystemLocale; 2756 private final boolean mIsSystemLanguage; 2757 2758 public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, 2759 InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { 2760 mImeName = imeName; 2761 mSubtypeName = subtypeName; 2762 mImi = imi; 2763 mSubtypeId = subtypeId; 2764 if (TextUtils.isEmpty(subtypeLocale)) { 2765 mIsSystemLocale = false; 2766 mIsSystemLanguage = false; 2767 } else { 2768 mIsSystemLocale = subtypeLocale.equals(systemLocale); 2769 mIsSystemLanguage = mIsSystemLocale 2770 || subtypeLocale.startsWith(systemLocale.substring(0, 2)); 2771 } 2772 } 2773 2774 @Override 2775 public int compareTo(ImeSubtypeListItem other) { 2776 if (TextUtils.isEmpty(mImeName)) { 2777 return 1; 2778 } 2779 if (TextUtils.isEmpty(other.mImeName)) { 2780 return -1; 2781 } 2782 if (!TextUtils.equals(mImeName, other.mImeName)) { 2783 return mImeName.toString().compareTo(other.mImeName.toString()); 2784 } 2785 if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) { 2786 return 0; 2787 } 2788 if (mIsSystemLocale) { 2789 return -1; 2790 } 2791 if (other.mIsSystemLocale) { 2792 return 1; 2793 } 2794 if (mIsSystemLanguage) { 2795 return -1; 2796 } 2797 if (other.mIsSystemLanguage) { 2798 return 1; 2799 } 2800 if (TextUtils.isEmpty(mSubtypeName)) { 2801 return 1; 2802 } 2803 if (TextUtils.isEmpty(other.mSubtypeName)) { 2804 return -1; 2805 } 2806 return mSubtypeName.toString().compareTo(other.mSubtypeName.toString()); 2807 } 2808 } 2809 2810 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> { 2811 private final LayoutInflater mInflater; 2812 private final int mTextViewResourceId; 2813 private final List<ImeSubtypeListItem> mItemsList; 2814 public int mCheckedItem; 2815 public ImeSubtypeListAdapter(Context context, int textViewResourceId, 2816 List<ImeSubtypeListItem> itemsList, int checkedItem) { 2817 super(context, textViewResourceId, itemsList); 2818 mTextViewResourceId = textViewResourceId; 2819 mItemsList = itemsList; 2820 mCheckedItem = checkedItem; 2821 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2822 } 2823 2824 @Override 2825 public View getView(int position, View convertView, ViewGroup parent) { 2826 final View view = convertView != null ? convertView 2827 : mInflater.inflate(mTextViewResourceId, null); 2828 if (position < 0 || position >= mItemsList.size()) return view; 2829 final ImeSubtypeListItem item = mItemsList.get(position); 2830 final CharSequence imeName = item.mImeName; 2831 final CharSequence subtypeName = item.mSubtypeName; 2832 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1); 2833 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2); 2834 if (TextUtils.isEmpty(subtypeName)) { 2835 firstTextView.setText(imeName); 2836 secondTextView.setVisibility(View.GONE); 2837 } else { 2838 firstTextView.setText(subtypeName); 2839 secondTextView.setText(imeName); 2840 secondTextView.setVisibility(View.VISIBLE); 2841 } 2842 final RadioButton radioButton = 2843 (RadioButton)view.findViewById(com.android.internal.R.id.radio); 2844 radioButton.setChecked(position == mCheckedItem); 2845 return view; 2846 } 2847 } 2848 2849 void hideInputMethodMenu() { 2850 synchronized (mMethodMap) { 2851 hideInputMethodMenuLocked(); 2852 } 2853 } 2854 2855 void hideInputMethodMenuLocked() { 2856 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 2857 2858 if (mSwitchingDialog != null) { 2859 mSwitchingDialog.dismiss(); 2860 mSwitchingDialog = null; 2861 } 2862 2863 mDialogBuilder = null; 2864 mIms = null; 2865 } 2866 2867 // ---------------------------------------------------------------------- 2868 2869 @Override 2870 public boolean setInputMethodEnabled(String id, boolean enabled) { 2871 // TODO: Make this work even for non-current users? 2872 if (!calledFromValidUser()) { 2873 return false; 2874 } 2875 synchronized (mMethodMap) { 2876 if (mContext.checkCallingOrSelfPermission( 2877 android.Manifest.permission.WRITE_SECURE_SETTINGS) 2878 != PackageManager.PERMISSION_GRANTED) { 2879 throw new SecurityException( 2880 "Requires permission " 2881 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 2882 } 2883 2884 long ident = Binder.clearCallingIdentity(); 2885 try { 2886 return setInputMethodEnabledLocked(id, enabled); 2887 } finally { 2888 Binder.restoreCallingIdentity(ident); 2889 } 2890 } 2891 } 2892 2893 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 2894 // Make sure this is a valid input method. 2895 InputMethodInfo imm = mMethodMap.get(id); 2896 if (imm == null) { 2897 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 2898 } 2899 2900 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 2901 .getEnabledInputMethodsAndSubtypeListLocked(); 2902 2903 if (enabled) { 2904 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 2905 if (pair.first.equals(id)) { 2906 // We are enabling this input method, but it is already enabled. 2907 // Nothing to do. The previous state was enabled. 2908 return true; 2909 } 2910 } 2911 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 2912 // Previous state was disabled. 2913 return false; 2914 } else { 2915 StringBuilder builder = new StringBuilder(); 2916 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2917 builder, enabledInputMethodsList, id)) { 2918 // Disabled input method is currently selected, switch to another one. 2919 final String selId = mSettings.getSelectedInputMethod(); 2920 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 2921 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 2922 resetSelectedInputMethodAndSubtypeLocked(""); 2923 } 2924 // Previous state was enabled. 2925 return true; 2926 } else { 2927 // We are disabling the input method but it is already disabled. 2928 // Nothing to do. The previous state was disabled. 2929 return false; 2930 } 2931 } 2932 } 2933 2934 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 2935 boolean setSubtypeOnly) { 2936 // Update the history of InputMethod and Subtype 2937 mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); 2938 2939 // Set Subtype here 2940 if (imi == null || subtypeId < 0) { 2941 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2942 mCurrentSubtype = null; 2943 } else { 2944 if (subtypeId < imi.getSubtypeCount()) { 2945 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); 2946 mSettings.putSelectedSubtype(subtype.hashCode()); 2947 mCurrentSubtype = subtype; 2948 } else { 2949 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2950 // If the subtype is not specified, choose the most applicable one 2951 mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); 2952 } 2953 } 2954 2955 // Workaround. 2956 // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked 2957 // IMEs are not recognized and considered uninstalled. 2958 // Actually, we can't move everything after SystemReady because 2959 // IMMS needs to run in the encryption lock screen. So, we just skip changing 2960 // the default IME here and try cheking the default IME again in systemReady(). 2961 // TODO: Do nothing before system ready and implement a separated logic for 2962 // the encryption lock screen. 2963 // TODO: ASEC should be ready before IMMS is instantiated. 2964 if (mSystemReady && !setSubtypeOnly) { 2965 // Set InputMethod here 2966 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 2967 } 2968 } 2969 2970 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 2971 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 2972 int lastSubtypeId = NOT_A_SUBTYPE_ID; 2973 // newDefaultIme is empty when there is no candidate for the selected IME. 2974 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 2975 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 2976 if (subtypeHashCode != null) { 2977 try { 2978 lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2979 imi, Integer.valueOf(subtypeHashCode)); 2980 } catch (NumberFormatException e) { 2981 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 2982 } 2983 } 2984 } 2985 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 2986 } 2987 2988 // If there are no selected shortcuts, tries finding the most applicable ones. 2989 private Pair<InputMethodInfo, InputMethodSubtype> 2990 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2991 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2992 InputMethodInfo mostApplicableIMI = null; 2993 InputMethodSubtype mostApplicableSubtype = null; 2994 boolean foundInSystemIME = false; 2995 2996 // Search applicable subtype for each InputMethodInfo 2997 for (InputMethodInfo imi: imis) { 2998 final String imiId = imi.getId(); 2999 if (foundInSystemIME && !imiId.equals(mCurMethodId)) { 3000 continue; 3001 } 3002 InputMethodSubtype subtype = null; 3003 final List<InputMethodSubtype> enabledSubtypes = 3004 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 3005 // 1. Search by the current subtype's locale from enabledSubtypes. 3006 if (mCurrentSubtype != null) { 3007 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3008 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); 3009 } 3010 // 2. Search by the system locale from enabledSubtypes. 3011 // 3. Search the first enabled subtype matched with mode from enabledSubtypes. 3012 if (subtype == null) { 3013 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3014 mRes, enabledSubtypes, mode, null, true); 3015 } 3016 final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes = 3017 InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode); 3018 final ArrayList<InputMethodSubtype> subtypesForSearch = 3019 overridingImplicitlyEnabledSubtypes.isEmpty() 3020 ? InputMethodUtils.getSubtypes(imi) 3021 : overridingImplicitlyEnabledSubtypes; 3022 // 4. Search by the current subtype's locale from all subtypes. 3023 if (subtype == null && mCurrentSubtype != null) { 3024 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3025 mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false); 3026 } 3027 // 5. Search by the system locale from all subtypes. 3028 // 6. Search the first enabled subtype matched with mode from all subtypes. 3029 if (subtype == null) { 3030 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3031 mRes, subtypesForSearch, mode, null, true); 3032 } 3033 if (subtype != null) { 3034 if (imiId.equals(mCurMethodId)) { 3035 // The current input method is the most applicable IME. 3036 mostApplicableIMI = imi; 3037 mostApplicableSubtype = subtype; 3038 break; 3039 } else if (!foundInSystemIME) { 3040 // The system input method is 2nd applicable IME. 3041 mostApplicableIMI = imi; 3042 mostApplicableSubtype = subtype; 3043 if ((imi.getServiceInfo().applicationInfo.flags 3044 & ApplicationInfo.FLAG_SYSTEM) != 0) { 3045 foundInSystemIME = true; 3046 } 3047 } 3048 } 3049 } 3050 if (DEBUG) { 3051 if (mostApplicableIMI != null) { 3052 Slog.w(TAG, "Most applicable shortcut input method was:" 3053 + mostApplicableIMI.getId()); 3054 if (mostApplicableSubtype != null) { 3055 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 3056 + "," + mostApplicableSubtype.getMode() + "," 3057 + mostApplicableSubtype.getLocale()); 3058 } 3059 } 3060 } 3061 if (mostApplicableIMI != null) { 3062 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 3063 mostApplicableSubtype); 3064 } else { 3065 return null; 3066 } 3067 } 3068 3069 /** 3070 * @return Return the current subtype of this input method. 3071 */ 3072 @Override 3073 public InputMethodSubtype getCurrentInputMethodSubtype() { 3074 // TODO: Make this work even for non-current users? 3075 if (!calledFromValidUser()) { 3076 return null; 3077 } 3078 synchronized (mMethodMap) { 3079 return getCurrentInputMethodSubtypeLocked(); 3080 } 3081 } 3082 3083 private InputMethodSubtype getCurrentInputMethodSubtypeLocked() { 3084 if (mCurMethodId == null) { 3085 return null; 3086 } 3087 final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); 3088 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 3089 if (imi == null || imi.getSubtypeCount() == 0) { 3090 return null; 3091 } 3092 if (!subtypeIsSelected || mCurrentSubtype == null 3093 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { 3094 int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId); 3095 if (subtypeId == NOT_A_SUBTYPE_ID) { 3096 // If there are no selected subtypes, the framework will try to find 3097 // the most applicable subtype from explicitly or implicitly enabled 3098 // subtypes. 3099 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 3100 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 3101 // If there is only one explicitly or implicitly enabled subtype, 3102 // just returns it. 3103 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 3104 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); 3105 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { 3106 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3107 mRes, explicitlyOrImplicitlyEnabledSubtypes, 3108 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true); 3109 if (mCurrentSubtype == null) { 3110 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3111 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, 3112 true); 3113 } 3114 } 3115 } else { 3116 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId); 3117 } 3118 } 3119 return mCurrentSubtype; 3120 } 3121 3122 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 3123 InputMethodSubtype subtype) { 3124 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 3125 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 3126 } else { 3127 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 3128 subtypes.add(subtype); 3129 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 3130 } 3131 } 3132 3133 // TODO: We should change the return type from List to List<Parcelable> 3134 @SuppressWarnings("rawtypes") 3135 @Override 3136 public List getShortcutInputMethodsAndSubtypes() { 3137 synchronized (mMethodMap) { 3138 ArrayList<Object> ret = new ArrayList<Object>(); 3139 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 3140 // If there are no selected shortcut subtypes, the framework will try to find 3141 // the most applicable subtype from all subtypes whose mode is 3142 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 3143 Pair<InputMethodInfo, InputMethodSubtype> info = 3144 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 3145 InputMethodUtils.SUBTYPE_MODE_VOICE); 3146 if (info != null) { 3147 ret.add(info.first); 3148 ret.add(info.second); 3149 } 3150 return ret; 3151 } 3152 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 3153 ret.add(imi); 3154 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 3155 ret.add(subtype); 3156 } 3157 } 3158 return ret; 3159 } 3160 } 3161 3162 @Override 3163 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 3164 // TODO: Make this work even for non-current users? 3165 if (!calledFromValidUser()) { 3166 return false; 3167 } 3168 synchronized (mMethodMap) { 3169 if (subtype != null && mCurMethodId != null) { 3170 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 3171 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()); 3172 if (subtypeId != NOT_A_SUBTYPE_ID) { 3173 setInputMethodLocked(mCurMethodId, subtypeId); 3174 return true; 3175 } 3176 } 3177 return false; 3178 } 3179 } 3180 3181 private static class InputMethodAndSubtypeListManager { 3182 private final Context mContext; 3183 // Used to load label 3184 private final PackageManager mPm; 3185 private final InputMethodManagerService mImms; 3186 private final String mSystemLocaleStr; 3187 public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) { 3188 mContext = context; 3189 mPm = context.getPackageManager(); 3190 mImms = imms; 3191 final Locale locale = context.getResources().getConfiguration().locale; 3192 mSystemLocaleStr = locale != null ? locale.toString() : ""; 3193 } 3194 3195 private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = 3196 new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( 3197 new Comparator<InputMethodInfo>() { 3198 @Override 3199 public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { 3200 if (imi2 == null) return 0; 3201 if (imi1 == null) return 1; 3202 if (mPm == null) { 3203 return imi1.getId().compareTo(imi2.getId()); 3204 } 3205 CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); 3206 CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); 3207 return imiId1.toString().compareTo(imiId2.toString()); 3208 } 3209 }); 3210 3211 public ImeSubtypeListItem getNextInputMethod( 3212 boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { 3213 if (imi == null) { 3214 return null; 3215 } 3216 final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList(); 3217 if (imList.size() <= 1) { 3218 return null; 3219 } 3220 final int N = imList.size(); 3221 final int currentSubtypeId = subtype != null 3222 ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) 3223 : NOT_A_SUBTYPE_ID; 3224 for (int i = 0; i < N; ++i) { 3225 final ImeSubtypeListItem isli = imList.get(i); 3226 if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) { 3227 if (!onlyCurrentIme) { 3228 return imList.get((i + 1) % N); 3229 } 3230 for (int j = 0; j < N - 1; ++j) { 3231 final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); 3232 if (candidate.mImi.equals(imi)) { 3233 return candidate; 3234 } 3235 } 3236 return null; 3237 } 3238 } 3239 return null; 3240 } 3241 3242 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { 3243 return getSortedInputMethodAndSubtypeList(true, false, false); 3244 } 3245 3246 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes, 3247 boolean inputShown, boolean isScreenLocked) { 3248 final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); 3249 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 3250 mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 3251 if (immis == null || immis.size() == 0) { 3252 return Collections.emptyList(); 3253 } 3254 mSortedImmis.clear(); 3255 mSortedImmis.putAll(immis); 3256 for (InputMethodInfo imi : mSortedImmis.keySet()) { 3257 if (imi == null) continue; 3258 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); 3259 HashSet<String> enabledSubtypeSet = new HashSet<String>(); 3260 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { 3261 enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); 3262 } 3263 final CharSequence imeLabel = imi.loadLabel(mPm); 3264 if (showSubtypes && enabledSubtypeSet.size() > 0) { 3265 final int subtypeCount = imi.getSubtypeCount(); 3266 if (DEBUG) { 3267 Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); 3268 } 3269 for (int j = 0; j < subtypeCount; ++j) { 3270 final InputMethodSubtype subtype = imi.getSubtypeAt(j); 3271 final String subtypeHashCode = String.valueOf(subtype.hashCode()); 3272 // We show all enabled IMEs and subtypes when an IME is shown. 3273 if (enabledSubtypeSet.contains(subtypeHashCode) 3274 && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { 3275 final CharSequence subtypeLabel = 3276 subtype.overridesImplicitlyEnabledSubtype() ? null 3277 : subtype.getDisplayName(mContext, imi.getPackageName(), 3278 imi.getServiceInfo().applicationInfo); 3279 imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j, 3280 subtype.getLocale(), mSystemLocaleStr)); 3281 3282 // Removing this subtype from enabledSubtypeSet because we no longer 3283 // need to add an entry of this subtype to imList to avoid duplicated 3284 // entries. 3285 enabledSubtypeSet.remove(subtypeHashCode); 3286 } 3287 } 3288 } else { 3289 imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, 3290 null, mSystemLocaleStr)); 3291 } 3292 } 3293 Collections.sort(imList); 3294 return imList; 3295 } 3296 } 3297 3298 // TODO: Cache the state for each user and reset when the cached user is removed. 3299 private static class InputMethodFileManager { 3300 private static final String SYSTEM_PATH = "system"; 3301 private static final String INPUT_METHOD_PATH = "inputmethod"; 3302 private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml"; 3303 private static final String NODE_SUBTYPES = "subtypes"; 3304 private static final String NODE_SUBTYPE = "subtype"; 3305 private static final String NODE_IMI = "imi"; 3306 private static final String ATTR_ID = "id"; 3307 private static final String ATTR_LABEL = "label"; 3308 private static final String ATTR_ICON = "icon"; 3309 private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale"; 3310 private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode"; 3311 private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue"; 3312 private static final String ATTR_IS_AUXILIARY = "isAuxiliary"; 3313 private final AtomicFile mAdditionalInputMethodSubtypeFile; 3314 private final HashMap<String, InputMethodInfo> mMethodMap; 3315 private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap = 3316 new HashMap<String, List<InputMethodSubtype>>(); 3317 public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) { 3318 if (methodMap == null) { 3319 throw new NullPointerException("methodMap is null"); 3320 } 3321 mMethodMap = methodMap; 3322 final File systemDir = userId == UserHandle.USER_OWNER 3323 ? new File(Environment.getDataDirectory(), SYSTEM_PATH) 3324 : Environment.getUserSystemDirectory(userId); 3325 final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH); 3326 if (!inputMethodDir.mkdirs()) { 3327 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath()); 3328 } 3329 final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME); 3330 mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile); 3331 if (!subtypeFile.exists()) { 3332 // If "subtypes.xml" doesn't exist, create a blank file. 3333 writeAdditionalInputMethodSubtypes( 3334 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap); 3335 } else { 3336 readAdditionalInputMethodSubtypes( 3337 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile); 3338 } 3339 } 3340 3341 private void deleteAllInputMethodSubtypes(String imiId) { 3342 synchronized (mMethodMap) { 3343 mAdditionalSubtypesMap.remove(imiId); 3344 writeAdditionalInputMethodSubtypes( 3345 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); 3346 } 3347 } 3348 3349 public void addInputMethodSubtypes( 3350 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) { 3351 synchronized (mMethodMap) { 3352 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 3353 final int N = additionalSubtypes.length; 3354 for (int i = 0; i < N; ++i) { 3355 final InputMethodSubtype subtype = additionalSubtypes[i]; 3356 if (!subtypes.contains(subtype)) { 3357 subtypes.add(subtype); 3358 } else { 3359 Slog.w(TAG, "Duplicated subtype definition found: " 3360 + subtype.getLocale() + ", " + subtype.getMode()); 3361 } 3362 } 3363 mAdditionalSubtypesMap.put(imi.getId(), subtypes); 3364 writeAdditionalInputMethodSubtypes( 3365 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); 3366 } 3367 } 3368 3369 public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() { 3370 synchronized (mMethodMap) { 3371 return mAdditionalSubtypesMap; 3372 } 3373 } 3374 3375 private static void writeAdditionalInputMethodSubtypes( 3376 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile, 3377 HashMap<String, InputMethodInfo> methodMap) { 3378 // Safety net for the case that this function is called before methodMap is set. 3379 final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0; 3380 FileOutputStream fos = null; 3381 try { 3382 fos = subtypesFile.startWrite(); 3383 final XmlSerializer out = new FastXmlSerializer(); 3384 out.setOutput(fos, "utf-8"); 3385 out.startDocument(null, true); 3386 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 3387 out.startTag(null, NODE_SUBTYPES); 3388 for (String imiId : allSubtypes.keySet()) { 3389 if (isSetMethodMap && !methodMap.containsKey(imiId)) { 3390 Slog.w(TAG, "IME uninstalled or not valid.: " + imiId); 3391 continue; 3392 } 3393 out.startTag(null, NODE_IMI); 3394 out.attribute(null, ATTR_ID, imiId); 3395 final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId); 3396 final int N = subtypesList.size(); 3397 for (int i = 0; i < N; ++i) { 3398 final InputMethodSubtype subtype = subtypesList.get(i); 3399 out.startTag(null, NODE_SUBTYPE); 3400 out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId())); 3401 out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId())); 3402 out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale()); 3403 out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode()); 3404 out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue()); 3405 out.attribute(null, ATTR_IS_AUXILIARY, 3406 String.valueOf(subtype.isAuxiliary() ? 1 : 0)); 3407 out.endTag(null, NODE_SUBTYPE); 3408 } 3409 out.endTag(null, NODE_IMI); 3410 } 3411 out.endTag(null, NODE_SUBTYPES); 3412 out.endDocument(); 3413 subtypesFile.finishWrite(fos); 3414 } catch (java.io.IOException e) { 3415 Slog.w(TAG, "Error writing subtypes", e); 3416 if (fos != null) { 3417 subtypesFile.failWrite(fos); 3418 } 3419 } 3420 } 3421 3422 private static void readAdditionalInputMethodSubtypes( 3423 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) { 3424 if (allSubtypes == null || subtypesFile == null) return; 3425 allSubtypes.clear(); 3426 FileInputStream fis = null; 3427 try { 3428 fis = subtypesFile.openRead(); 3429 final XmlPullParser parser = Xml.newPullParser(); 3430 parser.setInput(fis, null); 3431 int type = parser.getEventType(); 3432 // Skip parsing until START_TAG 3433 while ((type = parser.next()) != XmlPullParser.START_TAG 3434 && type != XmlPullParser.END_DOCUMENT) {} 3435 String firstNodeName = parser.getName(); 3436 if (!NODE_SUBTYPES.equals(firstNodeName)) { 3437 throw new XmlPullParserException("Xml doesn't start with subtypes"); 3438 } 3439 final int depth =parser.getDepth(); 3440 String currentImiId = null; 3441 ArrayList<InputMethodSubtype> tempSubtypesArray = null; 3442 while (((type = parser.next()) != XmlPullParser.END_TAG 3443 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 3444 if (type != XmlPullParser.START_TAG) 3445 continue; 3446 final String nodeName = parser.getName(); 3447 if (NODE_IMI.equals(nodeName)) { 3448 currentImiId = parser.getAttributeValue(null, ATTR_ID); 3449 if (TextUtils.isEmpty(currentImiId)) { 3450 Slog.w(TAG, "Invalid imi id found in subtypes.xml"); 3451 continue; 3452 } 3453 tempSubtypesArray = new ArrayList<InputMethodSubtype>(); 3454 allSubtypes.put(currentImiId, tempSubtypesArray); 3455 } else if (NODE_SUBTYPE.equals(nodeName)) { 3456 if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) { 3457 Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId); 3458 continue; 3459 } 3460 final int icon = Integer.valueOf( 3461 parser.getAttributeValue(null, ATTR_ICON)); 3462 final int label = Integer.valueOf( 3463 parser.getAttributeValue(null, ATTR_LABEL)); 3464 final String imeSubtypeLocale = 3465 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE); 3466 final String imeSubtypeMode = 3467 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE); 3468 final String imeSubtypeExtraValue = 3469 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE); 3470 final boolean isAuxiliary = "1".equals(String.valueOf( 3471 parser.getAttributeValue(null, ATTR_IS_AUXILIARY))); 3472 final InputMethodSubtype subtype = 3473 new InputMethodSubtype(label, icon, imeSubtypeLocale, 3474 imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary); 3475 tempSubtypesArray.add(subtype); 3476 } 3477 } 3478 } catch (XmlPullParserException e) { 3479 Slog.w(TAG, "Error reading subtypes: " + e); 3480 return; 3481 } catch (java.io.IOException e) { 3482 Slog.w(TAG, "Error reading subtypes: " + e); 3483 return; 3484 } catch (NumberFormatException e) { 3485 Slog.w(TAG, "Error reading subtypes: " + e); 3486 return; 3487 } finally { 3488 if (fis != null) { 3489 try { 3490 fis.close(); 3491 } catch (java.io.IOException e1) { 3492 Slog.w(TAG, "Failed to close."); 3493 } 3494 } 3495 } 3496 } 3497 } 3498 3499 @Override 3500 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 3501 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 3502 != PackageManager.PERMISSION_GRANTED) { 3503 3504 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 3505 + Binder.getCallingPid() 3506 + ", uid=" + Binder.getCallingUid()); 3507 return; 3508 } 3509 3510 IInputMethod method; 3511 ClientState client; 3512 3513 final Printer p = new PrintWriterPrinter(pw); 3514 3515 synchronized (mMethodMap) { 3516 p.println("Current Input Method Manager state:"); 3517 int N = mMethodList.size(); 3518 p.println(" Input Methods:"); 3519 for (int i=0; i<N; i++) { 3520 InputMethodInfo info = mMethodList.get(i); 3521 p.println(" InputMethod #" + i + ":"); 3522 info.dump(p, " "); 3523 } 3524 p.println(" Clients:"); 3525 for (ClientState ci : mClients.values()) { 3526 p.println(" Client " + ci + ":"); 3527 p.println(" client=" + ci.client); 3528 p.println(" inputContext=" + ci.inputContext); 3529 p.println(" sessionRequested=" + ci.sessionRequested); 3530 p.println(" curSession=" + ci.curSession); 3531 } 3532 p.println(" mCurMethodId=" + mCurMethodId); 3533 client = mCurClient; 3534 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 3535 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 3536 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 3537 + " mBoundToMethod=" + mBoundToMethod); 3538 p.println(" mCurToken=" + mCurToken); 3539 p.println(" mCurIntent=" + mCurIntent); 3540 method = mCurMethod; 3541 p.println(" mCurMethod=" + mCurMethod); 3542 p.println(" mEnabledSession=" + mEnabledSession); 3543 p.println(" mShowRequested=" + mShowRequested 3544 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 3545 + " mShowForced=" + mShowForced 3546 + " mInputShown=" + mInputShown); 3547 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 3548 } 3549 3550 p.println(" "); 3551 if (client != null) { 3552 pw.flush(); 3553 try { 3554 client.client.asBinder().dump(fd, args); 3555 } catch (RemoteException e) { 3556 p.println("Input method client dead: " + e); 3557 } 3558 } else { 3559 p.println("No input method client."); 3560 } 3561 3562 p.println(" "); 3563 if (method != null) { 3564 pw.flush(); 3565 try { 3566 method.asBinder().dump(fd, args); 3567 } catch (RemoteException e) { 3568 p.println("Input method service dead: " + e); 3569 } 3570 } else { 3571 p.println("No input method service."); 3572 } 3573 } 3574 } 3575