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