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