InputMethodManagerService.java revision 16331c8a1d33defccc5cbb18694def79196c921b
1/* 2 * Copyright (C) 2006-2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 5 * use this file except in compliance with the License. You may obtain a copy of 6 * the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 * License for the specific language governing permissions and limitations under 14 * the License. 15 */ 16 17package com.android.server; 18 19import com.android.internal.content.PackageMonitor; 20import com.android.internal.os.HandlerCaller; 21import com.android.internal.view.IInputContext; 22import com.android.internal.view.IInputMethod; 23import com.android.internal.view.IInputMethodCallback; 24import com.android.internal.view.IInputMethodClient; 25import com.android.internal.view.IInputMethodManager; 26import com.android.internal.view.IInputMethodSession; 27import com.android.internal.view.InputBindResult; 28 29import com.android.server.StatusBarManagerService; 30 31import org.xmlpull.v1.XmlPullParserException; 32 33import android.app.ActivityManagerNative; 34import android.app.AlertDialog; 35import android.app.PendingIntent; 36import android.content.ComponentName; 37import android.content.ContentResolver; 38import android.content.Context; 39import android.content.DialogInterface; 40import android.content.IntentFilter; 41import android.content.DialogInterface.OnCancelListener; 42import android.content.Intent; 43import android.content.ServiceConnection; 44import android.content.pm.ApplicationInfo; 45import android.content.pm.PackageManager; 46import android.content.pm.ResolveInfo; 47import android.content.pm.ServiceInfo; 48import android.content.res.Configuration; 49import android.content.res.Resources; 50import android.content.res.TypedArray; 51import android.database.ContentObserver; 52import android.os.Binder; 53import android.os.Handler; 54import android.os.IBinder; 55import android.os.IInterface; 56import android.os.Message; 57import android.os.Parcel; 58import android.os.RemoteException; 59import android.os.ResultReceiver; 60import android.os.ServiceManager; 61import android.os.SystemClock; 62import android.provider.Settings; 63import android.provider.Settings.Secure; 64import android.provider.Settings.SettingNotFoundException; 65import android.text.TextUtils; 66import android.util.EventLog; 67import android.util.Pair; 68import android.util.Slog; 69import android.util.PrintWriterPrinter; 70import android.util.Printer; 71import android.view.IWindowManager; 72import android.view.WindowManager; 73import android.view.inputmethod.EditorInfo; 74import android.view.inputmethod.InputBinding; 75import android.view.inputmethod.InputMethod; 76import android.view.inputmethod.InputMethodInfo; 77import android.view.inputmethod.InputMethodManager; 78import android.view.inputmethod.InputMethodSubtype; 79 80import java.io.FileDescriptor; 81import java.io.IOException; 82import java.io.PrintWriter; 83import java.text.Collator; 84import java.util.ArrayList; 85import java.util.HashMap; 86import java.util.HashSet; 87import java.util.List; 88import java.util.Map; 89import java.util.TreeMap; 90 91/** 92 * This class provides a system service that manages input methods. 93 */ 94public class InputMethodManagerService extends IInputMethodManager.Stub 95 implements ServiceConnection, Handler.Callback { 96 static final boolean DEBUG = false; 97 static final String TAG = "InputManagerService"; 98 99 static final int MSG_SHOW_IM_PICKER = 1; 100 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; 101 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; 102 static final int MSG_SHOW_IM_CONFIG = 4; 103 104 static final int MSG_UNBIND_INPUT = 1000; 105 static final int MSG_BIND_INPUT = 1010; 106 static final int MSG_SHOW_SOFT_INPUT = 1020; 107 static final int MSG_HIDE_SOFT_INPUT = 1030; 108 static final int MSG_ATTACH_TOKEN = 1040; 109 static final int MSG_CREATE_SESSION = 1050; 110 111 static final int MSG_START_INPUT = 2000; 112 static final int MSG_RESTART_INPUT = 2010; 113 114 static final int MSG_UNBIND_METHOD = 3000; 115 static final int MSG_BIND_METHOD = 3010; 116 117 static final long TIME_TO_RECONNECT = 10*1000; 118 119 private static final int NOT_A_SUBTYPE_ID = -1; 120 private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID); 121 // If IME doesn't support the system locale, the default subtype will be the first defined one. 122 private static final int DEFAULT_SUBTYPE_ID = 0; 123 124 private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; 125 private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; 126 private static final String SUBTYPE_MODE_VOICE = "voice"; 127 128 final Context mContext; 129 final Resources mRes; 130 final Handler mHandler; 131 final InputMethodSettings mSettings; 132 final SettingsObserver mSettingsObserver; 133 final StatusBarManagerService mStatusBar; 134 final IWindowManager mIWindowManager; 135 final HandlerCaller mCaller; 136 137 final InputBindResult mNoBinding = new InputBindResult(null, null, -1); 138 139 // All known input methods. mMethodMap also serves as the global 140 // lock for this class. 141 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); 142 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); 143 144 class SessionState { 145 final ClientState client; 146 final IInputMethod method; 147 final IInputMethodSession session; 148 149 @Override 150 public String toString() { 151 return "SessionState{uid " + client.uid + " pid " + client.pid 152 + " method " + Integer.toHexString( 153 System.identityHashCode(method)) 154 + " session " + Integer.toHexString( 155 System.identityHashCode(session)) 156 + "}"; 157 } 158 159 SessionState(ClientState _client, IInputMethod _method, 160 IInputMethodSession _session) { 161 client = _client; 162 method = _method; 163 session = _session; 164 } 165 } 166 167 class ClientState { 168 final IInputMethodClient client; 169 final IInputContext inputContext; 170 final int uid; 171 final int pid; 172 final InputBinding binding; 173 174 boolean sessionRequested; 175 SessionState curSession; 176 177 @Override 178 public String toString() { 179 return "ClientState{" + Integer.toHexString( 180 System.identityHashCode(this)) + " uid " + uid 181 + " pid " + pid + "}"; 182 } 183 184 ClientState(IInputMethodClient _client, IInputContext _inputContext, 185 int _uid, int _pid) { 186 client = _client; 187 inputContext = _inputContext; 188 uid = _uid; 189 pid = _pid; 190 binding = new InputBinding(null, inputContext.asBinder(), uid, pid); 191 } 192 } 193 194 final HashMap<IBinder, ClientState> mClients 195 = new HashMap<IBinder, ClientState>(); 196 197 /** 198 * Set once the system is ready to run third party code. 199 */ 200 boolean mSystemReady; 201 202 /** 203 * Id of the currently selected input method. 204 */ 205 String mCurMethodId; 206 207 /** 208 * The current binding sequence number, incremented every time there is 209 * a new bind performed. 210 */ 211 int mCurSeq; 212 213 /** 214 * The client that is currently bound to an input method. 215 */ 216 ClientState mCurClient; 217 218 /** 219 * The last window token that gained focus. 220 */ 221 IBinder mCurFocusedWindow; 222 223 /** 224 * The input context last provided by the current client. 225 */ 226 IInputContext mCurInputContext; 227 228 /** 229 * The attributes last provided by the current client. 230 */ 231 EditorInfo mCurAttribute; 232 233 /** 234 * The input method ID of the input method service that we are currently 235 * connected to or in the process of connecting to. 236 */ 237 String mCurId; 238 239 /** 240 * The current subtype of the current input method. 241 */ 242 private InputMethodSubtype mCurrentSubtype; 243 244 // This list contains the pairs of InputMethodInfo and InputMethodSubtype. 245 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> 246 mShortcutInputMethodsAndSubtypes = 247 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); 248 249 /** 250 * Set to true if our ServiceConnection is currently actively bound to 251 * a service (whether or not we have gotten its IBinder back yet). 252 */ 253 boolean mHaveConnection; 254 255 /** 256 * Set if the client has asked for the input method to be shown. 257 */ 258 boolean mShowRequested; 259 260 /** 261 * Set if we were explicitly told to show the input method. 262 */ 263 boolean mShowExplicitlyRequested; 264 265 /** 266 * Set if we were forced to be shown. 267 */ 268 boolean mShowForced; 269 270 /** 271 * Set if we last told the input method to show itself. 272 */ 273 boolean mInputShown; 274 275 /** 276 * The Intent used to connect to the current input method. 277 */ 278 Intent mCurIntent; 279 280 /** 281 * The token we have made for the currently active input method, to 282 * identify it in the future. 283 */ 284 IBinder mCurToken; 285 286 /** 287 * If non-null, this is the input method service we are currently connected 288 * to. 289 */ 290 IInputMethod mCurMethod; 291 292 /** 293 * Time that we last initiated a bind to the input method, to determine 294 * if we should try to disconnect and reconnect to it. 295 */ 296 long mLastBindTime; 297 298 /** 299 * Have we called mCurMethod.bindInput()? 300 */ 301 boolean mBoundToMethod; 302 303 /** 304 * Currently enabled session. Only touched by service thread, not 305 * protected by a lock. 306 */ 307 SessionState mEnabledSession; 308 309 /** 310 * True if the screen is on. The value is true initially. 311 */ 312 boolean mScreenOn = true; 313 314 AlertDialog.Builder mDialogBuilder; 315 AlertDialog mSwitchingDialog; 316 InputMethodInfo[] mIms; 317 CharSequence[] mItems; 318 int[] mSubtypeIds; 319 320 class SettingsObserver extends ContentObserver { 321 SettingsObserver(Handler handler) { 322 super(handler); 323 ContentResolver resolver = mContext.getContentResolver(); 324 resolver.registerContentObserver(Settings.Secure.getUriFor( 325 Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 326 resolver.registerContentObserver(Settings.Secure.getUriFor( 327 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 328 } 329 330 @Override public void onChange(boolean selfChange) { 331 synchronized (mMethodMap) { 332 updateFromSettingsLocked(); 333 } 334 } 335 } 336 337 class ScreenOnOffReceiver extends android.content.BroadcastReceiver { 338 @Override 339 public void onReceive(Context context, Intent intent) { 340 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { 341 mScreenOn = true; 342 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { 343 mScreenOn = false; 344 } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) { 345 hideInputMethodMenu(); 346 return; 347 } else { 348 Slog.w(TAG, "Unexpected intent " + intent); 349 } 350 351 // Inform the current client of the change in active status 352 try { 353 if (mCurClient != null && mCurClient.client != null) { 354 mCurClient.client.setActive(mScreenOn); 355 } 356 } catch (RemoteException e) { 357 Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid " 358 + mCurClient.pid + " uid " + mCurClient.uid); 359 } 360 } 361 } 362 363 class MyPackageMonitor extends PackageMonitor { 364 365 @Override 366 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 367 synchronized (mMethodMap) { 368 String curInputMethodId = Settings.Secure.getString(mContext 369 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 370 final int N = mMethodList.size(); 371 if (curInputMethodId != null) { 372 for (int i=0; i<N; i++) { 373 InputMethodInfo imi = mMethodList.get(i); 374 if (imi.getId().equals(curInputMethodId)) { 375 for (String pkg : packages) { 376 if (imi.getPackageName().equals(pkg)) { 377 if (!doit) { 378 return true; 379 } 380 resetSelectedInputMethodAndSubtypeLocked(""); 381 chooseNewDefaultIMELocked(); 382 return true; 383 } 384 } 385 } 386 } 387 } 388 } 389 return false; 390 } 391 392 @Override 393 public void onSomePackagesChanged() { 394 synchronized (mMethodMap) { 395 InputMethodInfo curIm = null; 396 String curInputMethodId = Settings.Secure.getString(mContext 397 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 398 final int N = mMethodList.size(); 399 if (curInputMethodId != null) { 400 for (int i=0; i<N; i++) { 401 InputMethodInfo imi = mMethodList.get(i); 402 if (imi.getId().equals(curInputMethodId)) { 403 curIm = imi; 404 } 405 int change = isPackageDisappearing(imi.getPackageName()); 406 if (change == PACKAGE_TEMPORARY_CHANGE 407 || change == PACKAGE_PERMANENT_CHANGE) { 408 Slog.i(TAG, "Input method uninstalled, disabling: " 409 + imi.getComponent()); 410 setInputMethodEnabledLocked(imi.getId(), false); 411 } 412 } 413 } 414 415 buildInputMethodListLocked(mMethodList, mMethodMap); 416 417 boolean changed = false; 418 419 if (curIm != null) { 420 int change = isPackageDisappearing(curIm.getPackageName()); 421 if (change == PACKAGE_TEMPORARY_CHANGE 422 || change == PACKAGE_PERMANENT_CHANGE) { 423 ServiceInfo si = null; 424 try { 425 si = mContext.getPackageManager().getServiceInfo( 426 curIm.getComponent(), 0); 427 } catch (PackageManager.NameNotFoundException ex) { 428 } 429 if (si == null) { 430 // Uh oh, current input method is no longer around! 431 // Pick another one... 432 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 433 if (!chooseNewDefaultIMELocked()) { 434 changed = true; 435 curIm = null; 436 Slog.i(TAG, "Unsetting current input method"); 437 resetSelectedInputMethodAndSubtypeLocked(""); 438 } 439 } 440 } 441 } 442 443 if (curIm == null) { 444 // We currently don't have a default input method... is 445 // one now available? 446 changed = chooseNewDefaultIMELocked(); 447 } 448 449 if (changed) { 450 updateFromSettingsLocked(); 451 } 452 } 453 } 454 } 455 456 class MethodCallback extends IInputMethodCallback.Stub { 457 final IInputMethod mMethod; 458 459 MethodCallback(IInputMethod method) { 460 mMethod = method; 461 } 462 463 public void finishedEvent(int seq, boolean handled) throws RemoteException { 464 } 465 466 public void sessionCreated(IInputMethodSession session) throws RemoteException { 467 onSessionCreated(mMethod, session); 468 } 469 } 470 471 public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { 472 mContext = context; 473 mRes = context.getResources(); 474 mHandler = new Handler(this); 475 mIWindowManager = IWindowManager.Stub.asInterface( 476 ServiceManager.getService(Context.WINDOW_SERVICE)); 477 mCaller = new HandlerCaller(context, new HandlerCaller.Callback() { 478 public void executeMessage(Message msg) { 479 handleMessage(msg); 480 } 481 }); 482 483 (new MyPackageMonitor()).register(mContext, true); 484 485 IntentFilter screenOnOffFilt = new IntentFilter(); 486 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON); 487 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF); 488 screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 489 mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt); 490 491 mStatusBar = statusBar; 492 statusBar.setIconVisibility("ime", false); 493 494 // mSettings should be created before buildInputMethodListLocked 495 mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList); 496 buildInputMethodListLocked(mMethodList, mMethodMap); 497 mSettings.enableAllIMEsIfThereIsNoEnabledIME(); 498 499 if (TextUtils.isEmpty(Settings.Secure.getString( 500 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) { 501 InputMethodInfo defIm = null; 502 for (InputMethodInfo imi: mMethodList) { 503 if (defIm == null && imi.getIsDefaultResourceId() != 0) { 504 try { 505 Resources res = context.createPackageContext( 506 imi.getPackageName(), 0).getResources(); 507 if (res.getBoolean(imi.getIsDefaultResourceId())) { 508 defIm = imi; 509 Slog.i(TAG, "Selected default: " + imi.getId()); 510 } 511 } catch (PackageManager.NameNotFoundException ex) { 512 } catch (Resources.NotFoundException ex) { 513 } 514 } 515 } 516 if (defIm == null && mMethodList.size() > 0) { 517 defIm = mMethodList.get(0); 518 Slog.i(TAG, "No default found, using " + defIm.getId()); 519 } 520 if (defIm != null) { 521 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); 522 } 523 } 524 525 mSettingsObserver = new SettingsObserver(mHandler); 526 updateFromSettingsLocked(); 527 } 528 529 @Override 530 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 531 throws RemoteException { 532 try { 533 return super.onTransact(code, data, reply, flags); 534 } catch (RuntimeException e) { 535 // The input method manager only throws security exceptions, so let's 536 // log all others. 537 if (!(e instanceof SecurityException)) { 538 Slog.e(TAG, "Input Method Manager Crash", e); 539 } 540 throw e; 541 } 542 } 543 544 public void systemReady() { 545 synchronized (mMethodMap) { 546 if (!mSystemReady) { 547 mSystemReady = true; 548 try { 549 startInputInnerLocked(); 550 } catch (RuntimeException e) { 551 Slog.w(TAG, "Unexpected exception", e); 552 } 553 } 554 } 555 } 556 557 public List<InputMethodInfo> getInputMethodList() { 558 synchronized (mMethodMap) { 559 return new ArrayList<InputMethodInfo>(mMethodList); 560 } 561 } 562 563 public List<InputMethodInfo> getEnabledInputMethodList() { 564 synchronized (mMethodMap) { 565 return mSettings.getEnabledInputMethodListLocked(); 566 } 567 } 568 569 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, 570 boolean allowsImplicitlySelectedSubtypes) { 571 synchronized (mMethodMap) { 572 if (imi == null && mCurMethodId != null) { 573 imi = mMethodMap.get(mCurMethodId); 574 } 575 final List<InputMethodSubtype> enabledSubtypes = 576 mSettings.getEnabledInputMethodSubtypeListLocked(imi); 577 if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) { 578 return enabledSubtypes; 579 } else { 580 return getApplicableSubtypesLocked(imi.getSubtypes()); 581 } 582 } 583 } 584 585 public void addClient(IInputMethodClient client, 586 IInputContext inputContext, int uid, int pid) { 587 synchronized (mMethodMap) { 588 mClients.put(client.asBinder(), new ClientState(client, 589 inputContext, uid, pid)); 590 } 591 } 592 593 public void removeClient(IInputMethodClient client) { 594 synchronized (mMethodMap) { 595 mClients.remove(client.asBinder()); 596 } 597 } 598 599 void executeOrSendMessage(IInterface target, Message msg) { 600 if (target.asBinder() instanceof Binder) { 601 mCaller.sendMessage(msg); 602 } else { 603 handleMessage(msg); 604 msg.recycle(); 605 } 606 } 607 608 void unbindCurrentClientLocked() { 609 if (mCurClient != null) { 610 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = " 611 + mCurClient.client.asBinder()); 612 if (mBoundToMethod) { 613 mBoundToMethod = false; 614 if (mCurMethod != null) { 615 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 616 MSG_UNBIND_INPUT, mCurMethod)); 617 } 618 } 619 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 620 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 621 mCurClient.sessionRequested = false; 622 623 // Call setActive(false) on the old client 624 try { 625 mCurClient.client.setActive(false); 626 } catch (RemoteException e) { 627 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " 628 + mCurClient.pid + " uid " + mCurClient.uid); 629 } 630 mCurClient = null; 631 632 hideInputMethodMenuLocked(); 633 } 634 } 635 636 private int getImeShowFlags() { 637 int flags = 0; 638 if (mShowForced) { 639 flags |= InputMethod.SHOW_FORCED 640 | InputMethod.SHOW_EXPLICIT; 641 } else if (mShowExplicitlyRequested) { 642 flags |= InputMethod.SHOW_EXPLICIT; 643 } 644 return flags; 645 } 646 647 private int getAppShowFlags() { 648 int flags = 0; 649 if (mShowForced) { 650 flags |= InputMethodManager.SHOW_FORCED; 651 } else if (!mShowExplicitlyRequested) { 652 flags |= InputMethodManager.SHOW_IMPLICIT; 653 } 654 return flags; 655 } 656 657 InputBindResult attachNewInputLocked(boolean initial, boolean needResult) { 658 if (!mBoundToMethod) { 659 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 660 MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); 661 mBoundToMethod = true; 662 } 663 final SessionState session = mCurClient.curSession; 664 if (initial) { 665 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 666 MSG_START_INPUT, session, mCurInputContext, mCurAttribute)); 667 } else { 668 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 669 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute)); 670 } 671 if (mShowRequested) { 672 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); 673 showCurrentInputLocked(getAppShowFlags(), null); 674 } 675 return needResult 676 ? new InputBindResult(session.session, mCurId, mCurSeq) 677 : null; 678 } 679 680 InputBindResult startInputLocked(IInputMethodClient client, 681 IInputContext inputContext, EditorInfo attribute, 682 boolean initial, boolean needResult) { 683 // If no method is currently selected, do nothing. 684 if (mCurMethodId == null) { 685 return mNoBinding; 686 } 687 688 ClientState cs = mClients.get(client.asBinder()); 689 if (cs == null) { 690 throw new IllegalArgumentException("unknown client " 691 + client.asBinder()); 692 } 693 694 try { 695 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 696 // Check with the window manager to make sure this client actually 697 // has a window with focus. If not, reject. This is thread safe 698 // because if the focus changes some time before or after, the 699 // next client receiving focus that has any interest in input will 700 // be calling through here after that change happens. 701 Slog.w(TAG, "Starting input on non-focused client " + cs.client 702 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 703 return null; 704 } 705 } catch (RemoteException e) { 706 } 707 708 if (mCurClient != cs) { 709 // If the client is changing, we need to switch over to the new 710 // one. 711 unbindCurrentClientLocked(); 712 if (DEBUG) Slog.v(TAG, "switching to client: client = " 713 + cs.client.asBinder()); 714 715 // If the screen is on, inform the new client it is active 716 if (mScreenOn) { 717 try { 718 cs.client.setActive(mScreenOn); 719 } catch (RemoteException e) { 720 Slog.w(TAG, "Got RemoteException sending setActive notification to pid " 721 + cs.pid + " uid " + cs.uid); 722 } 723 } 724 } 725 726 // Bump up the sequence for this client and attach it. 727 mCurSeq++; 728 if (mCurSeq <= 0) mCurSeq = 1; 729 mCurClient = cs; 730 mCurInputContext = inputContext; 731 mCurAttribute = attribute; 732 733 // Check if the input method is changing. 734 if (mCurId != null && mCurId.equals(mCurMethodId)) { 735 if (cs.curSession != null) { 736 // Fast case: if we are already connected to the input method, 737 // then just return it. 738 return attachNewInputLocked(initial, needResult); 739 } 740 if (mHaveConnection) { 741 if (mCurMethod != null) { 742 if (!cs.sessionRequested) { 743 cs.sessionRequested = true; 744 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); 745 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 746 MSG_CREATE_SESSION, mCurMethod, 747 new MethodCallback(mCurMethod))); 748 } 749 // Return to client, and we will get back with it when 750 // we have had a session made for it. 751 return new InputBindResult(null, mCurId, mCurSeq); 752 } else if (SystemClock.uptimeMillis() 753 < (mLastBindTime+TIME_TO_RECONNECT)) { 754 // In this case we have connected to the service, but 755 // don't yet have its interface. If it hasn't been too 756 // long since we did the connection, we'll return to 757 // the client and wait to get the service interface so 758 // we can report back. If it has been too long, we want 759 // to fall through so we can try a disconnect/reconnect 760 // to see if we can get back in touch with the service. 761 return new InputBindResult(null, mCurId, mCurSeq); 762 } else { 763 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 764 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0); 765 } 766 } 767 } 768 769 return startInputInnerLocked(); 770 } 771 772 InputBindResult startInputInnerLocked() { 773 if (mCurMethodId == null) { 774 return mNoBinding; 775 } 776 777 if (!mSystemReady) { 778 // If the system is not yet ready, we shouldn't be running third 779 // party code. 780 return new InputBindResult(null, mCurMethodId, mCurSeq); 781 } 782 783 InputMethodInfo info = mMethodMap.get(mCurMethodId); 784 if (info == null) { 785 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 786 } 787 788 unbindCurrentMethodLocked(false); 789 790 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); 791 mCurIntent.setComponent(info.getComponent()); 792 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 793 com.android.internal.R.string.input_method_binding_label); 794 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 795 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); 796 if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) { 797 mLastBindTime = SystemClock.uptimeMillis(); 798 mHaveConnection = true; 799 mCurId = info.getId(); 800 mCurToken = new Binder(); 801 try { 802 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); 803 mIWindowManager.addWindowToken(mCurToken, 804 WindowManager.LayoutParams.TYPE_INPUT_METHOD); 805 } catch (RemoteException e) { 806 } 807 return new InputBindResult(null, mCurId, mCurSeq); 808 } else { 809 mCurIntent = null; 810 Slog.w(TAG, "Failure connecting to input method service: " 811 + mCurIntent); 812 } 813 return null; 814 } 815 816 public InputBindResult startInput(IInputMethodClient client, 817 IInputContext inputContext, EditorInfo attribute, 818 boolean initial, boolean needResult) { 819 synchronized (mMethodMap) { 820 final long ident = Binder.clearCallingIdentity(); 821 try { 822 return startInputLocked(client, inputContext, attribute, 823 initial, needResult); 824 } finally { 825 Binder.restoreCallingIdentity(ident); 826 } 827 } 828 } 829 830 public void finishInput(IInputMethodClient client) { 831 } 832 833 public void onServiceConnected(ComponentName name, IBinder service) { 834 synchronized (mMethodMap) { 835 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 836 mCurMethod = IInputMethod.Stub.asInterface(service); 837 if (mCurToken == null) { 838 Slog.w(TAG, "Service connected without a token!"); 839 unbindCurrentMethodLocked(false); 840 return; 841 } 842 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 843 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 844 MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); 845 if (mCurClient != null) { 846 if (DEBUG) Slog.v(TAG, "Creating first session while with client " 847 + mCurClient); 848 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 849 MSG_CREATE_SESSION, mCurMethod, 850 new MethodCallback(mCurMethod))); 851 } 852 } 853 } 854 } 855 856 void onSessionCreated(IInputMethod method, IInputMethodSession session) { 857 synchronized (mMethodMap) { 858 if (mCurMethod != null && method != null 859 && mCurMethod.asBinder() == method.asBinder()) { 860 if (mCurClient != null) { 861 mCurClient.curSession = new SessionState(mCurClient, 862 method, session); 863 mCurClient.sessionRequested = false; 864 InputBindResult res = attachNewInputLocked(true, true); 865 if (res.method != null) { 866 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 867 MSG_BIND_METHOD, mCurClient.client, res)); 868 } 869 } 870 } 871 } 872 } 873 874 void unbindCurrentMethodLocked(boolean reportToClient) { 875 if (mHaveConnection) { 876 mContext.unbindService(this); 877 mHaveConnection = false; 878 } 879 880 if (mCurToken != null) { 881 try { 882 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken); 883 mIWindowManager.removeWindowToken(mCurToken); 884 } catch (RemoteException e) { 885 } 886 mCurToken = null; 887 } 888 889 mCurId = null; 890 clearCurMethodLocked(); 891 892 if (reportToClient && mCurClient != null) { 893 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 894 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 895 } 896 } 897 898 private void finishSession(SessionState sessionState) { 899 if (sessionState != null && sessionState.session != null) { 900 try { 901 sessionState.session.finishSession(); 902 } catch (RemoteException e) { 903 Slog.w(TAG, "Session failed to close due to remote exception", e); 904 } 905 } 906 } 907 908 void clearCurMethodLocked() { 909 if (mCurMethod != null) { 910 for (ClientState cs : mClients.values()) { 911 cs.sessionRequested = false; 912 finishSession(cs.curSession); 913 cs.curSession = null; 914 } 915 916 finishSession(mEnabledSession); 917 mEnabledSession = null; 918 mCurMethod = null; 919 } 920 mStatusBar.setIconVisibility("ime", false); 921 } 922 923 public void onServiceDisconnected(ComponentName name) { 924 synchronized (mMethodMap) { 925 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 926 + " mCurIntent=" + mCurIntent); 927 if (mCurMethod != null && mCurIntent != null 928 && name.equals(mCurIntent.getComponent())) { 929 clearCurMethodLocked(); 930 // We consider this to be a new bind attempt, since the system 931 // should now try to restart the service for us. 932 mLastBindTime = SystemClock.uptimeMillis(); 933 mShowRequested = mInputShown; 934 mInputShown = false; 935 if (mCurClient != null) { 936 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 937 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 938 } 939 } 940 } 941 } 942 943 public void updateStatusIcon(IBinder token, String packageName, int iconId) { 944 int uid = Binder.getCallingUid(); 945 long ident = Binder.clearCallingIdentity(); 946 try { 947 if (token == null || mCurToken != token) { 948 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); 949 return; 950 } 951 952 synchronized (mMethodMap) { 953 if (iconId == 0) { 954 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 955 mStatusBar.setIconVisibility("ime", false); 956 } else if (packageName != null) { 957 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 958 mStatusBar.setIcon("ime", packageName, iconId, 0); 959 mStatusBar.setIconVisibility("ime", true); 960 } 961 } 962 } finally { 963 Binder.restoreCallingIdentity(ident); 964 } 965 } 966 967 public void setIMEButtonVisible(IBinder token, boolean visible) { 968 int uid = Binder.getCallingUid(); 969 long ident = Binder.clearCallingIdentity(); 970 try { 971 if (token == null || mCurToken != token) { 972 Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token); 973 return; 974 } 975 976 synchronized (mMethodMap) { 977 mStatusBar.setIMEButtonVisible(token, visible); 978 } 979 } finally { 980 Binder.restoreCallingIdentity(ident); 981 } 982 } 983 984 void updateFromSettingsLocked() { 985 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 986 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 987 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 988 // enabled. 989 String id = Settings.Secure.getString(mContext.getContentResolver(), 990 Settings.Secure.DEFAULT_INPUT_METHOD); 991 // There is no input method selected, try to choose new applicable input method. 992 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 993 id = Settings.Secure.getString(mContext.getContentResolver(), 994 Settings.Secure.DEFAULT_INPUT_METHOD); 995 } 996 if (!TextUtils.isEmpty(id)) { 997 try { 998 setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); 999 } catch (IllegalArgumentException e) { 1000 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 1001 mCurMethodId = null; 1002 unbindCurrentMethodLocked(true); 1003 } 1004 mShortcutInputMethodsAndSubtypes.clear(); 1005 } else { 1006 // There is no longer an input method set, so stop any current one. 1007 mCurMethodId = null; 1008 unbindCurrentMethodLocked(true); 1009 } 1010 } 1011 1012 /* package */ void setInputMethodLocked(String id, int subtypeId) { 1013 InputMethodInfo info = mMethodMap.get(id); 1014 if (info == null) { 1015 throw new IllegalArgumentException("Unknown id: " + id); 1016 } 1017 1018 if (id.equals(mCurMethodId)) { 1019 ArrayList<InputMethodSubtype> subtypes = info.getSubtypes(); 1020 InputMethodSubtype subtype = null; 1021 if (subtypeId >= 0 && subtypeId < subtypes.size()) { 1022 subtype = subtypes.get(subtypeId); 1023 } 1024 if (subtype != mCurrentSubtype) { 1025 synchronized (mMethodMap) { 1026 if (mCurMethod != null) { 1027 try { 1028 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 1029 if (mInputShown) { 1030 // If mInputShown is false, there is no IME button on the 1031 // system bar. 1032 // Thus there is no need to make it invisible explicitly. 1033 mStatusBar.setIMEButtonVisible(mCurToken, true); 1034 } 1035 // If subtype is null, try to find the most applicable one from 1036 // getCurrentInputMethodSubtype. 1037 if (subtype == null) { 1038 subtype = getCurrentInputMethodSubtype(); 1039 } 1040 mCurMethod.changeInputMethodSubtype(subtype); 1041 } catch (RemoteException e) { 1042 return; 1043 } 1044 } 1045 } 1046 } 1047 return; 1048 } 1049 1050 final long ident = Binder.clearCallingIdentity(); 1051 try { 1052 // Set a subtype to this input method. 1053 // subtypeId the name of a subtype which will be set. 1054 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); 1055 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 1056 // because mCurMethodId is stored as a history in 1057 // setSelectedInputMethodAndSubtypeLocked(). 1058 mCurMethodId = id; 1059 1060 if (ActivityManagerNative.isSystemReady()) { 1061 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 1062 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1063 intent.putExtra("input_method_id", id); 1064 mContext.sendBroadcast(intent); 1065 } 1066 unbindCurrentClientLocked(); 1067 } finally { 1068 Binder.restoreCallingIdentity(ident); 1069 } 1070 } 1071 1072 public boolean showSoftInput(IInputMethodClient client, int flags, 1073 ResultReceiver resultReceiver) { 1074 int uid = Binder.getCallingUid(); 1075 long ident = Binder.clearCallingIdentity(); 1076 try { 1077 synchronized (mMethodMap) { 1078 if (mCurClient == null || client == null 1079 || mCurClient.client.asBinder() != client.asBinder()) { 1080 try { 1081 // We need to check if this is the current client with 1082 // focus in the window manager, to allow this call to 1083 // be made before input is started in it. 1084 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1085 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 1086 return false; 1087 } 1088 } catch (RemoteException e) { 1089 return false; 1090 } 1091 } 1092 1093 if (DEBUG) Slog.v(TAG, "Client requesting input be shown"); 1094 return showCurrentInputLocked(flags, resultReceiver); 1095 } 1096 } finally { 1097 Binder.restoreCallingIdentity(ident); 1098 } 1099 } 1100 1101 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1102 mShowRequested = true; 1103 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 1104 mShowExplicitlyRequested = true; 1105 } 1106 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 1107 mShowExplicitlyRequested = true; 1108 mShowForced = true; 1109 } 1110 1111 if (!mSystemReady) { 1112 return false; 1113 } 1114 1115 boolean res = false; 1116 if (mCurMethod != null) { 1117 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 1118 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, 1119 resultReceiver)); 1120 mInputShown = true; 1121 res = true; 1122 } else if (mHaveConnection && SystemClock.uptimeMillis() 1123 < (mLastBindTime+TIME_TO_RECONNECT)) { 1124 // The client has asked to have the input method shown, but 1125 // we have been sitting here too long with a connection to the 1126 // service and no interface received, so let's disconnect/connect 1127 // to try to prod things along. 1128 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 1129 SystemClock.uptimeMillis()-mLastBindTime,1); 1130 mContext.unbindService(this); 1131 mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE); 1132 } 1133 1134 return res; 1135 } 1136 1137 public boolean hideSoftInput(IInputMethodClient client, int flags, 1138 ResultReceiver resultReceiver) { 1139 int uid = Binder.getCallingUid(); 1140 long ident = Binder.clearCallingIdentity(); 1141 try { 1142 synchronized (mMethodMap) { 1143 if (mCurClient == null || client == null 1144 || mCurClient.client.asBinder() != client.asBinder()) { 1145 try { 1146 // We need to check if this is the current client with 1147 // focus in the window manager, to allow this call to 1148 // be made before input is started in it. 1149 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1150 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " 1151 + uid + ": " + client); 1152 return false; 1153 } 1154 } catch (RemoteException e) { 1155 return false; 1156 } 1157 } 1158 1159 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 1160 return hideCurrentInputLocked(flags, resultReceiver); 1161 } 1162 } finally { 1163 Binder.restoreCallingIdentity(ident); 1164 } 1165 } 1166 1167 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1168 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 1169 && (mShowExplicitlyRequested || mShowForced)) { 1170 if (DEBUG) Slog.v(TAG, 1171 "Not hiding: explicit show not cancelled by non-explicit hide"); 1172 return false; 1173 } 1174 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 1175 if (DEBUG) Slog.v(TAG, 1176 "Not hiding: forced show not cancelled by not-always hide"); 1177 return false; 1178 } 1179 boolean res; 1180 if (mInputShown && mCurMethod != null) { 1181 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1182 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver)); 1183 res = true; 1184 } else { 1185 res = false; 1186 } 1187 mInputShown = false; 1188 mShowRequested = false; 1189 mShowExplicitlyRequested = false; 1190 mShowForced = false; 1191 return res; 1192 } 1193 1194 public void windowGainedFocus(IInputMethodClient client, IBinder windowToken, 1195 boolean viewHasFocus, boolean isTextEditor, int softInputMode, 1196 boolean first, int windowFlags) { 1197 long ident = Binder.clearCallingIdentity(); 1198 try { 1199 synchronized (mMethodMap) { 1200 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder() 1201 + " viewHasFocus=" + viewHasFocus 1202 + " isTextEditor=" + isTextEditor 1203 + " softInputMode=#" + Integer.toHexString(softInputMode) 1204 + " first=" + first + " flags=#" 1205 + Integer.toHexString(windowFlags)); 1206 1207 if (mCurClient == null || client == null 1208 || mCurClient.client.asBinder() != client.asBinder()) { 1209 try { 1210 // We need to check if this is the current client with 1211 // focus in the window manager, to allow this call to 1212 // be made before input is started in it. 1213 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1214 Slog.w(TAG, "Client not active, ignoring focus gain of: " + client); 1215 return; 1216 } 1217 } catch (RemoteException e) { 1218 } 1219 } 1220 1221 if (mCurFocusedWindow == windowToken) { 1222 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client); 1223 return; 1224 } 1225 mCurFocusedWindow = windowToken; 1226 1227 // Should we auto-show the IME even if the caller has not 1228 // specified what should be done with it? 1229 // We only do this automatically if the window can resize 1230 // to accommodate the IME (so what the user sees will give 1231 // them good context without input information being obscured 1232 // by the IME) or if running on a large screen where there 1233 // is more room for the target window + IME. 1234 final boolean doAutoShow = 1235 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1236 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 1237 || mRes.getConfiguration().isLayoutSizeAtLeast( 1238 Configuration.SCREENLAYOUT_SIZE_LARGE); 1239 1240 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 1241 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 1242 if (!isTextEditor || !doAutoShow) { 1243 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { 1244 // There is no focus view, and this window will 1245 // be behind any soft input window, so hide the 1246 // soft input window if it is shown. 1247 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 1248 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); 1249 } 1250 } else if (isTextEditor && doAutoShow && (softInputMode & 1251 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1252 // There is a focus view, and we are navigating forward 1253 // into the window, so show the input window for the user. 1254 // We only do this automatically if the window an resize 1255 // to accomodate the IME (so what the user sees will give 1256 // them good context without input information being obscured 1257 // by the IME) or if running on a large screen where there 1258 // is more room for the target window + IME. 1259 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 1260 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1261 } 1262 break; 1263 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 1264 // Do nothing. 1265 break; 1266 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 1267 if ((softInputMode & 1268 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1269 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 1270 hideCurrentInputLocked(0, null); 1271 } 1272 break; 1273 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 1274 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 1275 hideCurrentInputLocked(0, null); 1276 break; 1277 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 1278 if ((softInputMode & 1279 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1280 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 1281 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1282 } 1283 break; 1284 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 1285 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 1286 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1287 break; 1288 } 1289 } 1290 } finally { 1291 Binder.restoreCallingIdentity(ident); 1292 } 1293 } 1294 1295 public void showInputMethodPickerFromClient(IInputMethodClient client) { 1296 synchronized (mMethodMap) { 1297 if (mCurClient == null || client == null 1298 || mCurClient.client.asBinder() != client.asBinder()) { 1299 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 1300 + Binder.getCallingUid() + ": " + client); 1301 } 1302 1303 // Always call subtype picker, because subtype picker is a superset of input method 1304 // picker. 1305 mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); 1306 } 1307 } 1308 1309 public void setInputMethod(IBinder token, String id) { 1310 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); 1311 } 1312 1313 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { 1314 synchronized (mMethodMap) { 1315 if (subtype != null) { 1316 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode( 1317 mMethodMap.get(id), subtype.hashCode())); 1318 } else { 1319 setInputMethod(token, id); 1320 } 1321 } 1322 } 1323 1324 public void showInputMethodAndSubtypeEnablerFromClient( 1325 IInputMethodClient client, String inputMethodId) { 1326 synchronized (mMethodMap) { 1327 if (mCurClient == null || client == null 1328 || mCurClient.client.asBinder() != client.asBinder()) { 1329 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); 1330 } 1331 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 1332 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); 1333 } 1334 } 1335 1336 public boolean switchToLastInputMethod(IBinder token) { 1337 synchronized (mMethodMap) { 1338 Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 1339 if (lastIme != null) { 1340 InputMethodInfo imi = mMethodMap.get(lastIme.first); 1341 if (imi != null) { 1342 setInputMethodWithSubtypeId(token, lastIme.first, getSubtypeIdFromHashCode( 1343 imi, Integer.valueOf(lastIme.second))); 1344 return true; 1345 } 1346 } 1347 return false; 1348 } 1349 } 1350 1351 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { 1352 synchronized (mMethodMap) { 1353 if (token == null) { 1354 if (mContext.checkCallingOrSelfPermission( 1355 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1356 != PackageManager.PERMISSION_GRANTED) { 1357 throw new SecurityException( 1358 "Using null token requires permission " 1359 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1360 } 1361 } else if (mCurToken != token) { 1362 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 1363 + " token: " + token); 1364 return; 1365 } 1366 1367 long ident = Binder.clearCallingIdentity(); 1368 try { 1369 setInputMethodLocked(id, subtypeId); 1370 } finally { 1371 Binder.restoreCallingIdentity(ident); 1372 } 1373 } 1374 } 1375 1376 public void hideMySoftInput(IBinder token, int flags) { 1377 synchronized (mMethodMap) { 1378 if (token == null || mCurToken != token) { 1379 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " 1380 + Binder.getCallingUid() + " token: " + token); 1381 return; 1382 } 1383 long ident = Binder.clearCallingIdentity(); 1384 try { 1385 hideCurrentInputLocked(flags, null); 1386 } finally { 1387 Binder.restoreCallingIdentity(ident); 1388 } 1389 } 1390 } 1391 1392 public void showMySoftInput(IBinder token, int flags) { 1393 synchronized (mMethodMap) { 1394 if (token == null || mCurToken != token) { 1395 Slog.w(TAG, "Ignoring showMySoftInput of uid " 1396 + Binder.getCallingUid() + " token: " + token); 1397 return; 1398 } 1399 long ident = Binder.clearCallingIdentity(); 1400 try { 1401 showCurrentInputLocked(flags, null); 1402 } finally { 1403 Binder.restoreCallingIdentity(ident); 1404 } 1405 } 1406 } 1407 1408 void setEnabledSessionInMainThread(SessionState session) { 1409 if (mEnabledSession != session) { 1410 if (mEnabledSession != null) { 1411 try { 1412 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 1413 mEnabledSession.method.setSessionEnabled( 1414 mEnabledSession.session, false); 1415 } catch (RemoteException e) { 1416 } 1417 } 1418 mEnabledSession = session; 1419 try { 1420 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 1421 session.method.setSessionEnabled( 1422 session.session, true); 1423 } catch (RemoteException e) { 1424 } 1425 } 1426 } 1427 1428 public boolean handleMessage(Message msg) { 1429 HandlerCaller.SomeArgs args; 1430 switch (msg.what) { 1431 case MSG_SHOW_IM_PICKER: 1432 showInputMethodMenu(); 1433 return true; 1434 1435 case MSG_SHOW_IM_SUBTYPE_PICKER: 1436 showInputMethodSubtypeMenu(); 1437 return true; 1438 1439 case MSG_SHOW_IM_SUBTYPE_ENABLER: 1440 args = (HandlerCaller.SomeArgs)msg.obj; 1441 showInputMethodAndSubtypeEnabler((String)args.arg1); 1442 return true; 1443 1444 case MSG_SHOW_IM_CONFIG: 1445 showConfigureInputMethods(); 1446 return true; 1447 1448 // --------------------------------------------------------- 1449 1450 case MSG_UNBIND_INPUT: 1451 try { 1452 ((IInputMethod)msg.obj).unbindInput(); 1453 } catch (RemoteException e) { 1454 // There is nothing interesting about the method dying. 1455 } 1456 return true; 1457 case MSG_BIND_INPUT: 1458 args = (HandlerCaller.SomeArgs)msg.obj; 1459 try { 1460 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 1461 } catch (RemoteException e) { 1462 } 1463 return true; 1464 case MSG_SHOW_SOFT_INPUT: 1465 args = (HandlerCaller.SomeArgs)msg.obj; 1466 try { 1467 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, 1468 (ResultReceiver)args.arg2); 1469 } catch (RemoteException e) { 1470 } 1471 return true; 1472 case MSG_HIDE_SOFT_INPUT: 1473 args = (HandlerCaller.SomeArgs)msg.obj; 1474 try { 1475 ((IInputMethod)args.arg1).hideSoftInput(0, 1476 (ResultReceiver)args.arg2); 1477 } catch (RemoteException e) { 1478 } 1479 return true; 1480 case MSG_ATTACH_TOKEN: 1481 args = (HandlerCaller.SomeArgs)msg.obj; 1482 try { 1483 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); 1484 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); 1485 } catch (RemoteException e) { 1486 } 1487 return true; 1488 case MSG_CREATE_SESSION: 1489 args = (HandlerCaller.SomeArgs)msg.obj; 1490 try { 1491 ((IInputMethod)args.arg1).createSession( 1492 (IInputMethodCallback)args.arg2); 1493 } catch (RemoteException e) { 1494 } 1495 return true; 1496 // --------------------------------------------------------- 1497 1498 case MSG_START_INPUT: 1499 args = (HandlerCaller.SomeArgs)msg.obj; 1500 try { 1501 SessionState session = (SessionState)args.arg1; 1502 setEnabledSessionInMainThread(session); 1503 session.method.startInput((IInputContext)args.arg2, 1504 (EditorInfo)args.arg3); 1505 } catch (RemoteException e) { 1506 } 1507 return true; 1508 case MSG_RESTART_INPUT: 1509 args = (HandlerCaller.SomeArgs)msg.obj; 1510 try { 1511 SessionState session = (SessionState)args.arg1; 1512 setEnabledSessionInMainThread(session); 1513 session.method.restartInput((IInputContext)args.arg2, 1514 (EditorInfo)args.arg3); 1515 } catch (RemoteException e) { 1516 } 1517 return true; 1518 1519 // --------------------------------------------------------- 1520 1521 case MSG_UNBIND_METHOD: 1522 try { 1523 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1); 1524 } catch (RemoteException e) { 1525 // There is nothing interesting about the last client dying. 1526 } 1527 return true; 1528 case MSG_BIND_METHOD: 1529 args = (HandlerCaller.SomeArgs)msg.obj; 1530 try { 1531 ((IInputMethodClient)args.arg1).onBindMethod( 1532 (InputBindResult)args.arg2); 1533 } catch (RemoteException e) { 1534 Slog.w(TAG, "Client died receiving input method " + args.arg2); 1535 } 1536 return true; 1537 } 1538 return false; 1539 } 1540 1541 private boolean isSystemIme(InputMethodInfo inputMethod) { 1542 return (inputMethod.getServiceInfo().applicationInfo.flags 1543 & ApplicationInfo.FLAG_SYSTEM) != 0; 1544 } 1545 1546 private boolean chooseNewDefaultIMELocked() { 1547 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 1548 if (enabled != null && enabled.size() > 0) { 1549 // We'd prefer to fall back on a system IME, since that is safer. 1550 int i=enabled.size(); 1551 while (i > 0) { 1552 i--; 1553 if ((enabled.get(i).getServiceInfo().applicationInfo.flags 1554 & ApplicationInfo.FLAG_SYSTEM) != 0) { 1555 break; 1556 } 1557 } 1558 InputMethodInfo imi = enabled.get(i); 1559 if (DEBUG) { 1560 Slog.d(TAG, "New default IME was selected: " + imi.getId()); 1561 } 1562 resetSelectedInputMethodAndSubtypeLocked(imi.getId()); 1563 return true; 1564 } 1565 1566 return false; 1567 } 1568 1569 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, 1570 HashMap<String, InputMethodInfo> map) { 1571 list.clear(); 1572 map.clear(); 1573 1574 PackageManager pm = mContext.getPackageManager(); 1575 final Configuration config = mRes.getConfiguration(); 1576 final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY; 1577 String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(), 1578 Secure.DISABLED_SYSTEM_INPUT_METHODS); 1579 if (disabledSysImes == null) disabledSysImes = ""; 1580 1581 List<ResolveInfo> services = pm.queryIntentServices( 1582 new Intent(InputMethod.SERVICE_INTERFACE), 1583 PackageManager.GET_META_DATA); 1584 1585 for (int i = 0; i < services.size(); ++i) { 1586 ResolveInfo ri = services.get(i); 1587 ServiceInfo si = ri.serviceInfo; 1588 ComponentName compName = new ComponentName(si.packageName, si.name); 1589 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals( 1590 si.permission)) { 1591 Slog.w(TAG, "Skipping input method " + compName 1592 + ": it does not require the permission " 1593 + android.Manifest.permission.BIND_INPUT_METHOD); 1594 continue; 1595 } 1596 1597 if (DEBUG) Slog.d(TAG, "Checking " + compName); 1598 1599 try { 1600 InputMethodInfo p = new InputMethodInfo(mContext, ri); 1601 list.add(p); 1602 final String id = p.getId(); 1603 map.put(id, p); 1604 1605 // System IMEs are enabled by default, unless there's a hard keyboard 1606 // and the system IME was explicitly disabled 1607 if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) { 1608 setInputMethodEnabledLocked(id, true); 1609 } 1610 1611 if (DEBUG) { 1612 Slog.d(TAG, "Found a third-party input method " + p); 1613 } 1614 1615 } catch (XmlPullParserException e) { 1616 Slog.w(TAG, "Unable to load input method " + compName, e); 1617 } catch (IOException e) { 1618 Slog.w(TAG, "Unable to load input method " + compName, e); 1619 } 1620 } 1621 1622 String defaultIme = Settings.Secure.getString(mContext 1623 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 1624 if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) { 1625 if (chooseNewDefaultIMELocked()) { 1626 updateFromSettingsLocked(); 1627 } 1628 } 1629 } 1630 1631 // ---------------------------------------------------------------------- 1632 1633 private void showInputMethodMenu() { 1634 showInputMethodMenuInternal(false); 1635 } 1636 1637 private void showInputMethodSubtypeMenu() { 1638 showInputMethodMenuInternal(true); 1639 } 1640 1641 private void showInputMethodAndSubtypeEnabler(String inputMethodId) { 1642 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_AND_SUBTYPE_ENABLER); 1643 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1644 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1645 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1646 if (!TextUtils.isEmpty(inputMethodId)) { 1647 intent.putExtra(EXTRA_INPUT_METHOD_ID, inputMethodId); 1648 } 1649 mContext.startActivity(intent); 1650 } 1651 1652 private void showConfigureInputMethods() { 1653 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); 1654 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 1655 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 1656 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 1657 mContext.startActivity(intent); 1658 } 1659 1660 private void showInputMethodMenuInternal(boolean showSubtypes) { 1661 if (DEBUG) Slog.v(TAG, "Show switching menu"); 1662 1663 final Context context = mContext; 1664 1665 final PackageManager pm = context.getPackageManager(); 1666 1667 String lastInputMethodId = Settings.Secure.getString(context 1668 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 1669 int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 1670 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 1671 1672 synchronized (mMethodMap) { 1673 final List<Pair<InputMethodInfo, ArrayList<String>>> immis = 1674 mSettings.getEnabledInputMethodAndSubtypeHashCodeListLocked(); 1675 int N = immis.size(); 1676 1677 // Add applicable subtypes if no subtype for each IME is enabled. 1678 for (int i = 0; i < N; ++i) { 1679 InputMethodInfo imi = immis.get(i).first; 1680 ArrayList<String> subtypes = immis.get(i).second; 1681 if (subtypes != null && subtypes.size() == 0) { 1682 ArrayList<InputMethodSubtype> applicableSubtypes = 1683 getApplicableSubtypesLocked(imi.getSubtypes()); 1684 final int numSubtypes = applicableSubtypes.size(); 1685 for (int j = 0; j < numSubtypes; ++j) { 1686 subtypes.add(String.valueOf(applicableSubtypes.get(j).hashCode())); 1687 } 1688 } 1689 } 1690 1691 ArrayList<Integer> subtypeIds = new ArrayList<Integer>(); 1692 1693 if (immis == null || immis.size() == 0) { 1694 return; 1695 } 1696 1697 hideInputMethodMenuLocked(); 1698 1699 1700 final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap = 1701 new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance()); 1702 1703 for (int i = 0; i < N; ++i) { 1704 InputMethodInfo property = immis.get(i).first; 1705 final ArrayList<String> enabledSubtypeIds = immis.get(i).second; 1706 HashSet<String> enabledSubtypeSet = new HashSet<String>(); 1707 for (String s : enabledSubtypeIds) { 1708 enabledSubtypeSet.add(s); 1709 } 1710 if (property == null) { 1711 continue; 1712 } 1713 ArrayList<InputMethodSubtype> subtypes = property.getSubtypes(); 1714 CharSequence label = property.loadLabel(pm); 1715 if (showSubtypes && enabledSubtypeSet.size() > 0) { 1716 for (int j = 0; j < subtypes.size(); ++j) { 1717 InputMethodSubtype subtype = subtypes.get(j); 1718 if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) { 1719 CharSequence title; 1720 int nameResId = subtype.getNameResId(); 1721 String mode = subtype.getMode(); 1722 if (nameResId != 0) { 1723 title = pm.getText(property.getPackageName(), nameResId, 1724 property.getServiceInfo().applicationInfo); 1725 } else { 1726 CharSequence language = subtype.getLocale(); 1727 // TODO: Use more friendly Title and UI 1728 title = label + "," + (mode == null ? "" : mode) + "," 1729 + (language == null ? "" : language); 1730 } 1731 imMap.put(title, new Pair<InputMethodInfo, Integer>(property, j)); 1732 } 1733 } 1734 } else { 1735 imMap.put(label, 1736 new Pair<InputMethodInfo, Integer>(property, NOT_A_SUBTYPE_ID)); 1737 subtypeIds.add(0); 1738 } 1739 } 1740 1741 N = imMap.size(); 1742 mItems = imMap.keySet().toArray(new CharSequence[N]); 1743 mIms = new InputMethodInfo[N]; 1744 mSubtypeIds = new int[N]; 1745 int checkedItem = 0; 1746 for (int i = 0; i < N; ++i) { 1747 Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]); 1748 mIms[i] = value.first; 1749 mSubtypeIds[i] = value.second; 1750 if (mIms[i].getId().equals(lastInputMethodId)) { 1751 int subtypeId = mSubtypeIds[i]; 1752 if ((subtypeId == NOT_A_SUBTYPE_ID) 1753 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 1754 || (subtypeId == lastInputMethodSubtypeId)) { 1755 checkedItem = i; 1756 } 1757 } 1758 } 1759 1760 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { 1761 public void onClick(DialogInterface dialog, int which) { 1762 hideInputMethodMenu(); 1763 } 1764 }; 1765 1766 TypedArray a = context.obtainStyledAttributes(null, 1767 com.android.internal.R.styleable.DialogPreference, 1768 com.android.internal.R.attr.alertDialogStyle, 0); 1769 mDialogBuilder = new AlertDialog.Builder(context) 1770 .setTitle(com.android.internal.R.string.select_input_method) 1771 .setOnCancelListener(new OnCancelListener() { 1772 public void onCancel(DialogInterface dialog) { 1773 hideInputMethodMenu(); 1774 } 1775 }) 1776 .setIcon(a.getDrawable( 1777 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 1778 a.recycle(); 1779 1780 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, 1781 new AlertDialog.OnClickListener() { 1782 public void onClick(DialogInterface dialog, int which) { 1783 synchronized (mMethodMap) { 1784 if (mIms == null || mIms.length <= which 1785 || mSubtypeIds == null || mSubtypeIds.length <= which) { 1786 return; 1787 } 1788 InputMethodInfo im = mIms[which]; 1789 int subtypeId = mSubtypeIds[which]; 1790 hideInputMethodMenu(); 1791 if (im != null) { 1792 if ((subtypeId < 0) 1793 || (subtypeId >= im.getSubtypes().size())) { 1794 subtypeId = NOT_A_SUBTYPE_ID; 1795 } 1796 setInputMethodLocked(im.getId(), subtypeId); 1797 } 1798 } 1799 } 1800 }); 1801 1802 if (showSubtypes) { 1803 mDialogBuilder.setPositiveButton(com.android.internal.R.string.more_item_label, 1804 new DialogInterface.OnClickListener() { 1805 public void onClick(DialogInterface dialog, int whichButton) { 1806 showConfigureInputMethods(); 1807 } 1808 }); 1809 } 1810 mDialogBuilder.setNegativeButton(com.android.internal.R.string.cancel, 1811 new DialogInterface.OnClickListener() { 1812 public void onClick(DialogInterface dialog, int whichButton) { 1813 hideInputMethodMenu(); 1814 } 1815 }); 1816 mSwitchingDialog = mDialogBuilder.create(); 1817 mSwitchingDialog.getWindow().setType( 1818 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1819 mSwitchingDialog.show(); 1820 } 1821 } 1822 1823 void hideInputMethodMenu() { 1824 synchronized (mMethodMap) { 1825 hideInputMethodMenuLocked(); 1826 } 1827 } 1828 1829 void hideInputMethodMenuLocked() { 1830 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 1831 1832 if (mSwitchingDialog != null) { 1833 mSwitchingDialog.dismiss(); 1834 mSwitchingDialog = null; 1835 } 1836 1837 mDialogBuilder = null; 1838 mItems = null; 1839 mIms = null; 1840 } 1841 1842 // ---------------------------------------------------------------------- 1843 1844 public boolean setInputMethodEnabled(String id, boolean enabled) { 1845 synchronized (mMethodMap) { 1846 if (mContext.checkCallingOrSelfPermission( 1847 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1848 != PackageManager.PERMISSION_GRANTED) { 1849 throw new SecurityException( 1850 "Requires permission " 1851 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1852 } 1853 1854 long ident = Binder.clearCallingIdentity(); 1855 try { 1856 return setInputMethodEnabledLocked(id, enabled); 1857 } finally { 1858 Binder.restoreCallingIdentity(ident); 1859 } 1860 } 1861 } 1862 1863 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 1864 // Make sure this is a valid input method. 1865 InputMethodInfo imm = mMethodMap.get(id); 1866 if (imm == null) { 1867 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1868 } 1869 1870 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 1871 .getEnabledInputMethodsAndSubtypeListLocked(); 1872 1873 if (enabled) { 1874 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 1875 if (pair.first.equals(id)) { 1876 // We are enabling this input method, but it is already enabled. 1877 // Nothing to do. The previous state was enabled. 1878 return true; 1879 } 1880 } 1881 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 1882 // Previous state was disabled. 1883 return false; 1884 } else { 1885 StringBuilder builder = new StringBuilder(); 1886 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 1887 builder, enabledInputMethodsList, id)) { 1888 // Disabled input method is currently selected, switch to another one. 1889 String selId = Settings.Secure.getString(mContext.getContentResolver(), 1890 Settings.Secure.DEFAULT_INPUT_METHOD); 1891 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 1892 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 1893 resetSelectedInputMethodAndSubtypeLocked(""); 1894 } 1895 // Previous state was enabled. 1896 return true; 1897 } else { 1898 // We are disabling the input method but it is already disabled. 1899 // Nothing to do. The previous state was disabled. 1900 return false; 1901 } 1902 } 1903 } 1904 1905 private void saveCurrentInputMethodAndSubtypeToHistory() { 1906 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1907 if (mCurrentSubtype != null) { 1908 subtypeId = String.valueOf(mCurrentSubtype.hashCode()); 1909 } 1910 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); 1911 } 1912 1913 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 1914 boolean setSubtypeOnly) { 1915 // Update the history of InputMethod and Subtype 1916 saveCurrentInputMethodAndSubtypeToHistory(); 1917 1918 // Set Subtype here 1919 if (imi == null || subtypeId < 0) { 1920 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 1921 mCurrentSubtype = null; 1922 } else { 1923 final ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); 1924 if (subtypeId < subtypes.size()) { 1925 mSettings.putSelectedSubtype(subtypes.get(subtypeId).hashCode()); 1926 mCurrentSubtype = subtypes.get(subtypeId); 1927 } else { 1928 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 1929 mCurrentSubtype = null; 1930 } 1931 } 1932 1933 if (!setSubtypeOnly) { 1934 // Set InputMethod here 1935 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 1936 } 1937 } 1938 1939 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 1940 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 1941 int lastSubtypeId = NOT_A_SUBTYPE_ID; 1942 // newDefaultIme is empty when there is no candidate for the selected IME. 1943 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 1944 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 1945 if (subtypeHashCode != null) { 1946 try { 1947 lastSubtypeId = getSubtypeIdFromHashCode( 1948 imi, Integer.valueOf(subtypeHashCode)); 1949 } catch (NumberFormatException e) { 1950 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 1951 } 1952 } 1953 } 1954 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 1955 } 1956 1957 private int getSelectedInputMethodSubtypeId(String id) { 1958 InputMethodInfo imi = mMethodMap.get(id); 1959 if (imi == null) { 1960 return NOT_A_SUBTYPE_ID; 1961 } 1962 int subtypeId; 1963 try { 1964 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), 1965 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); 1966 } catch (SettingNotFoundException e) { 1967 return NOT_A_SUBTYPE_ID; 1968 } 1969 return getSubtypeIdFromHashCode(imi, subtypeId); 1970 } 1971 1972 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 1973 if (imi != null) { 1974 ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); 1975 for (int i = 0; i < subtypes.size(); ++i) { 1976 InputMethodSubtype ims = subtypes.get(i); 1977 if (subtypeHashCode == ims.hashCode()) { 1978 return i; 1979 } 1980 } 1981 } 1982 return NOT_A_SUBTYPE_ID; 1983 } 1984 1985 private ArrayList<InputMethodSubtype> getApplicableSubtypesLocked( 1986 List<InputMethodSubtype> subtypes) { 1987 ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(); 1988 final String systemLocale = mRes.getConfiguration().locale.toString(); 1989 if (TextUtils.isEmpty(systemLocale)) return applicableSubtypes; 1990 final int N = subtypes.size(); 1991 boolean containsKeyboardSubtype = false; 1992 for (int i = 0; i < N; ++i) { 1993 InputMethodSubtype subtype = subtypes.get(i); 1994 if (subtype.getLocale().equals(systemLocale)) { 1995 applicableSubtypes.add(subtype); 1996 if (!containsKeyboardSubtype 1997 && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { 1998 containsKeyboardSubtype = true; 1999 } 2000 } 2001 } 2002 if (!containsKeyboardSubtype) { 2003 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 2004 subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, DEFAULT_SUBTYPE_ID); 2005 if (lastResortKeyboardSubtype != null) { 2006 applicableSubtypes.add(lastResortKeyboardSubtype); 2007 } 2008 } 2009 return applicableSubtypes; 2010 } 2011 2012 /** 2013 * If there are no selected subtypes, tries finding the most applicable one according to the 2014 * given locale. 2015 * @param subtypes this function will search the most applicable subtype in subtypes 2016 * @param mode subtypes will be filtered by mode 2017 * @param locale subtypes will be filtered by locale 2018 * @param defaultSubtypeId if this function can't find the most applicable subtype, it will 2019 * return defaultSubtypeId filtered by mode 2020 * @return the most applicable subtypeId 2021 */ 2022 private InputMethodSubtype findLastResortApplicableSubtypeLocked( 2023 List<InputMethodSubtype> subtypes, String mode, String locale, int defaultSubtypeId) { 2024 if (subtypes == null || subtypes.size() == 0) { 2025 return null; 2026 } 2027 if (TextUtils.isEmpty(locale)) { 2028 locale = mRes.getConfiguration().locale.toString(); 2029 } 2030 final String language = locale.substring(0, 2); 2031 boolean partialMatchFound = false; 2032 InputMethodSubtype applicableSubtype = null; 2033 final int N = subtypes.size(); 2034 for (int i = 0; i < N; ++i) { 2035 InputMethodSubtype subtype = subtypes.get(i); 2036 final String subtypeLocale = subtype.getLocale(); 2037 // An applicable subtype should match "mode". 2038 if (subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 2039 if (locale.equals(subtypeLocale)) { 2040 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 2041 applicableSubtype = subtype; 2042 break; 2043 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { 2044 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 2045 applicableSubtype = subtype; 2046 partialMatchFound = true; 2047 } 2048 } 2049 } 2050 2051 if (applicableSubtype == null && defaultSubtypeId != NOT_A_SUBTYPE_ID) { 2052 if (defaultSubtypeId >= 0 && N > defaultSubtypeId) { 2053 InputMethodSubtype defaultSubtype = subtypes.get(defaultSubtypeId); 2054 if (defaultSubtype.getMode().equalsIgnoreCase(mode)) { 2055 return defaultSubtype; 2056 } 2057 } 2058 } 2059 2060 // The first subtype applicable to the system locale will be defined as the most applicable 2061 // subtype. 2062 if (DEBUG) { 2063 if (applicableSubtype != null) { 2064 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 2065 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 2066 } 2067 } 2068 return applicableSubtype; 2069 } 2070 2071 // If there are no selected shortcuts, tries finding the most applicable ones. 2072 private Pair<InputMethodInfo, InputMethodSubtype> 2073 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2074 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2075 InputMethodInfo mostApplicableIMI = null; 2076 InputMethodSubtype mostApplicableSubtype = null; 2077 boolean foundInSystemIME = false; 2078 2079 // Search applicable subtype for each InputMethodInfo 2080 for (InputMethodInfo imi: imis) { 2081 InputMethodSubtype subtype = null; 2082 if (mCurrentSubtype != null) { 2083 // 1. Search with the current subtype's locale and the enabled subtypes 2084 subtype = findLastResortApplicableSubtypeLocked( 2085 mSettings.getEnabledInputMethodSubtypeListLocked( 2086 imi), mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID); 2087 if (subtype == null) { 2088 // 2. Search with the current subtype's locale and all subtypes 2089 subtype = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), 2090 mode, mCurrentSubtype.getLocale(), NOT_A_SUBTYPE_ID); 2091 } 2092 } 2093 // 3. Search with the system locale and the enabled subtypes 2094 if (subtype == null) { 2095 subtype = findLastResortApplicableSubtypeLocked( 2096 mSettings.getEnabledInputMethodSubtypeListLocked( 2097 imi), mode, null, NOT_A_SUBTYPE_ID); 2098 } 2099 if (subtype == null) { 2100 // 4. Search with the system locale and all subtypes 2101 subtype = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), 2102 mode, null, NOT_A_SUBTYPE_ID); 2103 } 2104 if (subtype != null) { 2105 if (imi.getId().equals(mCurMethodId)) { 2106 // The current input method is the most applicable IME. 2107 mostApplicableIMI = imi; 2108 mostApplicableSubtype = subtype; 2109 break; 2110 } else if ((imi.getServiceInfo().applicationInfo.flags 2111 & ApplicationInfo.FLAG_SYSTEM) != 0) { 2112 // The system input method is 2nd applicable IME. 2113 mostApplicableIMI = imi; 2114 mostApplicableSubtype = subtype; 2115 foundInSystemIME = true; 2116 } else if (!foundInSystemIME) { 2117 mostApplicableIMI = imi; 2118 mostApplicableSubtype = subtype; 2119 } 2120 } 2121 } 2122 if (DEBUG) { 2123 if (mostApplicableIMI != null) { 2124 Slog.w(TAG, "Most applicable shortcut input method was:" 2125 + mostApplicableIMI.getId()); 2126 if (mostApplicableSubtype != null) { 2127 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 2128 + "," + mostApplicableSubtype.getMode() + "," 2129 + mostApplicableSubtype.getLocale()); 2130 } 2131 } 2132 } 2133 if (mostApplicableIMI != null) { 2134 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 2135 mostApplicableSubtype); 2136 } else { 2137 return null; 2138 } 2139 } 2140 2141 /** 2142 * @return Return the current subtype of this input method. 2143 */ 2144 public InputMethodSubtype getCurrentInputMethodSubtype() { 2145 boolean subtypeIsSelected = false; 2146 try { 2147 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), 2148 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; 2149 } catch (SettingNotFoundException e) { 2150 } 2151 synchronized (mMethodMap) { 2152 if (!subtypeIsSelected || mCurrentSubtype == null) { 2153 String lastInputMethodId = Settings.Secure.getString( 2154 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2155 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 2156 if (subtypeId == NOT_A_SUBTYPE_ID) { 2157 InputMethodInfo imi = mMethodMap.get(lastInputMethodId); 2158 if (imi != null) { 2159 // If there are no selected subtypes, the framework will try to find 2160 // the most applicable subtype from all subtypes whose mode is 2161 // SUBTYPE_MODE_KEYBOARD. This is an exceptional case, so we will hardcode 2162 // the mode. 2163 mCurrentSubtype = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), 2164 SUBTYPE_MODE_KEYBOARD, null, DEFAULT_SUBTYPE_ID); 2165 } 2166 } else { 2167 mCurrentSubtype = 2168 mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId); 2169 } 2170 } 2171 return mCurrentSubtype; 2172 } 2173 } 2174 2175 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 2176 InputMethodSubtype subtype) { 2177 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 2178 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 2179 } else { 2180 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2181 subtypes.add(subtype); 2182 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 2183 } 2184 } 2185 2186 // TODO: We should change the return type from List to List<Parcelable> 2187 public List getShortcutInputMethodsAndSubtypes() { 2188 synchronized (mMethodMap) { 2189 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 2190 // If there are no selected shortcut subtypes, the framework will try to find 2191 // the most applicable subtype from all subtypes whose mode is 2192 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 2193 Pair<InputMethodInfo, InputMethodSubtype> info = 2194 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 2195 SUBTYPE_MODE_VOICE); 2196 addShortcutInputMethodAndSubtypes(info.first, info.second); 2197 } 2198 ArrayList<Object> ret = new ArrayList<Object>(); 2199 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 2200 ret.add(imi); 2201 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 2202 ret.add(subtype); 2203 } 2204 } 2205 return ret; 2206 } 2207 } 2208 2209 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 2210 synchronized (mMethodMap) { 2211 if (subtype != null && mCurMethodId != null) { 2212 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2213 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); 2214 if (subtypeId != NOT_A_SUBTYPE_ID) { 2215 setInputMethodLocked(mCurMethodId, subtypeId); 2216 return true; 2217 } 2218 } 2219 return false; 2220 } 2221 } 2222 2223 /** 2224 * Utility class for putting and getting settings for InputMethod 2225 * TODO: Move all putters and getters of settings to this class. 2226 */ 2227 private static class InputMethodSettings { 2228 // The string for enabled input method is saved as follows: 2229 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 2230 private static final char INPUT_METHOD_SEPARATER = ':'; 2231 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 2232 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 2233 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 2234 2235 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 2236 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 2237 2238 private final ContentResolver mResolver; 2239 private final HashMap<String, InputMethodInfo> mMethodMap; 2240 private final ArrayList<InputMethodInfo> mMethodList; 2241 2242 private String mEnabledInputMethodsStrCache; 2243 2244 private static void buildEnabledInputMethodsSettingString( 2245 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 2246 String id = pair.first; 2247 ArrayList<String> subtypes = pair.second; 2248 builder.append(id); 2249 // Inputmethod and subtypes are saved in the settings as follows: 2250 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 2251 for (String subtypeId: subtypes) { 2252 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 2253 } 2254 } 2255 2256 public InputMethodSettings( 2257 ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap, 2258 ArrayList<InputMethodInfo> methodList) { 2259 mResolver = resolver; 2260 mMethodMap = methodMap; 2261 mMethodList = methodList; 2262 } 2263 2264 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 2265 return createEnabledInputMethodListLocked( 2266 getEnabledInputMethodsAndSubtypeListLocked()); 2267 } 2268 2269 public List<Pair<InputMethodInfo, ArrayList<String>>> 2270 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 2271 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 2272 getEnabledInputMethodsAndSubtypeListLocked()); 2273 } 2274 2275 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 2276 InputMethodInfo imi) { 2277 List<Pair<String, ArrayList<String>>> imsList = 2278 getEnabledInputMethodsAndSubtypeListLocked(); 2279 ArrayList<InputMethodSubtype> enabledSubtypes = 2280 new ArrayList<InputMethodSubtype>(); 2281 if (imi != null) { 2282 for (Pair<String, ArrayList<String>> imsPair : imsList) { 2283 InputMethodInfo info = mMethodMap.get(imsPair.first); 2284 if (info != null && info.getId().equals(imi.getId())) { 2285 ArrayList<InputMethodSubtype> subtypes = info.getSubtypes(); 2286 for (InputMethodSubtype ims: subtypes) { 2287 for (String s: imsPair.second) { 2288 if (String.valueOf(ims.hashCode()).equals(s)) { 2289 enabledSubtypes.add(ims); 2290 } 2291 } 2292 } 2293 break; 2294 } 2295 } 2296 } 2297 return enabledSubtypes; 2298 } 2299 2300 // At the initial boot, the settings for input methods are not set, 2301 // so we need to enable IME in that case. 2302 public void enableAllIMEsIfThereIsNoEnabledIME() { 2303 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 2304 StringBuilder sb = new StringBuilder(); 2305 final int N = mMethodList.size(); 2306 for (int i = 0; i < N; i++) { 2307 InputMethodInfo imi = mMethodList.get(i); 2308 Slog.i(TAG, "Adding: " + imi.getId()); 2309 if (i > 0) sb.append(':'); 2310 sb.append(imi.getId()); 2311 } 2312 putEnabledInputMethodsStr(sb.toString()); 2313 } 2314 } 2315 2316 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 2317 ArrayList<Pair<String, ArrayList<String>>> imsList 2318 = new ArrayList<Pair<String, ArrayList<String>>>(); 2319 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 2320 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 2321 return imsList; 2322 } 2323 mInputMethodSplitter.setString(enabledInputMethodsStr); 2324 while (mInputMethodSplitter.hasNext()) { 2325 String nextImsStr = mInputMethodSplitter.next(); 2326 mSubtypeSplitter.setString(nextImsStr); 2327 if (mSubtypeSplitter.hasNext()) { 2328 ArrayList<String> subtypeHashes = new ArrayList<String>(); 2329 // The first element is ime id. 2330 String imeId = mSubtypeSplitter.next(); 2331 while (mSubtypeSplitter.hasNext()) { 2332 subtypeHashes.add(mSubtypeSplitter.next()); 2333 } 2334 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 2335 } 2336 } 2337 return imsList; 2338 } 2339 2340 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 2341 if (reloadInputMethodStr) { 2342 getEnabledInputMethodsStr(); 2343 } 2344 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 2345 // Add in the newly enabled input method. 2346 putEnabledInputMethodsStr(id); 2347 } else { 2348 putEnabledInputMethodsStr( 2349 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 2350 } 2351 } 2352 2353 /** 2354 * Build and put a string of EnabledInputMethods with removing specified Id. 2355 * @return the specified id was removed or not. 2356 */ 2357 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2358 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 2359 boolean isRemoved = false; 2360 boolean needsAppendSeparator = false; 2361 for (Pair<String, ArrayList<String>> ims: imsList) { 2362 String curId = ims.first; 2363 if (curId.equals(id)) { 2364 // We are disabling this input method, and it is 2365 // currently enabled. Skip it to remove from the 2366 // new list. 2367 isRemoved = true; 2368 } else { 2369 if (needsAppendSeparator) { 2370 builder.append(INPUT_METHOD_SEPARATER); 2371 } else { 2372 needsAppendSeparator = true; 2373 } 2374 buildEnabledInputMethodsSettingString(builder, ims); 2375 } 2376 } 2377 if (isRemoved) { 2378 // Update the setting with the new list of input methods. 2379 putEnabledInputMethodsStr(builder.toString()); 2380 } 2381 return isRemoved; 2382 } 2383 2384 private List<InputMethodInfo> createEnabledInputMethodListLocked( 2385 List<Pair<String, ArrayList<String>>> imsList) { 2386 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 2387 for (Pair<String, ArrayList<String>> ims: imsList) { 2388 InputMethodInfo info = mMethodMap.get(ims.first); 2389 if (info != null) { 2390 res.add(info); 2391 } 2392 } 2393 return res; 2394 } 2395 2396 private List<Pair<InputMethodInfo, ArrayList<String>>> 2397 createEnabledInputMethodAndSubtypeHashCodeListLocked( 2398 List<Pair<String, ArrayList<String>>> imsList) { 2399 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 2400 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 2401 for (Pair<String, ArrayList<String>> ims : imsList) { 2402 InputMethodInfo info = mMethodMap.get(ims.first); 2403 if (info != null) { 2404 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 2405 } 2406 } 2407 return res; 2408 } 2409 2410 private void putEnabledInputMethodsStr(String str) { 2411 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); 2412 mEnabledInputMethodsStrCache = str; 2413 } 2414 2415 private String getEnabledInputMethodsStr() { 2416 mEnabledInputMethodsStrCache = Settings.Secure.getString( 2417 mResolver, Settings.Secure.ENABLED_INPUT_METHODS); 2418 if (DEBUG) { 2419 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); 2420 } 2421 return mEnabledInputMethodsStrCache; 2422 } 2423 2424 private void saveSubtypeHistory( 2425 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 2426 StringBuilder builder = new StringBuilder(); 2427 boolean isImeAdded = false; 2428 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 2429 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2430 newSubtypeId); 2431 isImeAdded = true; 2432 } 2433 for (Pair<String, String> ime: savedImes) { 2434 String imeId = ime.first; 2435 String subtypeId = ime.second; 2436 if (TextUtils.isEmpty(subtypeId)) { 2437 subtypeId = NOT_A_SUBTYPE_ID_STR; 2438 } 2439 if (isImeAdded) { 2440 builder.append(INPUT_METHOD_SEPARATER); 2441 } else { 2442 isImeAdded = true; 2443 } 2444 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2445 subtypeId); 2446 } 2447 // Remove the last INPUT_METHOD_SEPARATER 2448 putSubtypeHistoryStr(builder.toString()); 2449 } 2450 2451 public void addSubtypeToHistory(String imeId, String subtypeId) { 2452 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2453 for (Pair<String, String> ime: subtypeHistory) { 2454 if (ime.first.equals(imeId)) { 2455 if (DEBUG) { 2456 Slog.v(TAG, "Subtype found in the history: " + imeId 2457 + ime.second); 2458 } 2459 // We should break here 2460 subtypeHistory.remove(ime); 2461 break; 2462 } 2463 } 2464 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 2465 } 2466 2467 private void putSubtypeHistoryStr(String str) { 2468 if (DEBUG) { 2469 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 2470 } 2471 Settings.Secure.putString( 2472 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 2473 } 2474 2475 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 2476 // Gets the first one from the history 2477 return getLastSubtypeForInputMethodLockedInternal(null); 2478 } 2479 2480 public String getLastSubtypeForInputMethodLocked(String imeId) { 2481 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 2482 if (ime != null) { 2483 return ime.second; 2484 } else { 2485 return null; 2486 } 2487 } 2488 2489 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 2490 List<Pair<String, ArrayList<String>>> enabledImes = 2491 getEnabledInputMethodsAndSubtypeListLocked(); 2492 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2493 for (Pair<String, String> imeAndSubtype: subtypeHistory) { 2494 final String imeInTheHistory = imeAndSubtype.first; 2495 // If imeId is empty, returns the first IME and subtype in the history 2496 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 2497 final String subtypeInTheHistory = imeAndSubtype.second; 2498 final String subtypeHashCode = getEnabledSubtypeForInputMethodAndSubtypeLocked( 2499 enabledImes, imeInTheHistory, subtypeInTheHistory); 2500 if (!TextUtils.isEmpty(subtypeHashCode)) { 2501 if (DEBUG) { 2502 Slog.d(TAG, "Enabled subtype found in the history:" + subtypeHashCode); 2503 } 2504 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 2505 } 2506 } 2507 } 2508 if (DEBUG) { 2509 Slog.d(TAG, "No enabled IME found in the history"); 2510 } 2511 return null; 2512 } 2513 2514 private String getEnabledSubtypeForInputMethodAndSubtypeLocked(List<Pair<String, 2515 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 2516 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 2517 if (enabledIme.first.equals(imeId)) { 2518 for (String s: enabledIme.second) { 2519 if (s.equals(subtypeHashCode)) { 2520 // If both imeId and subtypeId are enabled, return subtypeId. 2521 return s; 2522 } 2523 } 2524 // If imeId was enabled but subtypeId was disabled. 2525 return NOT_A_SUBTYPE_ID_STR; 2526 } 2527 } 2528 // If both imeId and subtypeId are disabled, return null 2529 return null; 2530 } 2531 2532 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 2533 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 2534 final String subtypeHistoryStr = getSubtypeHistoryStr(); 2535 if (TextUtils.isEmpty(subtypeHistoryStr)) { 2536 return imsList; 2537 } 2538 mInputMethodSplitter.setString(subtypeHistoryStr); 2539 while (mInputMethodSplitter.hasNext()) { 2540 String nextImsStr = mInputMethodSplitter.next(); 2541 mSubtypeSplitter.setString(nextImsStr); 2542 if (mSubtypeSplitter.hasNext()) { 2543 String subtypeId = NOT_A_SUBTYPE_ID_STR; 2544 // The first element is ime id. 2545 String imeId = mSubtypeSplitter.next(); 2546 while (mSubtypeSplitter.hasNext()) { 2547 subtypeId = mSubtypeSplitter.next(); 2548 break; 2549 } 2550 imsList.add(new Pair<String, String>(imeId, subtypeId)); 2551 } 2552 } 2553 return imsList; 2554 } 2555 2556 private String getSubtypeHistoryStr() { 2557 if (DEBUG) { 2558 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( 2559 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); 2560 } 2561 return Settings.Secure.getString( 2562 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); 2563 } 2564 2565 public void putSelectedInputMethod(String imeId) { 2566 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 2567 } 2568 2569 public void putSelectedSubtype(int subtypeId) { 2570 Settings.Secure.putInt( 2571 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 2572 } 2573 } 2574 2575 // ---------------------------------------------------------------------- 2576 2577 @Override 2578 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2579 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2580 != PackageManager.PERMISSION_GRANTED) { 2581 2582 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 2583 + Binder.getCallingPid() 2584 + ", uid=" + Binder.getCallingUid()); 2585 return; 2586 } 2587 2588 IInputMethod method; 2589 ClientState client; 2590 2591 final Printer p = new PrintWriterPrinter(pw); 2592 2593 synchronized (mMethodMap) { 2594 p.println("Current Input Method Manager state:"); 2595 int N = mMethodList.size(); 2596 p.println(" Input Methods:"); 2597 for (int i=0; i<N; i++) { 2598 InputMethodInfo info = mMethodList.get(i); 2599 p.println(" InputMethod #" + i + ":"); 2600 info.dump(p, " "); 2601 } 2602 p.println(" Clients:"); 2603 for (ClientState ci : mClients.values()) { 2604 p.println(" Client " + ci + ":"); 2605 p.println(" client=" + ci.client); 2606 p.println(" inputContext=" + ci.inputContext); 2607 p.println(" sessionRequested=" + ci.sessionRequested); 2608 p.println(" curSession=" + ci.curSession); 2609 } 2610 p.println(" mCurMethodId=" + mCurMethodId); 2611 client = mCurClient; 2612 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 2613 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 2614 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 2615 + " mBoundToMethod=" + mBoundToMethod); 2616 p.println(" mCurToken=" + mCurToken); 2617 p.println(" mCurIntent=" + mCurIntent); 2618 method = mCurMethod; 2619 p.println(" mCurMethod=" + mCurMethod); 2620 p.println(" mEnabledSession=" + mEnabledSession); 2621 p.println(" mShowRequested=" + mShowRequested 2622 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 2623 + " mShowForced=" + mShowForced 2624 + " mInputShown=" + mInputShown); 2625 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 2626 } 2627 2628 p.println(" "); 2629 if (client != null) { 2630 pw.flush(); 2631 try { 2632 client.client.asBinder().dump(fd, args); 2633 } catch (RemoteException e) { 2634 p.println("Input method client dead: " + e); 2635 } 2636 } else { 2637 p.println("No input method client."); 2638 } 2639 2640 p.println(" "); 2641 if (method != null) { 2642 pw.flush(); 2643 try { 2644 method.asBinder().dump(fd, args); 2645 } catch (RemoteException e) { 2646 p.println("Input method service dead: " + e); 2647 } 2648 } else { 2649 p.println("No input method service."); 2650 } 2651 } 2652} 2653