InputMethodManagerService.java revision 0aec3ea6defdee1ee4a9c0d6c4a3c13df3e7b812
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 synchronized (mMethodMap) { 1136 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 1137 final int N = imis.size(); 1138 if (N > 2) return true; 1139 if (N < 1) return false; 1140 int nonAuxCount = 0; 1141 int auxCount = 0; 1142 InputMethodSubtype nonAuxSubtype = null; 1143 InputMethodSubtype auxSubtype = null; 1144 for(int i = 0; i < N; ++i) { 1145 final InputMethodInfo imi = imis.get(i); 1146 final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked( 1147 imi, true); 1148 final int subtypeCount = subtypes.size(); 1149 if (subtypeCount == 0) { 1150 ++nonAuxCount; 1151 } else { 1152 for (int j = 0; j < subtypeCount; ++j) { 1153 final InputMethodSubtype subtype = subtypes.get(j); 1154 if (!subtype.isAuxiliary()) { 1155 ++nonAuxCount; 1156 nonAuxSubtype = subtype; 1157 } else { 1158 ++auxCount; 1159 auxSubtype = subtype; 1160 } 1161 } 1162 } 1163 } 1164 if (nonAuxCount > 1 || auxCount > 1) { 1165 return true; 1166 } else if (nonAuxCount == 1 && auxCount == 1) { 1167 if (nonAuxSubtype != null && auxSubtype != null 1168 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale()) 1169 || auxSubtype.overridesImplicitlyEnabledSubtype() 1170 || nonAuxSubtype.overridesImplicitlyEnabledSubtype()) 1171 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) { 1172 return false; 1173 } 1174 return true; 1175 } 1176 return false; 1177 } 1178 } 1179 1180 @SuppressWarnings("deprecation") 1181 @Override 1182 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1183 int uid = Binder.getCallingUid(); 1184 long ident = Binder.clearCallingIdentity(); 1185 try { 1186 if (token == null || mCurToken != token) { 1187 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); 1188 return; 1189 } 1190 1191 synchronized (mMethodMap) { 1192 mImeWindowVis = vis; 1193 mBackDisposition = backDisposition; 1194 if (mStatusBar != null) { 1195 mStatusBar.setImeWindowStatus(token, vis, backDisposition); 1196 } 1197 final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0; 1198 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 1199 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) { 1200 final PackageManager pm = mContext.getPackageManager(); 1201 final CharSequence title = mRes.getText( 1202 com.android.internal.R.string.select_input_method); 1203 final CharSequence imiLabel = imi.loadLabel(pm); 1204 final CharSequence summary = mCurrentSubtype != null 1205 ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext, 1206 imi.getPackageName(), imi.getServiceInfo().applicationInfo), 1207 (TextUtils.isEmpty(imiLabel) ? 1208 "" : " - " + imiLabel)) 1209 : imiLabel; 1210 1211 mImeSwitcherNotification.setLatestEventInfo( 1212 mContext, title, summary, mImeSwitchPendingIntent); 1213 if (mNotificationManager != null) { 1214 mNotificationManager.notify( 1215 com.android.internal.R.string.select_input_method, 1216 mImeSwitcherNotification); 1217 mNotificationShown = true; 1218 } 1219 } else { 1220 if (mNotificationShown && mNotificationManager != null) { 1221 mNotificationManager.cancel( 1222 com.android.internal.R.string.select_input_method); 1223 mNotificationShown = false; 1224 } 1225 } 1226 } 1227 } finally { 1228 Binder.restoreCallingIdentity(ident); 1229 } 1230 } 1231 1232 @Override 1233 public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { 1234 synchronized (mMethodMap) { 1235 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 1236 for (int i = 0; i < spans.length; ++i) { 1237 SuggestionSpan ss = spans[i]; 1238 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) { 1239 mSecureSuggestionSpans.put(ss, currentImi); 1240 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss); 1241 } 1242 } 1243 } 1244 } 1245 1246 @Override 1247 public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { 1248 synchronized (mMethodMap) { 1249 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); 1250 // TODO: Do not send the intent if the process of the targetImi is already dead. 1251 if (targetImi != null) { 1252 final String[] suggestions = span.getSuggestions(); 1253 if (index < 0 || index >= suggestions.length) return false; 1254 final String className = span.getNotificationTargetClassName(); 1255 final Intent intent = new Intent(); 1256 // Ensures that only a class in the original IME package will receive the 1257 // notification. 1258 intent.setClassName(targetImi.getPackageName(), className); 1259 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); 1260 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); 1261 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); 1262 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); 1263 mContext.sendBroadcast(intent); 1264 return true; 1265 } 1266 } 1267 return false; 1268 } 1269 1270 void updateFromSettingsLocked() { 1271 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 1272 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 1273 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 1274 // enabled. 1275 String id = Settings.Secure.getString(mContext.getContentResolver(), 1276 Settings.Secure.DEFAULT_INPUT_METHOD); 1277 // There is no input method selected, try to choose new applicable input method. 1278 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 1279 id = Settings.Secure.getString(mContext.getContentResolver(), 1280 Settings.Secure.DEFAULT_INPUT_METHOD); 1281 } 1282 if (!TextUtils.isEmpty(id)) { 1283 try { 1284 setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); 1285 } catch (IllegalArgumentException e) { 1286 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 1287 mCurMethodId = null; 1288 unbindCurrentMethodLocked(true); 1289 } 1290 mShortcutInputMethodsAndSubtypes.clear(); 1291 } else { 1292 // There is no longer an input method set, so stop any current one. 1293 mCurMethodId = null; 1294 unbindCurrentMethodLocked(true); 1295 } 1296 } 1297 1298 /* package */ void setInputMethodLocked(String id, int subtypeId) { 1299 InputMethodInfo info = mMethodMap.get(id); 1300 if (info == null) { 1301 throw new IllegalArgumentException("Unknown id: " + id); 1302 } 1303 1304 if (id.equals(mCurMethodId)) { 1305 InputMethodSubtype subtype = null; 1306 if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) { 1307 subtype = info.getSubtypeAt(subtypeId); 1308 } 1309 if (subtype != mCurrentSubtype) { 1310 synchronized (mMethodMap) { 1311 if (subtype != null) { 1312 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 1313 } 1314 if (mCurMethod != null) { 1315 try { 1316 refreshImeWindowVisibilityLocked(); 1317 // If subtype is null, try to find the most applicable one from 1318 // getCurrentInputMethodSubtype. 1319 if (subtype == null) { 1320 subtype = getCurrentInputMethodSubtype(); 1321 } 1322 mCurMethod.changeInputMethodSubtype(subtype); 1323 } catch (RemoteException e) { 1324 return; 1325 } 1326 } 1327 } 1328 } 1329 return; 1330 } 1331 1332 final long ident = Binder.clearCallingIdentity(); 1333 try { 1334 // Set a subtype to this input method. 1335 // subtypeId the name of a subtype which will be set. 1336 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); 1337 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 1338 // because mCurMethodId is stored as a history in 1339 // setSelectedInputMethodAndSubtypeLocked(). 1340 mCurMethodId = id; 1341 1342 if (ActivityManagerNative.isSystemReady()) { 1343 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 1344 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1345 intent.putExtra("input_method_id", id); 1346 mContext.sendBroadcast(intent); 1347 } 1348 unbindCurrentClientLocked(); 1349 } finally { 1350 Binder.restoreCallingIdentity(ident); 1351 } 1352 } 1353 1354 @Override 1355 public boolean showSoftInput(IInputMethodClient client, int flags, 1356 ResultReceiver resultReceiver) { 1357 int uid = Binder.getCallingUid(); 1358 long ident = Binder.clearCallingIdentity(); 1359 try { 1360 synchronized (mMethodMap) { 1361 if (mCurClient == null || client == null 1362 || mCurClient.client.asBinder() != client.asBinder()) { 1363 try { 1364 // We need to check if this is the current client with 1365 // focus in the window manager, to allow this call to 1366 // be made before input is started in it. 1367 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1368 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 1369 return false; 1370 } 1371 } catch (RemoteException e) { 1372 return false; 1373 } 1374 } 1375 1376 if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); 1377 return showCurrentInputLocked(flags, resultReceiver); 1378 } 1379 } finally { 1380 Binder.restoreCallingIdentity(ident); 1381 } 1382 } 1383 1384 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1385 mShowRequested = true; 1386 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 1387 mShowExplicitlyRequested = true; 1388 } 1389 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 1390 mShowExplicitlyRequested = true; 1391 mShowForced = true; 1392 } 1393 1394 if (!mSystemReady) { 1395 return false; 1396 } 1397 1398 boolean res = false; 1399 if (mCurMethod != null) { 1400 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 1401 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, 1402 resultReceiver)); 1403 mInputShown = true; 1404 if (mHaveConnection && !mVisibleBound) { 1405 mContext.bindService(mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE); 1406 mVisibleBound = true; 1407 } 1408 res = true; 1409 } else if (mHaveConnection && SystemClock.uptimeMillis() 1410 >= (mLastBindTime+TIME_TO_RECONNECT)) { 1411 // The client has asked to have the input method shown, but 1412 // we have been sitting here too long with a connection to the 1413 // service and no interface received, so let's disconnect/connect 1414 // to try to prod things along. 1415 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 1416 SystemClock.uptimeMillis()-mLastBindTime,1); 1417 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); 1418 mContext.unbindService(this); 1419 mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE 1420 | Context.BIND_NOT_VISIBLE); 1421 } 1422 1423 return res; 1424 } 1425 1426 @Override 1427 public boolean hideSoftInput(IInputMethodClient client, int flags, 1428 ResultReceiver resultReceiver) { 1429 int uid = Binder.getCallingUid(); 1430 long ident = Binder.clearCallingIdentity(); 1431 try { 1432 synchronized (mMethodMap) { 1433 if (mCurClient == null || client == null 1434 || mCurClient.client.asBinder() != client.asBinder()) { 1435 try { 1436 // We need to check if this is the current client with 1437 // focus in the window manager, to allow this call to 1438 // be made before input is started in it. 1439 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1440 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " 1441 + uid + ": " + client); 1442 setImeWindowVisibilityStatusHiddenLocked(); 1443 return false; 1444 } 1445 } catch (RemoteException e) { 1446 setImeWindowVisibilityStatusHiddenLocked(); 1447 return false; 1448 } 1449 } 1450 1451 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 1452 return hideCurrentInputLocked(flags, resultReceiver); 1453 } 1454 } finally { 1455 Binder.restoreCallingIdentity(ident); 1456 } 1457 } 1458 1459 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1460 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 1461 && (mShowExplicitlyRequested || mShowForced)) { 1462 if (DEBUG) Slog.v(TAG, 1463 "Not hiding: explicit show not cancelled by non-explicit hide"); 1464 return false; 1465 } 1466 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 1467 if (DEBUG) Slog.v(TAG, 1468 "Not hiding: forced show not cancelled by not-always hide"); 1469 return false; 1470 } 1471 boolean res; 1472 if (mInputShown && mCurMethod != null) { 1473 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1474 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver)); 1475 res = true; 1476 } else { 1477 res = false; 1478 } 1479 if (mHaveConnection && mVisibleBound) { 1480 mContext.unbindService(mVisibleConnection); 1481 mVisibleBound = false; 1482 } 1483 mInputShown = false; 1484 mShowRequested = false; 1485 mShowExplicitlyRequested = false; 1486 mShowForced = false; 1487 return res; 1488 } 1489 1490 @Override 1491 public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken, 1492 int controlFlags, int softInputMode, int windowFlags, 1493 EditorInfo attribute, IInputContext inputContext) { 1494 InputBindResult res = null; 1495 long ident = Binder.clearCallingIdentity(); 1496 try { 1497 synchronized (mMethodMap) { 1498 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder() 1499 + " controlFlags=#" + Integer.toHexString(controlFlags) 1500 + " softInputMode=#" + Integer.toHexString(softInputMode) 1501 + " windowFlags=#" + Integer.toHexString(windowFlags)); 1502 1503 ClientState cs = mClients.get(client.asBinder()); 1504 if (cs == null) { 1505 throw new IllegalArgumentException("unknown client " 1506 + client.asBinder()); 1507 } 1508 1509 try { 1510 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 1511 // Check with the window manager to make sure this client actually 1512 // has a window with focus. If not, reject. This is thread safe 1513 // because if the focus changes some time before or after, the 1514 // next client receiving focus that has any interest in input will 1515 // be calling through here after that change happens. 1516 Slog.w(TAG, "Focus gain on non-focused client " + cs.client 1517 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 1518 return null; 1519 } 1520 } catch (RemoteException e) { 1521 } 1522 1523 if (mCurFocusedWindow == windowToken) { 1524 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client); 1525 if (attribute != null) { 1526 return startInputUncheckedLocked(cs, inputContext, attribute, 1527 controlFlags); 1528 } 1529 return null; 1530 } 1531 mCurFocusedWindow = windowToken; 1532 1533 // Should we auto-show the IME even if the caller has not 1534 // specified what should be done with it? 1535 // We only do this automatically if the window can resize 1536 // to accommodate the IME (so what the user sees will give 1537 // them good context without input information being obscured 1538 // by the IME) or if running on a large screen where there 1539 // is more room for the target window + IME. 1540 final boolean doAutoShow = 1541 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1542 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 1543 || mRes.getConfiguration().isLayoutSizeAtLeast( 1544 Configuration.SCREENLAYOUT_SIZE_LARGE); 1545 final boolean isTextEditor = 1546 (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0; 1547 1548 // We want to start input before showing the IME, but after closing 1549 // it. We want to do this after closing it to help the IME disappear 1550 // more quickly (not get stuck behind it initializing itself for the 1551 // new focused input, even if its window wants to hide the IME). 1552 boolean didStart = false; 1553 1554 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 1555 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 1556 if (!isTextEditor || !doAutoShow) { 1557 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { 1558 // There is no focus view, and this window will 1559 // be behind any soft input window, so hide the 1560 // soft input window if it is shown. 1561 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 1562 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); 1563 } 1564 } else if (isTextEditor && doAutoShow && (softInputMode & 1565 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1566 // There is a focus view, and we are navigating forward 1567 // into the window, so show the input window for the user. 1568 // We only do this automatically if the window can resize 1569 // to accommodate the IME (so what the user sees will give 1570 // them good context without input information being obscured 1571 // by the IME) or if running on a large screen where there 1572 // is more room for the target window + IME. 1573 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 1574 if (attribute != null) { 1575 res = startInputUncheckedLocked(cs, inputContext, attribute, 1576 controlFlags); 1577 didStart = true; 1578 } 1579 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1580 } 1581 break; 1582 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 1583 // Do nothing. 1584 break; 1585 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 1586 if ((softInputMode & 1587 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1588 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 1589 hideCurrentInputLocked(0, null); 1590 } 1591 break; 1592 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 1593 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 1594 hideCurrentInputLocked(0, null); 1595 break; 1596 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 1597 if ((softInputMode & 1598 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1599 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 1600 if (attribute != null) { 1601 res = startInputUncheckedLocked(cs, inputContext, attribute, 1602 controlFlags); 1603 didStart = true; 1604 } 1605 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1606 } 1607 break; 1608 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 1609 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 1610 if (attribute != null) { 1611 res = startInputUncheckedLocked(cs, inputContext, attribute, 1612 controlFlags); 1613 didStart = true; 1614 } 1615 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1616 break; 1617 } 1618 1619 if (!didStart && attribute != null) { 1620 res = startInputUncheckedLocked(cs, inputContext, attribute, 1621 controlFlags); 1622 } 1623 } 1624 } finally { 1625 Binder.restoreCallingIdentity(ident); 1626 } 1627 1628 return res; 1629 } 1630 1631 @Override 1632 public void showInputMethodPickerFromClient(IInputMethodClient client) { 1633 synchronized (mMethodMap) { 1634 if (mCurClient == null || client == null 1635 || mCurClient.client.asBinder() != client.asBinder()) { 1636 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 1637 + Binder.getCallingUid() + ": " + client); 1638 } 1639 1640 // Always call subtype picker, because subtype picker is a superset of input method 1641 // picker. 1642 mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); 1643 } 1644 } 1645 1646 @Override 1647 public void setInputMethod(IBinder token, String id) { 1648 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); 1649 } 1650 1651 @Override 1652 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { 1653 synchronized (mMethodMap) { 1654 if (subtype != null) { 1655 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode( 1656 mMethodMap.get(id), subtype.hashCode())); 1657 } else { 1658 setInputMethod(token, id); 1659 } 1660 } 1661 } 1662 1663 @Override 1664 public void showInputMethodAndSubtypeEnablerFromClient( 1665 IInputMethodClient client, String inputMethodId) { 1666 synchronized (mMethodMap) { 1667 if (mCurClient == null || client == null 1668 || mCurClient.client.asBinder() != client.asBinder()) { 1669 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); 1670 } 1671 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 1672 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); 1673 } 1674 } 1675 1676 @Override 1677 public boolean switchToLastInputMethod(IBinder token) { 1678 synchronized (mMethodMap) { 1679 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 1680 final InputMethodInfo lastImi; 1681 if (lastIme != null) { 1682 lastImi = mMethodMap.get(lastIme.first); 1683 } else { 1684 lastImi = null; 1685 } 1686 String targetLastImiId = null; 1687 int subtypeId = NOT_A_SUBTYPE_ID; 1688 if (lastIme != null && lastImi != null) { 1689 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId); 1690 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 1691 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID 1692 : mCurrentSubtype.hashCode(); 1693 // If the last IME is the same as the current IME and the last subtype is not 1694 // defined, there is no need to switch to the last IME. 1695 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { 1696 targetLastImiId = lastIme.first; 1697 subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 1698 } 1699 } 1700 1701 if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) { 1702 // This is a safety net. If the currentSubtype can't be added to the history 1703 // and the framework couldn't find the last ime, we will make the last ime be 1704 // the most applicable enabled keyboard subtype of the system imes. 1705 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 1706 if (enabled != null) { 1707 final int N = enabled.size(); 1708 final String locale = mCurrentSubtype == null 1709 ? mRes.getConfiguration().locale.toString() 1710 : mCurrentSubtype.getLocale(); 1711 for (int i = 0; i < N; ++i) { 1712 final InputMethodInfo imi = enabled.get(i); 1713 if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) { 1714 InputMethodSubtype keyboardSubtype = 1715 findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi), 1716 SUBTYPE_MODE_KEYBOARD, locale, true); 1717 if (keyboardSubtype != null) { 1718 targetLastImiId = imi.getId(); 1719 subtypeId = getSubtypeIdFromHashCode( 1720 imi, keyboardSubtype.hashCode()); 1721 if(keyboardSubtype.getLocale().equals(locale)) { 1722 break; 1723 } 1724 } 1725 } 1726 } 1727 } 1728 } 1729 1730 if (!TextUtils.isEmpty(targetLastImiId)) { 1731 if (DEBUG) { 1732 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second 1733 + ", from: " + mCurMethodId + ", " + subtypeId); 1734 } 1735 setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId); 1736 return true; 1737 } else { 1738 return false; 1739 } 1740 } 1741 } 1742 1743 @Override 1744 public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { 1745 synchronized (mMethodMap) { 1746 final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( 1747 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype); 1748 if (nextSubtype == null) { 1749 return false; 1750 } 1751 setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId); 1752 return true; 1753 } 1754 } 1755 1756 @Override 1757 public InputMethodSubtype getLastInputMethodSubtype() { 1758 synchronized (mMethodMap) { 1759 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 1760 // TODO: Handle the case of the last IME with no subtypes 1761 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 1762 || TextUtils.isEmpty(lastIme.second)) return null; 1763 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 1764 if (lastImi == null) return null; 1765 try { 1766 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 1767 final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 1768 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 1769 return null; 1770 } 1771 return lastImi.getSubtypeAt(lastSubtypeId); 1772 } catch (NumberFormatException e) { 1773 return null; 1774 } 1775 } 1776 } 1777 1778 @Override 1779 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { 1780 // By this IPC call, only a process which shares the same uid with the IME can add 1781 // additional input method subtypes to the IME. 1782 if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return; 1783 synchronized (mMethodMap) { 1784 final InputMethodInfo imi = mMethodMap.get(imiId); 1785 if (imi == null) return; 1786 final PackageManager pm = mContext.getPackageManager(); 1787 final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid()); 1788 if (packageInfos != null) { 1789 final int packageNum = packageInfos.length; 1790 for (int i = 0; i < packageNum; ++i) { 1791 if (packageInfos[i].equals(imi.getPackageName())) { 1792 mFileManager.addInputMethodSubtypes(imi, subtypes); 1793 final long ident = Binder.clearCallingIdentity(); 1794 try { 1795 buildInputMethodListLocked(mMethodList, mMethodMap); 1796 } finally { 1797 Binder.restoreCallingIdentity(ident); 1798 } 1799 return; 1800 } 1801 } 1802 } 1803 } 1804 return; 1805 } 1806 1807 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { 1808 synchronized (mMethodMap) { 1809 if (token == null) { 1810 if (mContext.checkCallingOrSelfPermission( 1811 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1812 != PackageManager.PERMISSION_GRANTED) { 1813 throw new SecurityException( 1814 "Using null token requires permission " 1815 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1816 } 1817 } else if (mCurToken != token) { 1818 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 1819 + " token: " + token); 1820 return; 1821 } 1822 1823 final long ident = Binder.clearCallingIdentity(); 1824 try { 1825 setInputMethodLocked(id, subtypeId); 1826 } finally { 1827 Binder.restoreCallingIdentity(ident); 1828 } 1829 } 1830 } 1831 1832 @Override 1833 public void hideMySoftInput(IBinder token, int flags) { 1834 synchronized (mMethodMap) { 1835 if (token == null || mCurToken != token) { 1836 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " 1837 + Binder.getCallingUid() + " token: " + token); 1838 return; 1839 } 1840 long ident = Binder.clearCallingIdentity(); 1841 try { 1842 hideCurrentInputLocked(flags, null); 1843 } finally { 1844 Binder.restoreCallingIdentity(ident); 1845 } 1846 } 1847 } 1848 1849 @Override 1850 public void showMySoftInput(IBinder token, int flags) { 1851 synchronized (mMethodMap) { 1852 if (token == null || mCurToken != token) { 1853 Slog.w(TAG, "Ignoring showMySoftInput of uid " 1854 + Binder.getCallingUid() + " token: " + token); 1855 return; 1856 } 1857 long ident = Binder.clearCallingIdentity(); 1858 try { 1859 showCurrentInputLocked(flags, null); 1860 } finally { 1861 Binder.restoreCallingIdentity(ident); 1862 } 1863 } 1864 } 1865 1866 void setEnabledSessionInMainThread(SessionState session) { 1867 if (mEnabledSession != session) { 1868 if (mEnabledSession != null) { 1869 try { 1870 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 1871 mEnabledSession.method.setSessionEnabled( 1872 mEnabledSession.session, false); 1873 } catch (RemoteException e) { 1874 } 1875 } 1876 mEnabledSession = session; 1877 try { 1878 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 1879 session.method.setSessionEnabled( 1880 session.session, true); 1881 } catch (RemoteException e) { 1882 } 1883 } 1884 } 1885 1886 @Override 1887 public boolean handleMessage(Message msg) { 1888 HandlerCaller.SomeArgs args; 1889 switch (msg.what) { 1890 case MSG_SHOW_IM_PICKER: 1891 showInputMethodMenu(); 1892 return true; 1893 1894 case MSG_SHOW_IM_SUBTYPE_PICKER: 1895 showInputMethodSubtypeMenu(); 1896 return true; 1897 1898 case MSG_SHOW_IM_SUBTYPE_ENABLER: 1899 args = (HandlerCaller.SomeArgs)msg.obj; 1900 showInputMethodAndSubtypeEnabler((String)args.arg1); 1901 return true; 1902 1903 case MSG_SHOW_IM_CONFIG: 1904 showConfigureInputMethods(); 1905 return true; 1906 1907 // --------------------------------------------------------- 1908 1909 case MSG_UNBIND_INPUT: 1910 try { 1911 ((IInputMethod)msg.obj).unbindInput(); 1912 } catch (RemoteException e) { 1913 // There is nothing interesting about the method dying. 1914 } 1915 return true; 1916 case MSG_BIND_INPUT: 1917 args = (HandlerCaller.SomeArgs)msg.obj; 1918 try { 1919 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 1920 } catch (RemoteException e) { 1921 } 1922 return true; 1923 case MSG_SHOW_SOFT_INPUT: 1924 args = (HandlerCaller.SomeArgs)msg.obj; 1925 try { 1926 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, 1927 (ResultReceiver)args.arg2); 1928 } catch (RemoteException e) { 1929 } 1930 return true; 1931 case MSG_HIDE_SOFT_INPUT: 1932 args = (HandlerCaller.SomeArgs)msg.obj; 1933 try { 1934 ((IInputMethod)args.arg1).hideSoftInput(0, 1935 (ResultReceiver)args.arg2); 1936 } catch (RemoteException e) { 1937 } 1938 return true; 1939 case MSG_ATTACH_TOKEN: 1940 args = (HandlerCaller.SomeArgs)msg.obj; 1941 try { 1942 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); 1943 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); 1944 } catch (RemoteException e) { 1945 } 1946 return true; 1947 case MSG_CREATE_SESSION: 1948 args = (HandlerCaller.SomeArgs)msg.obj; 1949 try { 1950 ((IInputMethod)args.arg1).createSession( 1951 (IInputMethodCallback)args.arg2); 1952 } catch (RemoteException e) { 1953 } 1954 return true; 1955 // --------------------------------------------------------- 1956 1957 case MSG_START_INPUT: 1958 args = (HandlerCaller.SomeArgs)msg.obj; 1959 try { 1960 SessionState session = (SessionState)args.arg1; 1961 setEnabledSessionInMainThread(session); 1962 session.method.startInput((IInputContext)args.arg2, 1963 (EditorInfo)args.arg3); 1964 } catch (RemoteException e) { 1965 } 1966 return true; 1967 case MSG_RESTART_INPUT: 1968 args = (HandlerCaller.SomeArgs)msg.obj; 1969 try { 1970 SessionState session = (SessionState)args.arg1; 1971 setEnabledSessionInMainThread(session); 1972 session.method.restartInput((IInputContext)args.arg2, 1973 (EditorInfo)args.arg3); 1974 } catch (RemoteException e) { 1975 } 1976 return true; 1977 1978 // --------------------------------------------------------- 1979 1980 case MSG_UNBIND_METHOD: 1981 try { 1982 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1); 1983 } catch (RemoteException e) { 1984 // There is nothing interesting about the last client dying. 1985 } 1986 return true; 1987 case MSG_BIND_METHOD: 1988 args = (HandlerCaller.SomeArgs)msg.obj; 1989 try { 1990 ((IInputMethodClient)args.arg1).onBindMethod( 1991 (InputBindResult)args.arg2); 1992 } catch (RemoteException e) { 1993 Slog.w(TAG, "Client died receiving input method " + args.arg2); 1994 } 1995 return true; 1996 } 1997 return false; 1998 } 1999 2000 private boolean isSystemIme(InputMethodInfo inputMethod) { 2001 return (inputMethod.getServiceInfo().applicationInfo.flags 2002 & ApplicationInfo.FLAG_SYSTEM) != 0; 2003 } 2004 2005 private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) { 2006 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2007 final int subtypeCount = imi.getSubtypeCount(); 2008 for (int i = 0; i < subtypeCount; ++i) { 2009 subtypes.add(imi.getSubtypeAt(i)); 2010 } 2011 return subtypes; 2012 } 2013 2014 2015 private static ArrayList<InputMethodSubtype> getOverridingImplicitlyEnabledSubtypes( 2016 InputMethodInfo imi, String mode) { 2017 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2018 final int subtypeCount = imi.getSubtypeCount(); 2019 for (int i = 0; i < subtypeCount; ++i) { 2020 final InputMethodSubtype subtype = imi.getSubtypeAt(i); 2021 if (subtype.overridesImplicitlyEnabledSubtype() && subtype.getMode().equals(mode)) { 2022 subtypes.add(subtype); 2023 } 2024 } 2025 return subtypes; 2026 } 2027 2028 private InputMethodInfo getMostApplicableDefaultIMELocked() { 2029 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 2030 if (enabled != null && enabled.size() > 0) { 2031 // We'd prefer to fall back on a system IME, since that is safer. 2032 int i=enabled.size(); 2033 while (i > 0) { 2034 i--; 2035 final InputMethodInfo imi = enabled.get(i); 2036 if (isSystemIme(imi) && !imi.isAuxiliaryIme()) { 2037 break; 2038 } 2039 } 2040 return enabled.get(i); 2041 } 2042 return null; 2043 } 2044 2045 private boolean chooseNewDefaultIMELocked() { 2046 final InputMethodInfo imi = getMostApplicableDefaultIMELocked(); 2047 if (imi != null) { 2048 if (DEBUG) { 2049 Slog.d(TAG, "New default IME was selected: " + imi.getId()); 2050 } 2051 resetSelectedInputMethodAndSubtypeLocked(imi.getId()); 2052 return true; 2053 } 2054 2055 return false; 2056 } 2057 2058 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, 2059 HashMap<String, InputMethodInfo> map) { 2060 list.clear(); 2061 map.clear(); 2062 2063 PackageManager pm = mContext.getPackageManager(); 2064 final Configuration config = mRes.getConfiguration(); 2065 final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY; 2066 String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(), 2067 Secure.DISABLED_SYSTEM_INPUT_METHODS); 2068 if (disabledSysImes == null) disabledSysImes = ""; 2069 2070 List<ResolveInfo> services = pm.queryIntentServices( 2071 new Intent(InputMethod.SERVICE_INTERFACE), 2072 PackageManager.GET_META_DATA); 2073 2074 final HashMap<String, List<InputMethodSubtype>> additionalSubtypes = 2075 mFileManager.getAllAdditionalInputMethodSubtypes(); 2076 for (int i = 0; i < services.size(); ++i) { 2077 ResolveInfo ri = services.get(i); 2078 ServiceInfo si = ri.serviceInfo; 2079 ComponentName compName = new ComponentName(si.packageName, si.name); 2080 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals( 2081 si.permission)) { 2082 Slog.w(TAG, "Skipping input method " + compName 2083 + ": it does not require the permission " 2084 + android.Manifest.permission.BIND_INPUT_METHOD); 2085 continue; 2086 } 2087 2088 if (DEBUG) Slog.d(TAG, "Checking " + compName); 2089 2090 try { 2091 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes); 2092 list.add(p); 2093 final String id = p.getId(); 2094 map.put(id, p); 2095 2096 // System IMEs are enabled by default, unless there's a hard keyboard 2097 // and the system IME was explicitly disabled 2098 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) { 2099 setInputMethodEnabledLocked(id, true); 2100 } 2101 2102 if (DEBUG) { 2103 Slog.d(TAG, "Found a third-party input method " + p); 2104 } 2105 2106 } catch (XmlPullParserException e) { 2107 Slog.w(TAG, "Unable to load input method " + compName, e); 2108 } catch (IOException e) { 2109 Slog.w(TAG, "Unable to load input method " + compName, e); 2110 } 2111 } 2112 2113 String defaultIme = Settings.Secure.getString(mContext 2114 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2115 if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) { 2116 if (chooseNewDefaultIMELocked()) { 2117 updateFromSettingsLocked(); 2118 } 2119 } 2120 } 2121 2122 // ---------------------------------------------------------------------- 2123 2124 private void showInputMethodMenu() { 2125 showInputMethodMenuInternal(false); 2126 } 2127 2128 private void showInputMethodSubtypeMenu() { 2129 showInputMethodMenuInternal(true); 2130 } 2131 2132 private void showInputMethodAndSubtypeEnabler(String inputMethodId) { 2133 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 2134 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2135 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2136 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2137 if (!TextUtils.isEmpty(inputMethodId)) { 2138 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); 2139 } 2140 mContext.startActivity(intent); 2141 } 2142 2143 private void showConfigureInputMethods() { 2144 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); 2145 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2146 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2147 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2148 mContext.startActivity(intent); 2149 } 2150 2151 private void showInputMethodMenuInternal(boolean showSubtypes) { 2152 if (DEBUG) Slog.v(TAG, "Show switching menu"); 2153 2154 final Context context = mContext; 2155 final PackageManager pm = context.getPackageManager(); 2156 final boolean isScreenLocked = mKeyguardManager != null 2157 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); 2158 2159 String lastInputMethodId = Settings.Secure.getString(context 2160 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2161 int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 2162 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 2163 2164 synchronized (mMethodMap) { 2165 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 2166 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 2167 if (immis == null || immis.size() == 0) { 2168 return; 2169 } 2170 2171 hideInputMethodMenuLocked(); 2172 2173 final List<ImeSubtypeListItem> imList = 2174 mImListManager.getSortedInputMethodAndSubtypeList( 2175 showSubtypes, mInputShown, isScreenLocked); 2176 2177 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { 2178 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype(); 2179 if (currentSubtype != null) { 2180 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 2181 lastInputMethodSubtypeId = 2182 getSubtypeIdFromHashCode(currentImi, currentSubtype.hashCode()); 2183 } 2184 } 2185 2186 final int N = imList.size(); 2187 mIms = new InputMethodInfo[N]; 2188 mSubtypeIds = new int[N]; 2189 int checkedItem = 0; 2190 for (int i = 0; i < N; ++i) { 2191 final ImeSubtypeListItem item = imList.get(i); 2192 mIms[i] = item.mImi; 2193 mSubtypeIds[i] = item.mSubtypeId; 2194 if (mIms[i].getId().equals(lastInputMethodId)) { 2195 int subtypeId = mSubtypeIds[i]; 2196 if ((subtypeId == NOT_A_SUBTYPE_ID) 2197 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 2198 || (subtypeId == lastInputMethodSubtypeId)) { 2199 checkedItem = i; 2200 } 2201 } 2202 } 2203 2204 final TypedArray a = context.obtainStyledAttributes(null, 2205 com.android.internal.R.styleable.DialogPreference, 2206 com.android.internal.R.attr.alertDialogStyle, 0); 2207 mDialogBuilder = new AlertDialog.Builder(context) 2208 .setTitle(com.android.internal.R.string.select_input_method) 2209 .setOnCancelListener(new OnCancelListener() { 2210 @Override 2211 public void onCancel(DialogInterface dialog) { 2212 hideInputMethodMenu(); 2213 } 2214 }) 2215 .setIcon(a.getDrawable( 2216 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 2217 a.recycle(); 2218 2219 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context, 2220 com.android.internal.R.layout.simple_list_item_2_single_choice, imList, 2221 checkedItem); 2222 2223 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, 2224 new AlertDialog.OnClickListener() { 2225 @Override 2226 public void onClick(DialogInterface dialog, int which) { 2227 synchronized (mMethodMap) { 2228 if (mIms == null || mIms.length <= which 2229 || mSubtypeIds == null || mSubtypeIds.length <= which) { 2230 return; 2231 } 2232 InputMethodInfo im = mIms[which]; 2233 int subtypeId = mSubtypeIds[which]; 2234 hideInputMethodMenu(); 2235 if (im != null) { 2236 if ((subtypeId < 0) 2237 || (subtypeId >= im.getSubtypeCount())) { 2238 subtypeId = NOT_A_SUBTYPE_ID; 2239 } 2240 setInputMethodLocked(im.getId(), subtypeId); 2241 } 2242 } 2243 } 2244 }); 2245 2246 if (showSubtypes && !isScreenLocked) { 2247 mDialogBuilder.setPositiveButton( 2248 com.android.internal.R.string.configure_input_methods, 2249 new DialogInterface.OnClickListener() { 2250 @Override 2251 public void onClick(DialogInterface dialog, int whichButton) { 2252 showConfigureInputMethods(); 2253 } 2254 }); 2255 } 2256 mSwitchingDialog = mDialogBuilder.create(); 2257 mSwitchingDialog.setCanceledOnTouchOutside(true); 2258 mSwitchingDialog.getWindow().setType( 2259 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 2260 mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method"); 2261 mSwitchingDialog.show(); 2262 } 2263 } 2264 2265 private static class ImeSubtypeListItem { 2266 public final CharSequence mImeName; 2267 public final CharSequence mSubtypeName; 2268 public final InputMethodInfo mImi; 2269 public final int mSubtypeId; 2270 public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, 2271 InputMethodInfo imi, int subtypeId) { 2272 mImeName = imeName; 2273 mSubtypeName = subtypeName; 2274 mImi = imi; 2275 mSubtypeId = subtypeId; 2276 } 2277 } 2278 2279 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> { 2280 private final LayoutInflater mInflater; 2281 private final int mTextViewResourceId; 2282 private final List<ImeSubtypeListItem> mItemsList; 2283 private final int mCheckedItem; 2284 public ImeSubtypeListAdapter(Context context, int textViewResourceId, 2285 List<ImeSubtypeListItem> itemsList, int checkedItem) { 2286 super(context, textViewResourceId, itemsList); 2287 mTextViewResourceId = textViewResourceId; 2288 mItemsList = itemsList; 2289 mCheckedItem = checkedItem; 2290 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2291 } 2292 2293 @Override 2294 public View getView(int position, View convertView, ViewGroup parent) { 2295 final View view = convertView != null ? convertView 2296 : mInflater.inflate(mTextViewResourceId, null); 2297 if (position < 0 || position >= mItemsList.size()) return view; 2298 final ImeSubtypeListItem item = mItemsList.get(position); 2299 final CharSequence imeName = item.mImeName; 2300 final CharSequence subtypeName = item.mSubtypeName; 2301 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1); 2302 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2); 2303 if (TextUtils.isEmpty(subtypeName)) { 2304 firstTextView.setText(imeName); 2305 secondTextView.setVisibility(View.GONE); 2306 } else { 2307 firstTextView.setText(subtypeName); 2308 secondTextView.setText(imeName); 2309 secondTextView.setVisibility(View.VISIBLE); 2310 } 2311 final RadioButton radioButton = 2312 (RadioButton)view.findViewById(com.android.internal.R.id.radio); 2313 radioButton.setChecked(position == mCheckedItem); 2314 return view; 2315 } 2316 } 2317 2318 void hideInputMethodMenu() { 2319 synchronized (mMethodMap) { 2320 hideInputMethodMenuLocked(); 2321 } 2322 } 2323 2324 void hideInputMethodMenuLocked() { 2325 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 2326 2327 if (mSwitchingDialog != null) { 2328 mSwitchingDialog.dismiss(); 2329 mSwitchingDialog = null; 2330 } 2331 2332 mDialogBuilder = null; 2333 mIms = null; 2334 } 2335 2336 // ---------------------------------------------------------------------- 2337 2338 @Override 2339 public boolean setInputMethodEnabled(String id, boolean enabled) { 2340 synchronized (mMethodMap) { 2341 if (mContext.checkCallingOrSelfPermission( 2342 android.Manifest.permission.WRITE_SECURE_SETTINGS) 2343 != PackageManager.PERMISSION_GRANTED) { 2344 throw new SecurityException( 2345 "Requires permission " 2346 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 2347 } 2348 2349 long ident = Binder.clearCallingIdentity(); 2350 try { 2351 return setInputMethodEnabledLocked(id, enabled); 2352 } finally { 2353 Binder.restoreCallingIdentity(ident); 2354 } 2355 } 2356 } 2357 2358 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 2359 // Make sure this is a valid input method. 2360 InputMethodInfo imm = mMethodMap.get(id); 2361 if (imm == null) { 2362 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 2363 } 2364 2365 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 2366 .getEnabledInputMethodsAndSubtypeListLocked(); 2367 2368 if (enabled) { 2369 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 2370 if (pair.first.equals(id)) { 2371 // We are enabling this input method, but it is already enabled. 2372 // Nothing to do. The previous state was enabled. 2373 return true; 2374 } 2375 } 2376 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 2377 // Previous state was disabled. 2378 return false; 2379 } else { 2380 StringBuilder builder = new StringBuilder(); 2381 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2382 builder, enabledInputMethodsList, id)) { 2383 // Disabled input method is currently selected, switch to another one. 2384 String selId = Settings.Secure.getString(mContext.getContentResolver(), 2385 Settings.Secure.DEFAULT_INPUT_METHOD); 2386 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 2387 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 2388 resetSelectedInputMethodAndSubtypeLocked(""); 2389 } 2390 // Previous state was enabled. 2391 return true; 2392 } else { 2393 // We are disabling the input method but it is already disabled. 2394 // Nothing to do. The previous state was disabled. 2395 return false; 2396 } 2397 } 2398 } 2399 2400 private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 2401 if (subtype == null) return true; 2402 return !subtype.isAuxiliary(); 2403 } 2404 2405 private void saveCurrentInputMethodAndSubtypeToHistory() { 2406 String subtypeId = NOT_A_SUBTYPE_ID_STR; 2407 if (mCurrentSubtype != null) { 2408 subtypeId = String.valueOf(mCurrentSubtype.hashCode()); 2409 } 2410 if (canAddToLastInputMethod(mCurrentSubtype)) { 2411 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); 2412 } 2413 } 2414 2415 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 2416 boolean setSubtypeOnly) { 2417 // Update the history of InputMethod and Subtype 2418 saveCurrentInputMethodAndSubtypeToHistory(); 2419 2420 // Set Subtype here 2421 if (imi == null || subtypeId < 0) { 2422 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2423 mCurrentSubtype = null; 2424 } else { 2425 if (subtypeId < imi.getSubtypeCount()) { 2426 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); 2427 mSettings.putSelectedSubtype(subtype.hashCode()); 2428 mCurrentSubtype = subtype; 2429 } else { 2430 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2431 mCurrentSubtype = null; 2432 } 2433 } 2434 2435 if (!setSubtypeOnly) { 2436 // Set InputMethod here 2437 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 2438 } 2439 } 2440 2441 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 2442 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 2443 int lastSubtypeId = NOT_A_SUBTYPE_ID; 2444 // newDefaultIme is empty when there is no candidate for the selected IME. 2445 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 2446 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 2447 if (subtypeHashCode != null) { 2448 try { 2449 lastSubtypeId = getSubtypeIdFromHashCode( 2450 imi, Integer.valueOf(subtypeHashCode)); 2451 } catch (NumberFormatException e) { 2452 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 2453 } 2454 } 2455 } 2456 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 2457 } 2458 2459 private int getSelectedInputMethodSubtypeId(String id) { 2460 InputMethodInfo imi = mMethodMap.get(id); 2461 if (imi == null) { 2462 return NOT_A_SUBTYPE_ID; 2463 } 2464 int subtypeId; 2465 try { 2466 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), 2467 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); 2468 } catch (SettingNotFoundException e) { 2469 return NOT_A_SUBTYPE_ID; 2470 } 2471 return getSubtypeIdFromHashCode(imi, subtypeId); 2472 } 2473 2474 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 2475 if (imi != null) { 2476 final int subtypeCount = imi.getSubtypeCount(); 2477 for (int i = 0; i < subtypeCount; ++i) { 2478 InputMethodSubtype ims = imi.getSubtypeAt(i); 2479 if (subtypeHashCode == ims.hashCode()) { 2480 return i; 2481 } 2482 } 2483 } 2484 return NOT_A_SUBTYPE_ID; 2485 } 2486 2487 private static ArrayList<InputMethodSubtype> getImplicitlyApplicableSubtypesLocked( 2488 Resources res, InputMethodInfo imi) { 2489 final List<InputMethodSubtype> subtypes = getSubtypes(imi); 2490 final String systemLocale = res.getConfiguration().locale.toString(); 2491 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); 2492 final HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = 2493 new HashMap<String, InputMethodSubtype>(); 2494 final int N = subtypes.size(); 2495 for (int i = 0; i < N; ++i) { 2496 // scan overriding implicitly enabled subtypes. 2497 InputMethodSubtype subtype = subtypes.get(i); 2498 if (subtype.overridesImplicitlyEnabledSubtype()) { 2499 final String mode = subtype.getMode(); 2500 if (!applicableModeAndSubtypesMap.containsKey(mode)) { 2501 applicableModeAndSubtypesMap.put(mode, subtype); 2502 } 2503 } 2504 } 2505 if (applicableModeAndSubtypesMap.size() > 0) { 2506 return new ArrayList<InputMethodSubtype>(applicableModeAndSubtypesMap.values()); 2507 } 2508 for (int i = 0; i < N; ++i) { 2509 final InputMethodSubtype subtype = subtypes.get(i); 2510 final String locale = subtype.getLocale(); 2511 final String mode = subtype.getMode(); 2512 // When system locale starts with subtype's locale, that subtype will be applicable 2513 // for system locale 2514 // For instance, it's clearly applicable for cases like system locale = en_US and 2515 // subtype = en, but it is not necessarily considered applicable for cases like system 2516 // locale = en and subtype = en_US. 2517 // We just call systemLocale.startsWith(locale) in this function because there is no 2518 // need to find applicable subtypes aggressively unlike 2519 // findLastResortApplicableSubtypeLocked. 2520 if (systemLocale.startsWith(locale)) { 2521 final InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); 2522 // If more applicable subtypes are contained, skip. 2523 if (applicableSubtype != null) { 2524 if (systemLocale.equals(applicableSubtype.getLocale())) continue; 2525 if (!systemLocale.equals(locale)) continue; 2526 } 2527 applicableModeAndSubtypesMap.put(mode, subtype); 2528 } 2529 } 2530 final InputMethodSubtype keyboardSubtype 2531 = applicableModeAndSubtypesMap.get(SUBTYPE_MODE_KEYBOARD); 2532 final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( 2533 applicableModeAndSubtypesMap.values()); 2534 if (keyboardSubtype != null && !keyboardSubtype.containsExtraValueKey(TAG_ASCII_CAPABLE)) { 2535 for (int i = 0; i < N; ++i) { 2536 final InputMethodSubtype subtype = subtypes.get(i); 2537 final String mode = subtype.getMode(); 2538 if (SUBTYPE_MODE_KEYBOARD.equals(mode) && subtype.containsExtraValueKey( 2539 TAG_ENABLED_WHEN_DEFAULT_IS_NOT_ASCII_CAPABLE)) { 2540 applicableSubtypes.add(subtype); 2541 } 2542 } 2543 } 2544 if (keyboardSubtype == null) { 2545 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 2546 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 2547 if (lastResortKeyboardSubtype != null) { 2548 applicableSubtypes.add(lastResortKeyboardSubtype); 2549 } 2550 } 2551 return applicableSubtypes; 2552 } 2553 2554 /** 2555 * If there are no selected subtypes, tries finding the most applicable one according to the 2556 * given locale. 2557 * @param subtypes this function will search the most applicable subtype in subtypes 2558 * @param mode subtypes will be filtered by mode 2559 * @param locale subtypes will be filtered by locale 2560 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 2561 * it will return the first subtype matched with mode 2562 * @return the most applicable subtypeId 2563 */ 2564 private static InputMethodSubtype findLastResortApplicableSubtypeLocked( 2565 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, 2566 boolean canIgnoreLocaleAsLastResort) { 2567 if (subtypes == null || subtypes.size() == 0) { 2568 return null; 2569 } 2570 if (TextUtils.isEmpty(locale)) { 2571 locale = res.getConfiguration().locale.toString(); 2572 } 2573 final String language = locale.substring(0, 2); 2574 boolean partialMatchFound = false; 2575 InputMethodSubtype applicableSubtype = null; 2576 InputMethodSubtype firstMatchedModeSubtype = null; 2577 final int N = subtypes.size(); 2578 for (int i = 0; i < N; ++i) { 2579 InputMethodSubtype subtype = subtypes.get(i); 2580 final String subtypeLocale = subtype.getLocale(); 2581 // An applicable subtype should match "mode". If mode is null, mode will be ignored, 2582 // and all subtypes with all modes can be candidates. 2583 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 2584 if (firstMatchedModeSubtype == null) { 2585 firstMatchedModeSubtype = subtype; 2586 } 2587 if (locale.equals(subtypeLocale)) { 2588 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 2589 applicableSubtype = subtype; 2590 break; 2591 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { 2592 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 2593 applicableSubtype = subtype; 2594 partialMatchFound = true; 2595 } 2596 } 2597 } 2598 2599 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 2600 return firstMatchedModeSubtype; 2601 } 2602 2603 // The first subtype applicable to the system locale will be defined as the most applicable 2604 // subtype. 2605 if (DEBUG) { 2606 if (applicableSubtype != null) { 2607 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 2608 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 2609 } 2610 } 2611 return applicableSubtype; 2612 } 2613 2614 // If there are no selected shortcuts, tries finding the most applicable ones. 2615 private Pair<InputMethodInfo, InputMethodSubtype> 2616 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2617 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2618 InputMethodInfo mostApplicableIMI = null; 2619 InputMethodSubtype mostApplicableSubtype = null; 2620 boolean foundInSystemIME = false; 2621 2622 // Search applicable subtype for each InputMethodInfo 2623 for (InputMethodInfo imi: imis) { 2624 final String imiId = imi.getId(); 2625 if (foundInSystemIME && !imiId.equals(mCurMethodId)) { 2626 continue; 2627 } 2628 InputMethodSubtype subtype = null; 2629 final List<InputMethodSubtype> enabledSubtypes = 2630 getEnabledInputMethodSubtypeList(imi, true); 2631 // 1. Search by the current subtype's locale from enabledSubtypes. 2632 if (mCurrentSubtype != null) { 2633 subtype = findLastResortApplicableSubtypeLocked( 2634 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); 2635 } 2636 // 2. Search by the system locale from enabledSubtypes. 2637 // 3. Search the first enabled subtype matched with mode from enabledSubtypes. 2638 if (subtype == null) { 2639 subtype = findLastResortApplicableSubtypeLocked( 2640 mRes, enabledSubtypes, mode, null, true); 2641 } 2642 final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes = 2643 getOverridingImplicitlyEnabledSubtypes(imi, mode); 2644 final ArrayList<InputMethodSubtype> subtypesForSearch = 2645 overridingImplicitlyEnabledSubtypes.isEmpty() 2646 ? getSubtypes(imi) : overridingImplicitlyEnabledSubtypes; 2647 // 4. Search by the current subtype's locale from all subtypes. 2648 if (subtype == null && mCurrentSubtype != null) { 2649 subtype = findLastResortApplicableSubtypeLocked( 2650 mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false); 2651 } 2652 // 5. Search by the system locale from all subtypes. 2653 // 6. Search the first enabled subtype matched with mode from all subtypes. 2654 if (subtype == null) { 2655 subtype = findLastResortApplicableSubtypeLocked( 2656 mRes, subtypesForSearch, mode, null, true); 2657 } 2658 if (subtype != null) { 2659 if (imiId.equals(mCurMethodId)) { 2660 // The current input method is the most applicable IME. 2661 mostApplicableIMI = imi; 2662 mostApplicableSubtype = subtype; 2663 break; 2664 } else if (!foundInSystemIME) { 2665 // The system input method is 2nd applicable IME. 2666 mostApplicableIMI = imi; 2667 mostApplicableSubtype = subtype; 2668 if ((imi.getServiceInfo().applicationInfo.flags 2669 & ApplicationInfo.FLAG_SYSTEM) != 0) { 2670 foundInSystemIME = true; 2671 } 2672 } 2673 } 2674 } 2675 if (DEBUG) { 2676 if (mostApplicableIMI != null) { 2677 Slog.w(TAG, "Most applicable shortcut input method was:" 2678 + mostApplicableIMI.getId()); 2679 if (mostApplicableSubtype != null) { 2680 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 2681 + "," + mostApplicableSubtype.getMode() + "," 2682 + mostApplicableSubtype.getLocale()); 2683 } 2684 } 2685 } 2686 if (mostApplicableIMI != null) { 2687 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 2688 mostApplicableSubtype); 2689 } else { 2690 return null; 2691 } 2692 } 2693 2694 /** 2695 * @return Return the current subtype of this input method. 2696 */ 2697 @Override 2698 public InputMethodSubtype getCurrentInputMethodSubtype() { 2699 boolean subtypeIsSelected = false; 2700 try { 2701 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), 2702 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; 2703 } catch (SettingNotFoundException e) { 2704 } 2705 synchronized (mMethodMap) { 2706 if (!subtypeIsSelected || mCurrentSubtype == null) { 2707 String lastInputMethodId = Settings.Secure.getString( 2708 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2709 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 2710 if (subtypeId == NOT_A_SUBTYPE_ID) { 2711 InputMethodInfo imi = mMethodMap.get(lastInputMethodId); 2712 if (imi != null) { 2713 // If there are no selected subtypes, the framework will try to find 2714 // the most applicable subtype from explicitly or implicitly enabled 2715 // subtypes. 2716 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 2717 getEnabledInputMethodSubtypeList(imi, true); 2718 // If there is only one explicitly or implicitly enabled subtype, 2719 // just returns it. 2720 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 2721 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); 2722 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { 2723 mCurrentSubtype = findLastResortApplicableSubtypeLocked( 2724 mRes, explicitlyOrImplicitlyEnabledSubtypes, 2725 SUBTYPE_MODE_KEYBOARD, null, true); 2726 if (mCurrentSubtype == null) { 2727 mCurrentSubtype = findLastResortApplicableSubtypeLocked( 2728 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, 2729 true); 2730 } 2731 } 2732 } 2733 } else { 2734 mCurrentSubtype = 2735 getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId); 2736 } 2737 } 2738 return mCurrentSubtype; 2739 } 2740 } 2741 2742 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 2743 InputMethodSubtype subtype) { 2744 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 2745 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 2746 } else { 2747 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2748 subtypes.add(subtype); 2749 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 2750 } 2751 } 2752 2753 // TODO: We should change the return type from List to List<Parcelable> 2754 @SuppressWarnings("rawtypes") 2755 @Override 2756 public List getShortcutInputMethodsAndSubtypes() { 2757 synchronized (mMethodMap) { 2758 ArrayList<Object> ret = new ArrayList<Object>(); 2759 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 2760 // If there are no selected shortcut subtypes, the framework will try to find 2761 // the most applicable subtype from all subtypes whose mode is 2762 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 2763 Pair<InputMethodInfo, InputMethodSubtype> info = 2764 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 2765 SUBTYPE_MODE_VOICE); 2766 if (info != null) { 2767 ret.add(info.first); 2768 ret.add(info.second); 2769 } 2770 return ret; 2771 } 2772 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 2773 ret.add(imi); 2774 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 2775 ret.add(subtype); 2776 } 2777 } 2778 return ret; 2779 } 2780 } 2781 2782 @Override 2783 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 2784 synchronized (mMethodMap) { 2785 if (subtype != null && mCurMethodId != null) { 2786 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2787 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); 2788 if (subtypeId != NOT_A_SUBTYPE_ID) { 2789 setInputMethodLocked(mCurMethodId, subtypeId); 2790 return true; 2791 } 2792 } 2793 return false; 2794 } 2795 } 2796 2797 private static class InputMethodAndSubtypeListManager { 2798 private final Context mContext; 2799 private final PackageManager mPm; 2800 private final InputMethodManagerService mImms; 2801 public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) { 2802 mContext = context; 2803 mPm = context.getPackageManager(); 2804 mImms = imms; 2805 } 2806 2807 private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = 2808 new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( 2809 new Comparator<InputMethodInfo>() { 2810 @Override 2811 public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { 2812 if (imi2 == null) return 0; 2813 if (imi1 == null) return 1; 2814 if (mPm == null) { 2815 return imi1.getId().compareTo(imi2.getId()); 2816 } 2817 CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); 2818 CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); 2819 return imiId1.toString().compareTo(imiId2.toString()); 2820 } 2821 }); 2822 2823 public ImeSubtypeListItem getNextInputMethod( 2824 boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { 2825 if (imi == null) { 2826 return null; 2827 } 2828 final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList(); 2829 if (imList.size() <= 1) { 2830 return null; 2831 } 2832 final int N = imList.size(); 2833 final int currentSubtypeId = subtype != null 2834 ? mImms.getSubtypeIdFromHashCode(imi, subtype.hashCode()) 2835 : NOT_A_SUBTYPE_ID; 2836 for (int i = 0; i < N; ++i) { 2837 final ImeSubtypeListItem isli = imList.get(i); 2838 if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) { 2839 if (!onlyCurrentIme) { 2840 return imList.get((i + 1) % N); 2841 } 2842 for (int j = 0; j < N - 1; ++j) { 2843 final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); 2844 if (candidate.mImi.equals(imi)) { 2845 return candidate; 2846 } 2847 } 2848 return null; 2849 } 2850 } 2851 return null; 2852 } 2853 2854 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { 2855 return getSortedInputMethodAndSubtypeList(true, false, false); 2856 } 2857 2858 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes, 2859 boolean inputShown, boolean isScreenLocked) { 2860 final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); 2861 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 2862 mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 2863 if (immis == null || immis.size() == 0) { 2864 return Collections.emptyList(); 2865 } 2866 mSortedImmis.clear(); 2867 mSortedImmis.putAll(immis); 2868 for (InputMethodInfo imi : mSortedImmis.keySet()) { 2869 if (imi == null) continue; 2870 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); 2871 HashSet<String> enabledSubtypeSet = new HashSet<String>(); 2872 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { 2873 enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); 2874 } 2875 ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); 2876 final CharSequence imeLabel = imi.loadLabel(mPm); 2877 if (showSubtypes && enabledSubtypeSet.size() > 0) { 2878 final int subtypeCount = imi.getSubtypeCount(); 2879 if (DEBUG) { 2880 Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); 2881 } 2882 for (int j = 0; j < subtypeCount; ++j) { 2883 final InputMethodSubtype subtype = imi.getSubtypeAt(j); 2884 final String subtypeHashCode = String.valueOf(subtype.hashCode()); 2885 // We show all enabled IMEs and subtypes when an IME is shown. 2886 if (enabledSubtypeSet.contains(subtypeHashCode) 2887 && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { 2888 final CharSequence subtypeLabel = 2889 subtype.overridesImplicitlyEnabledSubtype() ? null 2890 : subtype.getDisplayName(mContext, imi.getPackageName(), 2891 imi.getServiceInfo().applicationInfo); 2892 imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j)); 2893 2894 // Removing this subtype from enabledSubtypeSet because we no longer 2895 // need to add an entry of this subtype to imList to avoid duplicated 2896 // entries. 2897 enabledSubtypeSet.remove(subtypeHashCode); 2898 } 2899 } 2900 } else { 2901 imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID)); 2902 } 2903 } 2904 return imList; 2905 } 2906 } 2907 2908 /** 2909 * Utility class for putting and getting settings for InputMethod 2910 * TODO: Move all putters and getters of settings to this class. 2911 */ 2912 private static class InputMethodSettings { 2913 // The string for enabled input method is saved as follows: 2914 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 2915 private static final char INPUT_METHOD_SEPARATER = ':'; 2916 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 2917 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 2918 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 2919 2920 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 2921 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 2922 2923 private final Resources mRes; 2924 private final ContentResolver mResolver; 2925 private final HashMap<String, InputMethodInfo> mMethodMap; 2926 private final ArrayList<InputMethodInfo> mMethodList; 2927 2928 private String mEnabledInputMethodsStrCache; 2929 2930 private static void buildEnabledInputMethodsSettingString( 2931 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 2932 String id = pair.first; 2933 ArrayList<String> subtypes = pair.second; 2934 builder.append(id); 2935 // Inputmethod and subtypes are saved in the settings as follows: 2936 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 2937 for (String subtypeId: subtypes) { 2938 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 2939 } 2940 } 2941 2942 public InputMethodSettings( 2943 Resources res, ContentResolver resolver, 2944 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { 2945 mRes = res; 2946 mResolver = resolver; 2947 mMethodMap = methodMap; 2948 mMethodList = methodList; 2949 } 2950 2951 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 2952 return createEnabledInputMethodListLocked( 2953 getEnabledInputMethodsAndSubtypeListLocked()); 2954 } 2955 2956 public List<Pair<InputMethodInfo, ArrayList<String>>> 2957 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 2958 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 2959 getEnabledInputMethodsAndSubtypeListLocked()); 2960 } 2961 2962 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 2963 InputMethodInfo imi) { 2964 List<Pair<String, ArrayList<String>>> imsList = 2965 getEnabledInputMethodsAndSubtypeListLocked(); 2966 ArrayList<InputMethodSubtype> enabledSubtypes = 2967 new ArrayList<InputMethodSubtype>(); 2968 if (imi != null) { 2969 for (Pair<String, ArrayList<String>> imsPair : imsList) { 2970 InputMethodInfo info = mMethodMap.get(imsPair.first); 2971 if (info != null && info.getId().equals(imi.getId())) { 2972 final int subtypeCount = info.getSubtypeCount(); 2973 for (int i = 0; i < subtypeCount; ++i) { 2974 InputMethodSubtype ims = info.getSubtypeAt(i); 2975 for (String s: imsPair.second) { 2976 if (String.valueOf(ims.hashCode()).equals(s)) { 2977 enabledSubtypes.add(ims); 2978 } 2979 } 2980 } 2981 break; 2982 } 2983 } 2984 } 2985 return enabledSubtypes; 2986 } 2987 2988 // At the initial boot, the settings for input methods are not set, 2989 // so we need to enable IME in that case. 2990 public void enableAllIMEsIfThereIsNoEnabledIME() { 2991 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 2992 StringBuilder sb = new StringBuilder(); 2993 final int N = mMethodList.size(); 2994 for (int i = 0; i < N; i++) { 2995 InputMethodInfo imi = mMethodList.get(i); 2996 Slog.i(TAG, "Adding: " + imi.getId()); 2997 if (i > 0) sb.append(':'); 2998 sb.append(imi.getId()); 2999 } 3000 putEnabledInputMethodsStr(sb.toString()); 3001 } 3002 } 3003 3004 private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 3005 ArrayList<Pair<String, ArrayList<String>>> imsList 3006 = new ArrayList<Pair<String, ArrayList<String>>>(); 3007 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 3008 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 3009 return imsList; 3010 } 3011 mInputMethodSplitter.setString(enabledInputMethodsStr); 3012 while (mInputMethodSplitter.hasNext()) { 3013 String nextImsStr = mInputMethodSplitter.next(); 3014 mSubtypeSplitter.setString(nextImsStr); 3015 if (mSubtypeSplitter.hasNext()) { 3016 ArrayList<String> subtypeHashes = new ArrayList<String>(); 3017 // The first element is ime id. 3018 String imeId = mSubtypeSplitter.next(); 3019 while (mSubtypeSplitter.hasNext()) { 3020 subtypeHashes.add(mSubtypeSplitter.next()); 3021 } 3022 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 3023 } 3024 } 3025 return imsList; 3026 } 3027 3028 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 3029 if (reloadInputMethodStr) { 3030 getEnabledInputMethodsStr(); 3031 } 3032 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 3033 // Add in the newly enabled input method. 3034 putEnabledInputMethodsStr(id); 3035 } else { 3036 putEnabledInputMethodsStr( 3037 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 3038 } 3039 } 3040 3041 /** 3042 * Build and put a string of EnabledInputMethods with removing specified Id. 3043 * @return the specified id was removed or not. 3044 */ 3045 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 3046 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 3047 boolean isRemoved = false; 3048 boolean needsAppendSeparator = false; 3049 for (Pair<String, ArrayList<String>> ims: imsList) { 3050 String curId = ims.first; 3051 if (curId.equals(id)) { 3052 // We are disabling this input method, and it is 3053 // currently enabled. Skip it to remove from the 3054 // new list. 3055 isRemoved = true; 3056 } else { 3057 if (needsAppendSeparator) { 3058 builder.append(INPUT_METHOD_SEPARATER); 3059 } else { 3060 needsAppendSeparator = true; 3061 } 3062 buildEnabledInputMethodsSettingString(builder, ims); 3063 } 3064 } 3065 if (isRemoved) { 3066 // Update the setting with the new list of input methods. 3067 putEnabledInputMethodsStr(builder.toString()); 3068 } 3069 return isRemoved; 3070 } 3071 3072 private List<InputMethodInfo> createEnabledInputMethodListLocked( 3073 List<Pair<String, ArrayList<String>>> imsList) { 3074 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 3075 for (Pair<String, ArrayList<String>> ims: imsList) { 3076 InputMethodInfo info = mMethodMap.get(ims.first); 3077 if (info != null) { 3078 res.add(info); 3079 } 3080 } 3081 return res; 3082 } 3083 3084 private List<Pair<InputMethodInfo, ArrayList<String>>> 3085 createEnabledInputMethodAndSubtypeHashCodeListLocked( 3086 List<Pair<String, ArrayList<String>>> imsList) { 3087 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 3088 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 3089 for (Pair<String, ArrayList<String>> ims : imsList) { 3090 InputMethodInfo info = mMethodMap.get(ims.first); 3091 if (info != null) { 3092 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 3093 } 3094 } 3095 return res; 3096 } 3097 3098 private void putEnabledInputMethodsStr(String str) { 3099 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); 3100 mEnabledInputMethodsStrCache = str; 3101 } 3102 3103 private String getEnabledInputMethodsStr() { 3104 mEnabledInputMethodsStrCache = Settings.Secure.getString( 3105 mResolver, Settings.Secure.ENABLED_INPUT_METHODS); 3106 if (DEBUG) { 3107 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); 3108 } 3109 return mEnabledInputMethodsStrCache; 3110 } 3111 3112 private void saveSubtypeHistory( 3113 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 3114 StringBuilder builder = new StringBuilder(); 3115 boolean isImeAdded = false; 3116 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 3117 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 3118 newSubtypeId); 3119 isImeAdded = true; 3120 } 3121 for (Pair<String, String> ime: savedImes) { 3122 String imeId = ime.first; 3123 String subtypeId = ime.second; 3124 if (TextUtils.isEmpty(subtypeId)) { 3125 subtypeId = NOT_A_SUBTYPE_ID_STR; 3126 } 3127 if (isImeAdded) { 3128 builder.append(INPUT_METHOD_SEPARATER); 3129 } else { 3130 isImeAdded = true; 3131 } 3132 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 3133 subtypeId); 3134 } 3135 // Remove the last INPUT_METHOD_SEPARATER 3136 putSubtypeHistoryStr(builder.toString()); 3137 } 3138 3139 public void addSubtypeToHistory(String imeId, String subtypeId) { 3140 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 3141 for (Pair<String, String> ime: subtypeHistory) { 3142 if (ime.first.equals(imeId)) { 3143 if (DEBUG) { 3144 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 3145 + ime.second); 3146 } 3147 // We should break here 3148 subtypeHistory.remove(ime); 3149 break; 3150 } 3151 } 3152 if (DEBUG) { 3153 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 3154 } 3155 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 3156 } 3157 3158 private void putSubtypeHistoryStr(String str) { 3159 if (DEBUG) { 3160 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 3161 } 3162 Settings.Secure.putString( 3163 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 3164 } 3165 3166 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 3167 // Gets the first one from the history 3168 return getLastSubtypeForInputMethodLockedInternal(null); 3169 } 3170 3171 public String getLastSubtypeForInputMethodLocked(String imeId) { 3172 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 3173 if (ime != null) { 3174 return ime.second; 3175 } else { 3176 return null; 3177 } 3178 } 3179 3180 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 3181 List<Pair<String, ArrayList<String>>> enabledImes = 3182 getEnabledInputMethodsAndSubtypeListLocked(); 3183 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 3184 for (Pair<String, String> imeAndSubtype : subtypeHistory) { 3185 final String imeInTheHistory = imeAndSubtype.first; 3186 // If imeId is empty, returns the first IME and subtype in the history 3187 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 3188 final String subtypeInTheHistory = imeAndSubtype.second; 3189 final String subtypeHashCode = 3190 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 3191 enabledImes, imeInTheHistory, subtypeInTheHistory); 3192 if (!TextUtils.isEmpty(subtypeHashCode)) { 3193 if (DEBUG) { 3194 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 3195 } 3196 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 3197 } 3198 } 3199 } 3200 if (DEBUG) { 3201 Slog.d(TAG, "No enabled IME found in the history"); 3202 } 3203 return null; 3204 } 3205 3206 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 3207 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 3208 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 3209 if (enabledIme.first.equals(imeId)) { 3210 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 3211 if (explicitlyEnabledSubtypes.size() == 0) { 3212 // If there are no explicitly enabled subtypes, applicable subtypes are 3213 // enabled implicitly. 3214 InputMethodInfo imi = mMethodMap.get(imeId); 3215 // If IME is enabled and no subtypes are enabled, applicable subtypes 3216 // are enabled implicitly, so needs to treat them to be enabled. 3217 if (imi != null && imi.getSubtypeCount() > 0) { 3218 List<InputMethodSubtype> implicitlySelectedSubtypes = 3219 getImplicitlyApplicableSubtypesLocked(mRes, imi); 3220 if (implicitlySelectedSubtypes != null) { 3221 final int N = implicitlySelectedSubtypes.size(); 3222 for (int i = 0; i < N; ++i) { 3223 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); 3224 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 3225 return subtypeHashCode; 3226 } 3227 } 3228 } 3229 } 3230 } else { 3231 for (String s: explicitlyEnabledSubtypes) { 3232 if (s.equals(subtypeHashCode)) { 3233 // If both imeId and subtypeId are enabled, return subtypeId. 3234 return s; 3235 } 3236 } 3237 } 3238 // If imeId was enabled but subtypeId was disabled. 3239 return NOT_A_SUBTYPE_ID_STR; 3240 } 3241 } 3242 // If both imeId and subtypeId are disabled, return null 3243 return null; 3244 } 3245 3246 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 3247 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 3248 final String subtypeHistoryStr = getSubtypeHistoryStr(); 3249 if (TextUtils.isEmpty(subtypeHistoryStr)) { 3250 return imsList; 3251 } 3252 mInputMethodSplitter.setString(subtypeHistoryStr); 3253 while (mInputMethodSplitter.hasNext()) { 3254 String nextImsStr = mInputMethodSplitter.next(); 3255 mSubtypeSplitter.setString(nextImsStr); 3256 if (mSubtypeSplitter.hasNext()) { 3257 String subtypeId = NOT_A_SUBTYPE_ID_STR; 3258 // The first element is ime id. 3259 String imeId = mSubtypeSplitter.next(); 3260 while (mSubtypeSplitter.hasNext()) { 3261 subtypeId = mSubtypeSplitter.next(); 3262 break; 3263 } 3264 imsList.add(new Pair<String, String>(imeId, subtypeId)); 3265 } 3266 } 3267 return imsList; 3268 } 3269 3270 private String getSubtypeHistoryStr() { 3271 if (DEBUG) { 3272 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( 3273 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); 3274 } 3275 return Settings.Secure.getString( 3276 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); 3277 } 3278 3279 public void putSelectedInputMethod(String imeId) { 3280 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 3281 } 3282 3283 public void putSelectedSubtype(int subtypeId) { 3284 Settings.Secure.putInt( 3285 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 3286 } 3287 } 3288 3289 private static class InputMethodFileManager { 3290 private static final String SYSTEM_PATH = "system"; 3291 private static final String INPUT_METHOD_PATH = "inputmethod"; 3292 private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml"; 3293 private static final String NODE_SUBTYPES = "subtypes"; 3294 private static final String NODE_SUBTYPE = "subtype"; 3295 private static final String NODE_IMI = "imi"; 3296 private static final String ATTR_ID = "id"; 3297 private static final String ATTR_LABEL = "label"; 3298 private static final String ATTR_ICON = "icon"; 3299 private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale"; 3300 private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode"; 3301 private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue"; 3302 private static final String ATTR_IS_AUXILIARY = "isAuxiliary"; 3303 private final AtomicFile mAdditionalInputMethodSubtypeFile; 3304 private final HashMap<String, InputMethodInfo> mMethodMap; 3305 private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap = 3306 new HashMap<String, List<InputMethodSubtype>>(); 3307 public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) { 3308 if (methodMap == null) { 3309 throw new NullPointerException("methodMap is null"); 3310 } 3311 mMethodMap = methodMap; 3312 final File systemDir = new File(Environment.getDataDirectory(), SYSTEM_PATH); 3313 final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH); 3314 if (!inputMethodDir.mkdirs()) { 3315 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath()); 3316 } 3317 final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME); 3318 mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile); 3319 if (!subtypeFile.exists()) { 3320 // If "subtypes.xml" doesn't exist, create a blank file. 3321 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, 3322 methodMap); 3323 } else { 3324 readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile); 3325 } 3326 } 3327 3328 private void deleteAllInputMethodSubtypes(String imiId) { 3329 synchronized (mMethodMap) { 3330 mSubtypesMap.remove(imiId); 3331 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, 3332 mMethodMap); 3333 } 3334 } 3335 3336 public void addInputMethodSubtypes( 3337 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) { 3338 synchronized (mMethodMap) { 3339 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 3340 final int N = additionalSubtypes.length; 3341 for (int i = 0; i < N; ++i) { 3342 final InputMethodSubtype subtype = additionalSubtypes[i]; 3343 if (!subtypes.contains(subtype)) { 3344 subtypes.add(subtype); 3345 } 3346 } 3347 mSubtypesMap.put(imi.getId(), subtypes); 3348 writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile, 3349 mMethodMap); 3350 } 3351 } 3352 3353 public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() { 3354 synchronized (mMethodMap) { 3355 return mSubtypesMap; 3356 } 3357 } 3358 3359 private static void writeAdditionalInputMethodSubtypes( 3360 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile, 3361 HashMap<String, InputMethodInfo> methodMap) { 3362 // Safety net for the case that this function is called before methodMap is set. 3363 final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0; 3364 FileOutputStream fos = null; 3365 try { 3366 fos = subtypesFile.startWrite(); 3367 final XmlSerializer out = new FastXmlSerializer(); 3368 out.setOutput(fos, "utf-8"); 3369 out.startDocument(null, true); 3370 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 3371 out.startTag(null, NODE_SUBTYPES); 3372 for (String imiId : allSubtypes.keySet()) { 3373 if (isSetMethodMap && !methodMap.containsKey(imiId)) { 3374 Slog.w(TAG, "IME uninstalled or not valid.: " + imiId); 3375 continue; 3376 } 3377 out.startTag(null, NODE_IMI); 3378 out.attribute(null, ATTR_ID, imiId); 3379 final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId); 3380 final int N = subtypesList.size(); 3381 for (int i = 0; i < N; ++i) { 3382 final InputMethodSubtype subtype = subtypesList.get(i); 3383 out.startTag(null, NODE_SUBTYPE); 3384 out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId())); 3385 out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId())); 3386 out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale()); 3387 out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode()); 3388 out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue()); 3389 out.attribute(null, ATTR_IS_AUXILIARY, 3390 String.valueOf(subtype.isAuxiliary() ? 1 : 0)); 3391 out.endTag(null, NODE_SUBTYPE); 3392 } 3393 out.endTag(null, NODE_IMI); 3394 } 3395 out.endTag(null, NODE_SUBTYPES); 3396 out.endDocument(); 3397 subtypesFile.finishWrite(fos); 3398 } catch (java.io.IOException e) { 3399 Slog.w(TAG, "Error writing subtypes", e); 3400 if (fos != null) { 3401 subtypesFile.failWrite(fos); 3402 } 3403 } 3404 } 3405 3406 private static void readAdditionalInputMethodSubtypes( 3407 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) { 3408 if (allSubtypes == null || subtypesFile == null) return; 3409 allSubtypes.clear(); 3410 FileInputStream fis = null; 3411 try { 3412 fis = subtypesFile.openRead(); 3413 final XmlPullParser parser = Xml.newPullParser(); 3414 parser.setInput(fis, null); 3415 int type = parser.getEventType(); 3416 // Skip parsing until START_TAG 3417 while ((type = parser.next()) != XmlPullParser.START_TAG 3418 && type != XmlPullParser.END_DOCUMENT) {} 3419 String firstNodeName = parser.getName(); 3420 if (!NODE_SUBTYPES.equals(firstNodeName)) { 3421 throw new XmlPullParserException("Xml doesn't start with subtypes"); 3422 } 3423 final int depth =parser.getDepth(); 3424 String currentImiId = null; 3425 ArrayList<InputMethodSubtype> tempSubtypesArray = null; 3426 while (((type = parser.next()) != XmlPullParser.END_TAG 3427 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 3428 if (type != XmlPullParser.START_TAG) 3429 continue; 3430 final String nodeName = parser.getName(); 3431 if (NODE_IMI.equals(nodeName)) { 3432 currentImiId = parser.getAttributeValue(null, ATTR_ID); 3433 if (TextUtils.isEmpty(currentImiId)) { 3434 Slog.w(TAG, "Invalid imi id found in subtypes.xml"); 3435 continue; 3436 } 3437 tempSubtypesArray = new ArrayList<InputMethodSubtype>(); 3438 allSubtypes.put(currentImiId, tempSubtypesArray); 3439 } else if (NODE_SUBTYPE.equals(nodeName)) { 3440 if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) { 3441 Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId); 3442 continue; 3443 } 3444 final int icon = Integer.valueOf( 3445 parser.getAttributeValue(null, ATTR_ICON)); 3446 final int label = Integer.valueOf( 3447 parser.getAttributeValue(null, ATTR_LABEL)); 3448 final String imeSubtypeLocale = 3449 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE); 3450 final String imeSubtypeMode = 3451 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE); 3452 final String imeSubtypeExtraValue = 3453 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE); 3454 final boolean isAuxiliary = "1".equals(String.valueOf( 3455 parser.getAttributeValue(null, ATTR_IS_AUXILIARY))); 3456 final InputMethodSubtype subtype = 3457 new InputMethodSubtype(label, icon, imeSubtypeLocale, 3458 imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary); 3459 tempSubtypesArray.add(subtype); 3460 } 3461 } 3462 } catch (XmlPullParserException e) { 3463 Slog.w(TAG, "Error reading subtypes: " + e); 3464 return; 3465 } catch (java.io.IOException e) { 3466 Slog.w(TAG, "Error reading subtypes: " + e); 3467 return; 3468 } catch (NumberFormatException e) { 3469 Slog.w(TAG, "Error reading subtypes: " + e); 3470 return; 3471 } finally { 3472 if (fis != null) { 3473 try { 3474 fis.close(); 3475 } catch (java.io.IOException e1) { 3476 Slog.w(TAG, "Failed to close."); 3477 } 3478 } 3479 } 3480 } 3481 } 3482 3483 // ---------------------------------------------------------------------- 3484 3485 @Override 3486 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 3487 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 3488 != PackageManager.PERMISSION_GRANTED) { 3489 3490 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 3491 + Binder.getCallingPid() 3492 + ", uid=" + Binder.getCallingUid()); 3493 return; 3494 } 3495 3496 IInputMethod method; 3497 ClientState client; 3498 3499 final Printer p = new PrintWriterPrinter(pw); 3500 3501 synchronized (mMethodMap) { 3502 p.println("Current Input Method Manager state:"); 3503 int N = mMethodList.size(); 3504 p.println(" Input Methods:"); 3505 for (int i=0; i<N; i++) { 3506 InputMethodInfo info = mMethodList.get(i); 3507 p.println(" InputMethod #" + i + ":"); 3508 info.dump(p, " "); 3509 } 3510 p.println(" Clients:"); 3511 for (ClientState ci : mClients.values()) { 3512 p.println(" Client " + ci + ":"); 3513 p.println(" client=" + ci.client); 3514 p.println(" inputContext=" + ci.inputContext); 3515 p.println(" sessionRequested=" + ci.sessionRequested); 3516 p.println(" curSession=" + ci.curSession); 3517 } 3518 p.println(" mCurMethodId=" + mCurMethodId); 3519 client = mCurClient; 3520 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 3521 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 3522 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 3523 + " mBoundToMethod=" + mBoundToMethod); 3524 p.println(" mCurToken=" + mCurToken); 3525 p.println(" mCurIntent=" + mCurIntent); 3526 method = mCurMethod; 3527 p.println(" mCurMethod=" + mCurMethod); 3528 p.println(" mEnabledSession=" + mEnabledSession); 3529 p.println(" mShowRequested=" + mShowRequested 3530 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 3531 + " mShowForced=" + mShowForced 3532 + " mInputShown=" + mInputShown); 3533 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 3534 } 3535 3536 p.println(" "); 3537 if (client != null) { 3538 pw.flush(); 3539 try { 3540 client.client.asBinder().dump(fd, args); 3541 } catch (RemoteException e) { 3542 p.println("Input method client dead: " + e); 3543 } 3544 } else { 3545 p.println("No input method client."); 3546 } 3547 3548 p.println(" "); 3549 if (method != null) { 3550 pw.flush(); 3551 try { 3552 method.asBinder().dump(fd, args); 3553 } catch (RemoteException e) { 3554 p.println("Input method service dead: " + e); 3555 } 3556 } else { 3557 p.println("No input method service."); 3558 } 3559 } 3560} 3561