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