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