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