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