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