InputMethodManagerService.java revision 82beadfa067b1e286fa604f8d7960d769411c954
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 private static final String EXTRA_INPUT_METHOD_ID = "input_method_id"; 122 private static final String SUBTYPE_MODE_KEYBOARD = "keyboard"; 123 private static final String SUBTYPE_MODE_VOICE = "voice"; 124 125 final Context mContext; 126 final Resources mRes; 127 final Handler mHandler; 128 final InputMethodSettings mSettings; 129 final SettingsObserver mSettingsObserver; 130 final StatusBarManagerService mStatusBar; 131 final IWindowManager mIWindowManager; 132 final HandlerCaller mCaller; 133 134 final InputBindResult mNoBinding = new InputBindResult(null, null, -1); 135 136 // All known input methods. mMethodMap also serves as the global 137 // lock for this class. 138 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); 139 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); 140 141 class SessionState { 142 final ClientState client; 143 final IInputMethod method; 144 final IInputMethodSession session; 145 146 @Override 147 public String toString() { 148 return "SessionState{uid " + client.uid + " pid " + client.pid 149 + " method " + Integer.toHexString( 150 System.identityHashCode(method)) 151 + " session " + Integer.toHexString( 152 System.identityHashCode(session)) 153 + "}"; 154 } 155 156 SessionState(ClientState _client, IInputMethod _method, 157 IInputMethodSession _session) { 158 client = _client; 159 method = _method; 160 session = _session; 161 } 162 } 163 164 class ClientState { 165 final IInputMethodClient client; 166 final IInputContext inputContext; 167 final int uid; 168 final int pid; 169 final InputBinding binding; 170 171 boolean sessionRequested; 172 SessionState curSession; 173 174 @Override 175 public String toString() { 176 return "ClientState{" + Integer.toHexString( 177 System.identityHashCode(this)) + " uid " + uid 178 + " pid " + pid + "}"; 179 } 180 181 ClientState(IInputMethodClient _client, IInputContext _inputContext, 182 int _uid, int _pid) { 183 client = _client; 184 inputContext = _inputContext; 185 uid = _uid; 186 pid = _pid; 187 binding = new InputBinding(null, inputContext.asBinder(), uid, pid); 188 } 189 } 190 191 final HashMap<IBinder, ClientState> mClients 192 = new HashMap<IBinder, ClientState>(); 193 194 /** 195 * Set once the system is ready to run third party code. 196 */ 197 boolean mSystemReady; 198 199 /** 200 * Id of the currently selected input method. 201 */ 202 String mCurMethodId; 203 204 /** 205 * The current binding sequence number, incremented every time there is 206 * a new bind performed. 207 */ 208 int mCurSeq; 209 210 /** 211 * The client that is currently bound to an input method. 212 */ 213 ClientState mCurClient; 214 215 /** 216 * The last window token that gained focus. 217 */ 218 IBinder mCurFocusedWindow; 219 220 /** 221 * The input context last provided by the current client. 222 */ 223 IInputContext mCurInputContext; 224 225 /** 226 * The attributes last provided by the current client. 227 */ 228 EditorInfo mCurAttribute; 229 230 /** 231 * The input method ID of the input method service that we are currently 232 * connected to or in the process of connecting to. 233 */ 234 String mCurId; 235 236 /** 237 * The current subtype of the current input method. 238 */ 239 private InputMethodSubtype mCurrentSubtype; 240 241 // This list contains the pairs of InputMethodInfo and InputMethodSubtype. 242 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> 243 mShortcutInputMethodsAndSubtypes = 244 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); 245 246 /** 247 * Set to true if our ServiceConnection is currently actively bound to 248 * a service (whether or not we have gotten its IBinder back yet). 249 */ 250 boolean mHaveConnection; 251 252 /** 253 * Set if the client has asked for the input method to be shown. 254 */ 255 boolean mShowRequested; 256 257 /** 258 * Set if we were explicitly told to show the input method. 259 */ 260 boolean mShowExplicitlyRequested; 261 262 /** 263 * Set if we were forced to be shown. 264 */ 265 boolean mShowForced; 266 267 /** 268 * Set if we last told the input method to show itself. 269 */ 270 boolean mInputShown; 271 272 /** 273 * The Intent used to connect to the current input method. 274 */ 275 Intent mCurIntent; 276 277 /** 278 * The token we have made for the currently active input method, to 279 * identify it in the future. 280 */ 281 IBinder mCurToken; 282 283 /** 284 * If non-null, this is the input method service we are currently connected 285 * to. 286 */ 287 IInputMethod mCurMethod; 288 289 /** 290 * Time that we last initiated a bind to the input method, to determine 291 * if we should try to disconnect and reconnect to it. 292 */ 293 long mLastBindTime; 294 295 /** 296 * Have we called mCurMethod.bindInput()? 297 */ 298 boolean mBoundToMethod; 299 300 /** 301 * Currently enabled session. Only touched by service thread, not 302 * protected by a lock. 303 */ 304 SessionState mEnabledSession; 305 306 /** 307 * True if the screen is on. The value is true initially. 308 */ 309 boolean mScreenOn = true; 310 311 AlertDialog.Builder mDialogBuilder; 312 AlertDialog mSwitchingDialog; 313 InputMethodInfo[] mIms; 314 CharSequence[] mItems; 315 int[] mSubtypeIds; 316 317 class SettingsObserver extends ContentObserver { 318 SettingsObserver(Handler handler) { 319 super(handler); 320 ContentResolver resolver = mContext.getContentResolver(); 321 resolver.registerContentObserver(Settings.Secure.getUriFor( 322 Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 323 resolver.registerContentObserver(Settings.Secure.getUriFor( 324 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 325 } 326 327 @Override public void onChange(boolean selfChange) { 328 synchronized (mMethodMap) { 329 updateFromSettingsLocked(); 330 } 331 } 332 } 333 334 class ScreenOnOffReceiver extends android.content.BroadcastReceiver { 335 @Override 336 public void onReceive(Context context, Intent intent) { 337 if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) { 338 mScreenOn = true; 339 } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) { 340 mScreenOn = false; 341 } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) { 342 hideInputMethodMenu(); 343 return; 344 } else { 345 Slog.w(TAG, "Unexpected intent " + intent); 346 } 347 348 // Inform the current client of the change in active status 349 try { 350 if (mCurClient != null && mCurClient.client != null) { 351 mCurClient.client.setActive(mScreenOn); 352 } 353 } catch (RemoteException e) { 354 Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid " 355 + mCurClient.pid + " uid " + mCurClient.uid); 356 } 357 } 358 } 359 360 class MyPackageMonitor extends PackageMonitor { 361 362 @Override 363 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 364 synchronized (mMethodMap) { 365 String curInputMethodId = Settings.Secure.getString(mContext 366 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 367 final int N = mMethodList.size(); 368 if (curInputMethodId != null) { 369 for (int i=0; i<N; i++) { 370 InputMethodInfo imi = mMethodList.get(i); 371 if (imi.getId().equals(curInputMethodId)) { 372 for (String pkg : packages) { 373 if (imi.getPackageName().equals(pkg)) { 374 if (!doit) { 375 return true; 376 } 377 resetSelectedInputMethodAndSubtypeLocked(""); 378 chooseNewDefaultIMELocked(); 379 return true; 380 } 381 } 382 } 383 } 384 } 385 } 386 return false; 387 } 388 389 @Override 390 public void onSomePackagesChanged() { 391 synchronized (mMethodMap) { 392 InputMethodInfo curIm = null; 393 String curInputMethodId = Settings.Secure.getString(mContext 394 .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 395 final int N = mMethodList.size(); 396 if (curInputMethodId != null) { 397 for (int i=0; i<N; i++) { 398 InputMethodInfo imi = mMethodList.get(i); 399 if (imi.getId().equals(curInputMethodId)) { 400 curIm = imi; 401 } 402 int change = isPackageDisappearing(imi.getPackageName()); 403 if (change == PACKAGE_TEMPORARY_CHANGE 404 || change == PACKAGE_PERMANENT_CHANGE) { 405 Slog.i(TAG, "Input method uninstalled, disabling: " 406 + imi.getComponent()); 407 setInputMethodEnabledLocked(imi.getId(), false); 408 } 409 } 410 } 411 412 buildInputMethodListLocked(mMethodList, mMethodMap); 413 414 boolean changed = false; 415 416 if (curIm != null) { 417 int change = isPackageDisappearing(curIm.getPackageName()); 418 if (change == PACKAGE_TEMPORARY_CHANGE 419 || change == PACKAGE_PERMANENT_CHANGE) { 420 ServiceInfo si = null; 421 try { 422 si = mContext.getPackageManager().getServiceInfo( 423 curIm.getComponent(), 0); 424 } catch (PackageManager.NameNotFoundException ex) { 425 } 426 if (si == null) { 427 // Uh oh, current input method is no longer around! 428 // Pick another one... 429 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 430 mStatusBar.setIMEButtonVisible(mCurToken, false); 431 if (!chooseNewDefaultIMELocked()) { 432 changed = true; 433 curIm = null; 434 Slog.i(TAG, "Unsetting current input method"); 435 resetSelectedInputMethodAndSubtypeLocked(""); 436 } 437 } 438 } 439 } 440 441 if (curIm == null) { 442 // We currently don't have a default input method... is 443 // one now available? 444 changed = chooseNewDefaultIMELocked(); 445 } 446 447 if (changed) { 448 updateFromSettingsLocked(); 449 } 450 } 451 } 452 } 453 454 class MethodCallback extends IInputMethodCallback.Stub { 455 final IInputMethod mMethod; 456 457 MethodCallback(IInputMethod method) { 458 mMethod = method; 459 } 460 461 public void finishedEvent(int seq, boolean handled) throws RemoteException { 462 } 463 464 public void sessionCreated(IInputMethodSession session) throws RemoteException { 465 onSessionCreated(mMethod, session); 466 } 467 } 468 469 public InputMethodManagerService(Context context, StatusBarManagerService statusBar) { 470 mContext = context; 471 mRes = context.getResources(); 472 mHandler = new Handler(this); 473 mIWindowManager = IWindowManager.Stub.asInterface( 474 ServiceManager.getService(Context.WINDOW_SERVICE)); 475 mCaller = new HandlerCaller(context, new HandlerCaller.Callback() { 476 public void executeMessage(Message msg) { 477 handleMessage(msg); 478 } 479 }); 480 481 (new MyPackageMonitor()).register(mContext, true); 482 483 IntentFilter screenOnOffFilt = new IntentFilter(); 484 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON); 485 screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF); 486 screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 487 mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt); 488 489 mStatusBar = statusBar; 490 statusBar.setIconVisibility("ime", false); 491 492 // mSettings should be created before buildInputMethodListLocked 493 mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList); 494 buildInputMethodListLocked(mMethodList, mMethodMap); 495 mSettings.enableAllIMEsIfThereIsNoEnabledIME(); 496 497 if (TextUtils.isEmpty(Settings.Secure.getString( 498 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) { 499 InputMethodInfo defIm = null; 500 for (InputMethodInfo imi: mMethodList) { 501 if (defIm == null && imi.getIsDefaultResourceId() != 0) { 502 try { 503 Resources res = context.createPackageContext( 504 imi.getPackageName(), 0).getResources(); 505 if (res.getBoolean(imi.getIsDefaultResourceId())) { 506 defIm = imi; 507 Slog.i(TAG, "Selected default: " + imi.getId()); 508 } 509 } catch (PackageManager.NameNotFoundException ex) { 510 } catch (Resources.NotFoundException ex) { 511 } 512 } 513 } 514 if (defIm == null && mMethodList.size() > 0) { 515 defIm = mMethodList.get(0); 516 Slog.i(TAG, "No default found, using " + defIm.getId()); 517 } 518 if (defIm != null) { 519 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); 520 } 521 } 522 523 mSettingsObserver = new SettingsObserver(mHandler); 524 updateFromSettingsLocked(); 525 } 526 527 @Override 528 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 529 throws RemoteException { 530 try { 531 return super.onTransact(code, data, reply, flags); 532 } catch (RuntimeException e) { 533 // The input method manager only throws security exceptions, so let's 534 // log all others. 535 if (!(e instanceof SecurityException)) { 536 Slog.e(TAG, "Input Method Manager Crash", e); 537 } 538 throw e; 539 } 540 } 541 542 public void systemReady() { 543 synchronized (mMethodMap) { 544 if (!mSystemReady) { 545 mSystemReady = true; 546 try { 547 startInputInnerLocked(); 548 } catch (RuntimeException e) { 549 Slog.w(TAG, "Unexpected exception", e); 550 } 551 } 552 } 553 } 554 555 public List<InputMethodInfo> getInputMethodList() { 556 synchronized (mMethodMap) { 557 return new ArrayList<InputMethodInfo>(mMethodList); 558 } 559 } 560 561 public List<InputMethodInfo> getEnabledInputMethodList() { 562 synchronized (mMethodMap) { 563 return mSettings.getEnabledInputMethodListLocked(); 564 } 565 } 566 567 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, 568 boolean allowsImplicitlySelectedSubtypes) { 569 synchronized (mMethodMap) { 570 if (imi == null && mCurMethodId != null) { 571 imi = mMethodMap.get(mCurMethodId); 572 } 573 final List<InputMethodSubtype> enabledSubtypes = 574 mSettings.getEnabledInputMethodSubtypeListLocked(imi); 575 if (!allowsImplicitlySelectedSubtypes || enabledSubtypes.size() > 0) { 576 return enabledSubtypes; 577 } else { 578 return getApplicableSubtypesLocked(imi.getSubtypes()); 579 } 580 } 581 } 582 583 public void addClient(IInputMethodClient client, 584 IInputContext inputContext, int uid, int pid) { 585 synchronized (mMethodMap) { 586 mClients.put(client.asBinder(), new ClientState(client, 587 inputContext, uid, pid)); 588 } 589 } 590 591 public void removeClient(IInputMethodClient client) { 592 synchronized (mMethodMap) { 593 mClients.remove(client.asBinder()); 594 } 595 } 596 597 void executeOrSendMessage(IInterface target, Message msg) { 598 if (target.asBinder() instanceof Binder) { 599 mCaller.sendMessage(msg); 600 } else { 601 handleMessage(msg); 602 msg.recycle(); 603 } 604 } 605 606 void unbindCurrentClientLocked() { 607 if (mCurClient != null) { 608 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = " 609 + mCurClient.client.asBinder()); 610 if (mBoundToMethod) { 611 mBoundToMethod = false; 612 if (mCurMethod != null) { 613 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 614 MSG_UNBIND_INPUT, mCurMethod)); 615 } 616 } 617 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 618 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 619 mCurClient.sessionRequested = false; 620 621 // Call setActive(false) on the old client 622 try { 623 mCurClient.client.setActive(false); 624 } catch (RemoteException e) { 625 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " 626 + mCurClient.pid + " uid " + mCurClient.uid); 627 } 628 mCurClient = null; 629 630 hideInputMethodMenuLocked(); 631 } 632 } 633 634 private int getImeShowFlags() { 635 int flags = 0; 636 if (mShowForced) { 637 flags |= InputMethod.SHOW_FORCED 638 | InputMethod.SHOW_EXPLICIT; 639 } else if (mShowExplicitlyRequested) { 640 flags |= InputMethod.SHOW_EXPLICIT; 641 } 642 return flags; 643 } 644 645 private int getAppShowFlags() { 646 int flags = 0; 647 if (mShowForced) { 648 flags |= InputMethodManager.SHOW_FORCED; 649 } else if (!mShowExplicitlyRequested) { 650 flags |= InputMethodManager.SHOW_IMPLICIT; 651 } 652 return flags; 653 } 654 655 InputBindResult attachNewInputLocked(boolean initial, boolean needResult) { 656 if (!mBoundToMethod) { 657 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 658 MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); 659 mBoundToMethod = true; 660 } 661 final SessionState session = mCurClient.curSession; 662 if (initial) { 663 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 664 MSG_START_INPUT, session, mCurInputContext, mCurAttribute)); 665 } else { 666 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 667 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute)); 668 } 669 if (mShowRequested) { 670 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); 671 showCurrentInputLocked(getAppShowFlags(), null); 672 } 673 return needResult 674 ? new InputBindResult(session.session, mCurId, mCurSeq) 675 : null; 676 } 677 678 InputBindResult startInputLocked(IInputMethodClient client, 679 IInputContext inputContext, EditorInfo attribute, 680 boolean initial, boolean needResult) { 681 // If no method is currently selected, do nothing. 682 if (mCurMethodId == null) { 683 return mNoBinding; 684 } 685 686 ClientState cs = mClients.get(client.asBinder()); 687 if (cs == null) { 688 throw new IllegalArgumentException("unknown client " 689 + client.asBinder()); 690 } 691 692 try { 693 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 694 // Check with the window manager to make sure this client actually 695 // has a window with focus. If not, reject. This is thread safe 696 // because if the focus changes some time before or after, the 697 // next client receiving focus that has any interest in input will 698 // be calling through here after that change happens. 699 Slog.w(TAG, "Starting input on non-focused client " + cs.client 700 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 701 return null; 702 } 703 } catch (RemoteException e) { 704 } 705 706 if (mCurClient != cs) { 707 // If the client is changing, we need to switch over to the new 708 // one. 709 unbindCurrentClientLocked(); 710 if (DEBUG) Slog.v(TAG, "switching to client: client = " 711 + cs.client.asBinder()); 712 713 // If the screen is on, inform the new client it is active 714 if (mScreenOn) { 715 try { 716 cs.client.setActive(mScreenOn); 717 } catch (RemoteException e) { 718 Slog.w(TAG, "Got RemoteException sending setActive notification to pid " 719 + cs.pid + " uid " + cs.uid); 720 } 721 } 722 } 723 724 // Bump up the sequence for this client and attach it. 725 mCurSeq++; 726 if (mCurSeq <= 0) mCurSeq = 1; 727 mCurClient = cs; 728 mCurInputContext = inputContext; 729 mCurAttribute = attribute; 730 731 // Check if the input method is changing. 732 if (mCurId != null && mCurId.equals(mCurMethodId)) { 733 if (cs.curSession != null) { 734 // Fast case: if we are already connected to the input method, 735 // then just return it. 736 return attachNewInputLocked(initial, needResult); 737 } 738 if (mHaveConnection) { 739 if (mCurMethod != null) { 740 if (!cs.sessionRequested) { 741 cs.sessionRequested = true; 742 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); 743 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 744 MSG_CREATE_SESSION, mCurMethod, 745 new MethodCallback(mCurMethod))); 746 } 747 // Return to client, and we will get back with it when 748 // we have had a session made for it. 749 return new InputBindResult(null, mCurId, mCurSeq); 750 } else if (SystemClock.uptimeMillis() 751 < (mLastBindTime+TIME_TO_RECONNECT)) { 752 // In this case we have connected to the service, but 753 // don't yet have its interface. If it hasn't been too 754 // long since we did the connection, we'll return to 755 // the client and wait to get the service interface so 756 // we can report back. If it has been too long, we want 757 // to fall through so we can try a disconnect/reconnect 758 // to see if we can get back in touch with the service. 759 return new InputBindResult(null, mCurId, mCurSeq); 760 } else { 761 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 762 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0); 763 } 764 } 765 } 766 767 return startInputInnerLocked(); 768 } 769 770 InputBindResult startInputInnerLocked() { 771 if (mCurMethodId == null) { 772 return mNoBinding; 773 } 774 775 if (!mSystemReady) { 776 // If the system is not yet ready, we shouldn't be running third 777 // party code. 778 return new InputBindResult(null, mCurMethodId, mCurSeq); 779 } 780 781 InputMethodInfo info = mMethodMap.get(mCurMethodId); 782 if (info == null) { 783 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 784 } 785 786 unbindCurrentMethodLocked(false); 787 788 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); 789 mCurIntent.setComponent(info.getComponent()); 790 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 791 com.android.internal.R.string.input_method_binding_label); 792 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 793 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); 794 if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) { 795 mLastBindTime = SystemClock.uptimeMillis(); 796 mHaveConnection = true; 797 mCurId = info.getId(); 798 mCurToken = new Binder(); 799 try { 800 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); 801 mIWindowManager.addWindowToken(mCurToken, 802 WindowManager.LayoutParams.TYPE_INPUT_METHOD); 803 } catch (RemoteException e) { 804 } 805 return new InputBindResult(null, mCurId, mCurSeq); 806 } else { 807 mCurIntent = null; 808 Slog.w(TAG, "Failure connecting to input method service: " 809 + mCurIntent); 810 } 811 return null; 812 } 813 814 public InputBindResult startInput(IInputMethodClient client, 815 IInputContext inputContext, EditorInfo attribute, 816 boolean initial, boolean needResult) { 817 synchronized (mMethodMap) { 818 final long ident = Binder.clearCallingIdentity(); 819 try { 820 return startInputLocked(client, inputContext, attribute, 821 initial, needResult); 822 } finally { 823 Binder.restoreCallingIdentity(ident); 824 } 825 } 826 } 827 828 public void finishInput(IInputMethodClient client) { 829 } 830 831 public void onServiceConnected(ComponentName name, IBinder service) { 832 synchronized (mMethodMap) { 833 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 834 mCurMethod = IInputMethod.Stub.asInterface(service); 835 if (mCurToken == null) { 836 Slog.w(TAG, "Service connected without a token!"); 837 unbindCurrentMethodLocked(false); 838 return; 839 } 840 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 841 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 842 MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); 843 if (mCurClient != null) { 844 if (DEBUG) Slog.v(TAG, "Creating first session while with client " 845 + mCurClient); 846 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 847 MSG_CREATE_SESSION, mCurMethod, 848 new MethodCallback(mCurMethod))); 849 } 850 } 851 } 852 } 853 854 void onSessionCreated(IInputMethod method, IInputMethodSession session) { 855 synchronized (mMethodMap) { 856 if (mCurMethod != null && method != null 857 && mCurMethod.asBinder() == method.asBinder()) { 858 if (mCurClient != null) { 859 mCurClient.curSession = new SessionState(mCurClient, 860 method, session); 861 mCurClient.sessionRequested = false; 862 InputBindResult res = attachNewInputLocked(true, true); 863 if (res.method != null) { 864 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 865 MSG_BIND_METHOD, mCurClient.client, res)); 866 } 867 } 868 } 869 } 870 } 871 872 void unbindCurrentMethodLocked(boolean reportToClient) { 873 if (mHaveConnection) { 874 mContext.unbindService(this); 875 mHaveConnection = false; 876 } 877 878 if (mCurToken != null) { 879 try { 880 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken); 881 mIWindowManager.removeWindowToken(mCurToken); 882 } catch (RemoteException e) { 883 } 884 mCurToken = null; 885 } 886 887 mCurId = null; 888 clearCurMethodLocked(); 889 890 if (reportToClient && mCurClient != null) { 891 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 892 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 893 } 894 } 895 896 private void finishSession(SessionState sessionState) { 897 if (sessionState != null && sessionState.session != null) { 898 try { 899 sessionState.session.finishSession(); 900 } catch (RemoteException e) { 901 Slog.w(TAG, "Session failed to close due to remote exception", e); 902 } 903 } 904 } 905 906 void clearCurMethodLocked() { 907 if (mCurMethod != null) { 908 for (ClientState cs : mClients.values()) { 909 cs.sessionRequested = false; 910 finishSession(cs.curSession); 911 cs.curSession = null; 912 } 913 914 finishSession(mEnabledSession); 915 mEnabledSession = null; 916 mCurMethod = null; 917 } 918 mStatusBar.setIconVisibility("ime", false); 919 } 920 921 public void onServiceDisconnected(ComponentName name) { 922 synchronized (mMethodMap) { 923 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 924 + " mCurIntent=" + mCurIntent); 925 if (mCurMethod != null && mCurIntent != null 926 && name.equals(mCurIntent.getComponent())) { 927 clearCurMethodLocked(); 928 // We consider this to be a new bind attempt, since the system 929 // should now try to restart the service for us. 930 mLastBindTime = SystemClock.uptimeMillis(); 931 mShowRequested = mInputShown; 932 mInputShown = false; 933 if (mCurClient != null) { 934 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 935 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 936 } 937 } 938 } 939 } 940 941 public void updateStatusIcon(IBinder token, String packageName, int iconId) { 942 int uid = Binder.getCallingUid(); 943 long ident = Binder.clearCallingIdentity(); 944 try { 945 if (token == null || mCurToken != token) { 946 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); 947 return; 948 } 949 950 synchronized (mMethodMap) { 951 if (iconId == 0) { 952 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 953 mStatusBar.setIconVisibility("ime", false); 954 } else if (packageName != null) { 955 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 956 mStatusBar.setIcon("ime", packageName, iconId, 0); 957 mStatusBar.setIconVisibility("ime", true); 958 } 959 } 960 } finally { 961 Binder.restoreCallingIdentity(ident); 962 } 963 } 964 965 public void setIMEButtonVisible(IBinder token, boolean visible) { 966 int uid = Binder.getCallingUid(); 967 long ident = Binder.clearCallingIdentity(); 968 try { 969 if (token == null || mCurToken != token) { 970 Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token); 971 return; 972 } 973 974 synchronized (mMethodMap) { 975 mStatusBar.setIMEButtonVisible(token, visible); 976 } 977 } finally { 978 Binder.restoreCallingIdentity(ident); 979 } 980 } 981 982 void updateFromSettingsLocked() { 983 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 984 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 985 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 986 // enabled. 987 String id = Settings.Secure.getString(mContext.getContentResolver(), 988 Settings.Secure.DEFAULT_INPUT_METHOD); 989 // There is no input method selected, try to choose new applicable input method. 990 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 991 id = Settings.Secure.getString(mContext.getContentResolver(), 992 Settings.Secure.DEFAULT_INPUT_METHOD); 993 } 994 if (!TextUtils.isEmpty(id)) { 995 try { 996 setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id)); 997 } catch (IllegalArgumentException e) { 998 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 999 mCurMethodId = null; 1000 unbindCurrentMethodLocked(true); 1001 } 1002 mShortcutInputMethodsAndSubtypes.clear(); 1003 } else { 1004 // There is no longer an input method set, so stop any current one. 1005 mCurMethodId = null; 1006 unbindCurrentMethodLocked(true); 1007 } 1008 } 1009 1010 /* package */ void setInputMethodLocked(String id, int subtypeId) { 1011 InputMethodInfo info = mMethodMap.get(id); 1012 if (info == null) { 1013 throw new IllegalArgumentException("Unknown id: " + id); 1014 } 1015 1016 if (id.equals(mCurMethodId)) { 1017 ArrayList<InputMethodSubtype> subtypes = info.getSubtypes(); 1018 InputMethodSubtype subtype = null; 1019 if (subtypeId >= 0 && subtypeId < subtypes.size()) { 1020 subtype = subtypes.get(subtypeId); 1021 } 1022 if (subtype != mCurrentSubtype) { 1023 synchronized (mMethodMap) { 1024 if (subtype != null) { 1025 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 1026 } 1027 if (mCurMethod != null) { 1028 try { 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( 1804 com.android.internal.R.string.configure_input_methods, 1805 new DialogInterface.OnClickListener() { 1806 public void onClick(DialogInterface dialog, int whichButton) { 1807 showConfigureInputMethods(); 1808 } 1809 }); 1810 } 1811 mSwitchingDialog = mDialogBuilder.create(); 1812 mSwitchingDialog.getWindow().setType( 1813 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1814 mSwitchingDialog.show(); 1815 } 1816 } 1817 1818 void hideInputMethodMenu() { 1819 synchronized (mMethodMap) { 1820 hideInputMethodMenuLocked(); 1821 } 1822 } 1823 1824 void hideInputMethodMenuLocked() { 1825 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 1826 1827 if (mSwitchingDialog != null) { 1828 mSwitchingDialog.dismiss(); 1829 mSwitchingDialog = null; 1830 } 1831 1832 mDialogBuilder = null; 1833 mItems = null; 1834 mIms = null; 1835 } 1836 1837 // ---------------------------------------------------------------------- 1838 1839 public boolean setInputMethodEnabled(String id, boolean enabled) { 1840 synchronized (mMethodMap) { 1841 if (mContext.checkCallingOrSelfPermission( 1842 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1843 != PackageManager.PERMISSION_GRANTED) { 1844 throw new SecurityException( 1845 "Requires permission " 1846 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1847 } 1848 1849 long ident = Binder.clearCallingIdentity(); 1850 try { 1851 return setInputMethodEnabledLocked(id, enabled); 1852 } finally { 1853 Binder.restoreCallingIdentity(ident); 1854 } 1855 } 1856 } 1857 1858 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 1859 // Make sure this is a valid input method. 1860 InputMethodInfo imm = mMethodMap.get(id); 1861 if (imm == null) { 1862 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1863 } 1864 1865 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 1866 .getEnabledInputMethodsAndSubtypeListLocked(); 1867 1868 if (enabled) { 1869 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 1870 if (pair.first.equals(id)) { 1871 // We are enabling this input method, but it is already enabled. 1872 // Nothing to do. The previous state was enabled. 1873 return true; 1874 } 1875 } 1876 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 1877 // Previous state was disabled. 1878 return false; 1879 } else { 1880 StringBuilder builder = new StringBuilder(); 1881 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 1882 builder, enabledInputMethodsList, id)) { 1883 // Disabled input method is currently selected, switch to another one. 1884 String selId = Settings.Secure.getString(mContext.getContentResolver(), 1885 Settings.Secure.DEFAULT_INPUT_METHOD); 1886 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 1887 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 1888 resetSelectedInputMethodAndSubtypeLocked(""); 1889 } 1890 // Previous state was enabled. 1891 return true; 1892 } else { 1893 // We are disabling the input method but it is already disabled. 1894 // Nothing to do. The previous state was disabled. 1895 return false; 1896 } 1897 } 1898 } 1899 1900 private void saveCurrentInputMethodAndSubtypeToHistory() { 1901 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1902 if (mCurrentSubtype != null) { 1903 subtypeId = String.valueOf(mCurrentSubtype.hashCode()); 1904 } 1905 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); 1906 } 1907 1908 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 1909 boolean setSubtypeOnly) { 1910 // Update the history of InputMethod and Subtype 1911 saveCurrentInputMethodAndSubtypeToHistory(); 1912 1913 // Set Subtype here 1914 if (imi == null || subtypeId < 0) { 1915 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 1916 mCurrentSubtype = null; 1917 } else { 1918 final ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); 1919 if (subtypeId < subtypes.size()) { 1920 mSettings.putSelectedSubtype(subtypes.get(subtypeId).hashCode()); 1921 mCurrentSubtype = subtypes.get(subtypeId); 1922 } else { 1923 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 1924 mCurrentSubtype = null; 1925 } 1926 } 1927 1928 if (!setSubtypeOnly) { 1929 // Set InputMethod here 1930 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 1931 } 1932 } 1933 1934 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 1935 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 1936 int lastSubtypeId = NOT_A_SUBTYPE_ID; 1937 // newDefaultIme is empty when there is no candidate for the selected IME. 1938 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 1939 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 1940 if (subtypeHashCode != null) { 1941 try { 1942 lastSubtypeId = getSubtypeIdFromHashCode( 1943 imi, Integer.valueOf(subtypeHashCode)); 1944 } catch (NumberFormatException e) { 1945 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 1946 } 1947 } 1948 } 1949 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 1950 } 1951 1952 private int getSelectedInputMethodSubtypeId(String id) { 1953 InputMethodInfo imi = mMethodMap.get(id); 1954 if (imi == null) { 1955 return NOT_A_SUBTYPE_ID; 1956 } 1957 int subtypeId; 1958 try { 1959 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), 1960 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); 1961 } catch (SettingNotFoundException e) { 1962 return NOT_A_SUBTYPE_ID; 1963 } 1964 return getSubtypeIdFromHashCode(imi, subtypeId); 1965 } 1966 1967 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 1968 if (imi != null) { 1969 ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes(); 1970 for (int i = 0; i < subtypes.size(); ++i) { 1971 InputMethodSubtype ims = subtypes.get(i); 1972 if (subtypeHashCode == ims.hashCode()) { 1973 return i; 1974 } 1975 } 1976 } 1977 return NOT_A_SUBTYPE_ID; 1978 } 1979 1980 private ArrayList<InputMethodSubtype> getApplicableSubtypesLocked( 1981 List<InputMethodSubtype> subtypes) { 1982 final String systemLocale = mRes.getConfiguration().locale.toString(); 1983 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); 1984 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = 1985 new HashMap<String, InputMethodSubtype>(); 1986 final int N = subtypes.size(); 1987 boolean containsKeyboardSubtype = false; 1988 for (int i = 0; i < N; ++i) { 1989 InputMethodSubtype subtype = subtypes.get(i); 1990 final String locale = subtype.getLocale(); 1991 final String mode = subtype.getMode(); 1992 // When system locale starts with subtype's locale, that subtype will be applicable 1993 // for system locale 1994 // For instance, it's clearly applicable for cases like system locale = en_US and 1995 // subtype = en, but it is not necessarily considered applicable for cases like system 1996 // locale = en and subtype = en_US. 1997 // We just call systemLocale.startsWith(locale) in this function because there is no 1998 // need to find applicable subtypes aggressively unlike 1999 // findLastResortApplicableSubtypeLocked. 2000 if (systemLocale.startsWith(locale)) { 2001 InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); 2002 // If more applicable subtypes are contained, skip. 2003 if (applicableSubtype != null 2004 && systemLocale.equals(applicableSubtype.getLocale())) continue; 2005 applicableModeAndSubtypesMap.put(mode, subtype); 2006 if (!containsKeyboardSubtype 2007 && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { 2008 containsKeyboardSubtype = true; 2009 } 2010 } 2011 } 2012 ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( 2013 applicableModeAndSubtypesMap.values()); 2014 if (!containsKeyboardSubtype) { 2015 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 2016 subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 2017 if (lastResortKeyboardSubtype != null) { 2018 applicableSubtypes.add(lastResortKeyboardSubtype); 2019 } 2020 } 2021 return applicableSubtypes; 2022 } 2023 2024 /** 2025 * If there are no selected subtypes, tries finding the most applicable one according to the 2026 * given locale. 2027 * @param subtypes this function will search the most applicable subtype in subtypes 2028 * @param mode subtypes will be filtered by mode 2029 * @param locale subtypes will be filtered by locale 2030 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 2031 * it will return the first subtype matched with mode 2032 * @return the most applicable subtypeId 2033 */ 2034 private InputMethodSubtype findLastResortApplicableSubtypeLocked( 2035 List<InputMethodSubtype> subtypes, String mode, String locale, 2036 boolean canIgnoreLocaleAsLastResort) { 2037 if (subtypes == null || subtypes.size() == 0) { 2038 return null; 2039 } 2040 if (TextUtils.isEmpty(locale)) { 2041 locale = mRes.getConfiguration().locale.toString(); 2042 } 2043 final String language = locale.substring(0, 2); 2044 boolean partialMatchFound = false; 2045 InputMethodSubtype applicableSubtype = null; 2046 InputMethodSubtype firstMatchedModeSubtype = null; 2047 final int N = subtypes.size(); 2048 for (int i = 0; i < N; ++i) { 2049 InputMethodSubtype subtype = subtypes.get(i); 2050 final String subtypeLocale = subtype.getLocale(); 2051 // An applicable subtype should match "mode". 2052 if (subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 2053 if (firstMatchedModeSubtype == null) { 2054 firstMatchedModeSubtype = subtype; 2055 } 2056 if (locale.equals(subtypeLocale)) { 2057 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 2058 applicableSubtype = subtype; 2059 break; 2060 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { 2061 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 2062 applicableSubtype = subtype; 2063 partialMatchFound = true; 2064 } 2065 } 2066 } 2067 2068 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 2069 return firstMatchedModeSubtype; 2070 } 2071 2072 // The first subtype applicable to the system locale will be defined as the most applicable 2073 // subtype. 2074 if (DEBUG) { 2075 if (applicableSubtype != null) { 2076 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 2077 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 2078 } 2079 } 2080 return applicableSubtype; 2081 } 2082 2083 // If there are no selected shortcuts, tries finding the most applicable ones. 2084 private Pair<InputMethodInfo, InputMethodSubtype> 2085 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2086 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2087 InputMethodInfo mostApplicableIMI = null; 2088 InputMethodSubtype mostApplicableSubtype = null; 2089 boolean foundInSystemIME = false; 2090 2091 // Search applicable subtype for each InputMethodInfo 2092 for (InputMethodInfo imi: imis) { 2093 final String imiId = imi.getId(); 2094 if (foundInSystemIME && !imiId.equals(mCurMethodId)) { 2095 continue; 2096 } 2097 InputMethodSubtype subtype = null; 2098 final List<InputMethodSubtype> explicitlyEnabledSubtypes = 2099 mSettings.getEnabledInputMethodSubtypeListLocked(imi); 2100 // 1. Search by the current subtype's locale from explicitlyEnabledSubtypes. 2101 if (mCurrentSubtype != null) { 2102 subtype = findLastResortApplicableSubtypeLocked( 2103 explicitlyEnabledSubtypes, mode, mCurrentSubtype.getLocale(), false); 2104 } 2105 // 2. Search by the system locale from explicitlyEnabledSubtypes. 2106 // 3. Search the first enabled subtype matched with mode from explicitlyEnabledSubtypes. 2107 if (subtype == null) { 2108 subtype = findLastResortApplicableSubtypeLocked( 2109 explicitlyEnabledSubtypes, mode, null, true); 2110 } 2111 // 4. Search by the current subtype's locale from all subtypes. 2112 if (subtype == null && mCurrentSubtype != null) { 2113 subtype = findLastResortApplicableSubtypeLocked( 2114 imi.getSubtypes(), mode, mCurrentSubtype.getLocale(), false); 2115 } 2116 // 5. Search by the system locale from all subtypes. 2117 // 6. Search the first enabled subtype matched with mode from all subtypes. 2118 if (subtype == null) { 2119 subtype = findLastResortApplicableSubtypeLocked( 2120 imi.getSubtypes(), mode, null, true); 2121 } 2122 if (subtype != null) { 2123 if (imiId.equals(mCurMethodId)) { 2124 // The current input method is the most applicable IME. 2125 mostApplicableIMI = imi; 2126 mostApplicableSubtype = subtype; 2127 break; 2128 } else if (!foundInSystemIME) { 2129 // The system input method is 2nd applicable IME. 2130 mostApplicableIMI = imi; 2131 mostApplicableSubtype = subtype; 2132 if ((imi.getServiceInfo().applicationInfo.flags 2133 & ApplicationInfo.FLAG_SYSTEM) != 0) { 2134 foundInSystemIME = true; 2135 } 2136 } 2137 } 2138 } 2139 if (DEBUG) { 2140 if (mostApplicableIMI != null) { 2141 Slog.w(TAG, "Most applicable shortcut input method was:" 2142 + mostApplicableIMI.getId()); 2143 if (mostApplicableSubtype != null) { 2144 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 2145 + "," + mostApplicableSubtype.getMode() + "," 2146 + mostApplicableSubtype.getLocale()); 2147 } 2148 } 2149 } 2150 if (mostApplicableIMI != null) { 2151 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 2152 mostApplicableSubtype); 2153 } else { 2154 return null; 2155 } 2156 } 2157 2158 /** 2159 * @return Return the current subtype of this input method. 2160 */ 2161 public InputMethodSubtype getCurrentInputMethodSubtype() { 2162 boolean subtypeIsSelected = false; 2163 try { 2164 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), 2165 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; 2166 } catch (SettingNotFoundException e) { 2167 } 2168 synchronized (mMethodMap) { 2169 if (!subtypeIsSelected || mCurrentSubtype == null) { 2170 String lastInputMethodId = Settings.Secure.getString( 2171 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2172 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 2173 if (subtypeId == NOT_A_SUBTYPE_ID) { 2174 InputMethodInfo imi = mMethodMap.get(lastInputMethodId); 2175 if (imi != null) { 2176 // If there are no selected subtypes, the framework will try to find 2177 // the most applicable subtype from all subtypes whose mode is 2178 // SUBTYPE_MODE_KEYBOARD. This is an exceptional case, so we will hardcode 2179 // the mode. 2180 mCurrentSubtype = findLastResortApplicableSubtypeLocked(imi.getSubtypes(), 2181 SUBTYPE_MODE_KEYBOARD, null, true); 2182 } 2183 } else { 2184 mCurrentSubtype = 2185 mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId); 2186 } 2187 } 2188 return mCurrentSubtype; 2189 } 2190 } 2191 2192 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 2193 InputMethodSubtype subtype) { 2194 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 2195 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 2196 } else { 2197 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2198 subtypes.add(subtype); 2199 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 2200 } 2201 } 2202 2203 // TODO: We should change the return type from List to List<Parcelable> 2204 public List getShortcutInputMethodsAndSubtypes() { 2205 synchronized (mMethodMap) { 2206 ArrayList<Object> ret = new ArrayList<Object>(); 2207 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 2208 // If there are no selected shortcut subtypes, the framework will try to find 2209 // the most applicable subtype from all subtypes whose mode is 2210 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 2211 Pair<InputMethodInfo, InputMethodSubtype> info = 2212 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 2213 SUBTYPE_MODE_VOICE); 2214 if (info != null) { 2215 ret.add(info.first); 2216 ret.add(info.second); 2217 } 2218 return ret; 2219 } 2220 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 2221 ret.add(imi); 2222 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 2223 ret.add(subtype); 2224 } 2225 } 2226 return ret; 2227 } 2228 } 2229 2230 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 2231 synchronized (mMethodMap) { 2232 if (subtype != null && mCurMethodId != null) { 2233 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2234 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); 2235 if (subtypeId != NOT_A_SUBTYPE_ID) { 2236 setInputMethodLocked(mCurMethodId, subtypeId); 2237 return true; 2238 } 2239 } 2240 return false; 2241 } 2242 } 2243 2244 /** 2245 * Utility class for putting and getting settings for InputMethod 2246 * TODO: Move all putters and getters of settings to this class. 2247 */ 2248 private static class InputMethodSettings { 2249 // The string for enabled input method is saved as follows: 2250 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 2251 private static final char INPUT_METHOD_SEPARATER = ':'; 2252 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 2253 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 2254 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 2255 2256 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 2257 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 2258 2259 private final ContentResolver mResolver; 2260 private final HashMap<String, InputMethodInfo> mMethodMap; 2261 private final ArrayList<InputMethodInfo> mMethodList; 2262 2263 private String mEnabledInputMethodsStrCache; 2264 2265 private static void buildEnabledInputMethodsSettingString( 2266 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 2267 String id = pair.first; 2268 ArrayList<String> subtypes = pair.second; 2269 builder.append(id); 2270 // Inputmethod and subtypes are saved in the settings as follows: 2271 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 2272 for (String subtypeId: subtypes) { 2273 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 2274 } 2275 } 2276 2277 public InputMethodSettings( 2278 ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap, 2279 ArrayList<InputMethodInfo> methodList) { 2280 mResolver = resolver; 2281 mMethodMap = methodMap; 2282 mMethodList = methodList; 2283 } 2284 2285 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 2286 return createEnabledInputMethodListLocked( 2287 getEnabledInputMethodsAndSubtypeListLocked()); 2288 } 2289 2290 public List<Pair<InputMethodInfo, ArrayList<String>>> 2291 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 2292 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 2293 getEnabledInputMethodsAndSubtypeListLocked()); 2294 } 2295 2296 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 2297 InputMethodInfo imi) { 2298 List<Pair<String, ArrayList<String>>> imsList = 2299 getEnabledInputMethodsAndSubtypeListLocked(); 2300 ArrayList<InputMethodSubtype> enabledSubtypes = 2301 new ArrayList<InputMethodSubtype>(); 2302 if (imi != null) { 2303 for (Pair<String, ArrayList<String>> imsPair : imsList) { 2304 InputMethodInfo info = mMethodMap.get(imsPair.first); 2305 if (info != null && info.getId().equals(imi.getId())) { 2306 ArrayList<InputMethodSubtype> subtypes = info.getSubtypes(); 2307 for (InputMethodSubtype ims: subtypes) { 2308 for (String s: imsPair.second) { 2309 if (String.valueOf(ims.hashCode()).equals(s)) { 2310 enabledSubtypes.add(ims); 2311 } 2312 } 2313 } 2314 break; 2315 } 2316 } 2317 } 2318 return enabledSubtypes; 2319 } 2320 2321 // At the initial boot, the settings for input methods are not set, 2322 // so we need to enable IME in that case. 2323 public void enableAllIMEsIfThereIsNoEnabledIME() { 2324 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 2325 StringBuilder sb = new StringBuilder(); 2326 final int N = mMethodList.size(); 2327 for (int i = 0; i < N; i++) { 2328 InputMethodInfo imi = mMethodList.get(i); 2329 Slog.i(TAG, "Adding: " + imi.getId()); 2330 if (i > 0) sb.append(':'); 2331 sb.append(imi.getId()); 2332 } 2333 putEnabledInputMethodsStr(sb.toString()); 2334 } 2335 } 2336 2337 public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 2338 ArrayList<Pair<String, ArrayList<String>>> imsList 2339 = new ArrayList<Pair<String, ArrayList<String>>>(); 2340 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 2341 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 2342 return imsList; 2343 } 2344 mInputMethodSplitter.setString(enabledInputMethodsStr); 2345 while (mInputMethodSplitter.hasNext()) { 2346 String nextImsStr = mInputMethodSplitter.next(); 2347 mSubtypeSplitter.setString(nextImsStr); 2348 if (mSubtypeSplitter.hasNext()) { 2349 ArrayList<String> subtypeHashes = new ArrayList<String>(); 2350 // The first element is ime id. 2351 String imeId = mSubtypeSplitter.next(); 2352 while (mSubtypeSplitter.hasNext()) { 2353 subtypeHashes.add(mSubtypeSplitter.next()); 2354 } 2355 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 2356 } 2357 } 2358 return imsList; 2359 } 2360 2361 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 2362 if (reloadInputMethodStr) { 2363 getEnabledInputMethodsStr(); 2364 } 2365 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 2366 // Add in the newly enabled input method. 2367 putEnabledInputMethodsStr(id); 2368 } else { 2369 putEnabledInputMethodsStr( 2370 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 2371 } 2372 } 2373 2374 /** 2375 * Build and put a string of EnabledInputMethods with removing specified Id. 2376 * @return the specified id was removed or not. 2377 */ 2378 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2379 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 2380 boolean isRemoved = false; 2381 boolean needsAppendSeparator = false; 2382 for (Pair<String, ArrayList<String>> ims: imsList) { 2383 String curId = ims.first; 2384 if (curId.equals(id)) { 2385 // We are disabling this input method, and it is 2386 // currently enabled. Skip it to remove from the 2387 // new list. 2388 isRemoved = true; 2389 } else { 2390 if (needsAppendSeparator) { 2391 builder.append(INPUT_METHOD_SEPARATER); 2392 } else { 2393 needsAppendSeparator = true; 2394 } 2395 buildEnabledInputMethodsSettingString(builder, ims); 2396 } 2397 } 2398 if (isRemoved) { 2399 // Update the setting with the new list of input methods. 2400 putEnabledInputMethodsStr(builder.toString()); 2401 } 2402 return isRemoved; 2403 } 2404 2405 private List<InputMethodInfo> createEnabledInputMethodListLocked( 2406 List<Pair<String, ArrayList<String>>> imsList) { 2407 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 2408 for (Pair<String, ArrayList<String>> ims: imsList) { 2409 InputMethodInfo info = mMethodMap.get(ims.first); 2410 if (info != null) { 2411 res.add(info); 2412 } 2413 } 2414 return res; 2415 } 2416 2417 private List<Pair<InputMethodInfo, ArrayList<String>>> 2418 createEnabledInputMethodAndSubtypeHashCodeListLocked( 2419 List<Pair<String, ArrayList<String>>> imsList) { 2420 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 2421 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 2422 for (Pair<String, ArrayList<String>> ims : imsList) { 2423 InputMethodInfo info = mMethodMap.get(ims.first); 2424 if (info != null) { 2425 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 2426 } 2427 } 2428 return res; 2429 } 2430 2431 private void putEnabledInputMethodsStr(String str) { 2432 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); 2433 mEnabledInputMethodsStrCache = str; 2434 } 2435 2436 private String getEnabledInputMethodsStr() { 2437 mEnabledInputMethodsStrCache = Settings.Secure.getString( 2438 mResolver, Settings.Secure.ENABLED_INPUT_METHODS); 2439 if (DEBUG) { 2440 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); 2441 } 2442 return mEnabledInputMethodsStrCache; 2443 } 2444 2445 private void saveSubtypeHistory( 2446 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 2447 StringBuilder builder = new StringBuilder(); 2448 boolean isImeAdded = false; 2449 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 2450 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2451 newSubtypeId); 2452 isImeAdded = true; 2453 } 2454 for (Pair<String, String> ime: savedImes) { 2455 String imeId = ime.first; 2456 String subtypeId = ime.second; 2457 if (TextUtils.isEmpty(subtypeId)) { 2458 subtypeId = NOT_A_SUBTYPE_ID_STR; 2459 } 2460 if (isImeAdded) { 2461 builder.append(INPUT_METHOD_SEPARATER); 2462 } else { 2463 isImeAdded = true; 2464 } 2465 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2466 subtypeId); 2467 } 2468 // Remove the last INPUT_METHOD_SEPARATER 2469 putSubtypeHistoryStr(builder.toString()); 2470 } 2471 2472 public void addSubtypeToHistory(String imeId, String subtypeId) { 2473 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2474 for (Pair<String, String> ime: subtypeHistory) { 2475 if (ime.first.equals(imeId)) { 2476 if (DEBUG) { 2477 Slog.v(TAG, "Subtype found in the history: " + imeId 2478 + ime.second); 2479 } 2480 // We should break here 2481 subtypeHistory.remove(ime); 2482 break; 2483 } 2484 } 2485 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 2486 } 2487 2488 private void putSubtypeHistoryStr(String str) { 2489 if (DEBUG) { 2490 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 2491 } 2492 Settings.Secure.putString( 2493 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 2494 } 2495 2496 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 2497 // Gets the first one from the history 2498 return getLastSubtypeForInputMethodLockedInternal(null); 2499 } 2500 2501 public String getLastSubtypeForInputMethodLocked(String imeId) { 2502 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 2503 if (ime != null) { 2504 return ime.second; 2505 } else { 2506 return null; 2507 } 2508 } 2509 2510 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 2511 List<Pair<String, ArrayList<String>>> enabledImes = 2512 getEnabledInputMethodsAndSubtypeListLocked(); 2513 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2514 for (Pair<String, String> imeAndSubtype: subtypeHistory) { 2515 final String imeInTheHistory = imeAndSubtype.first; 2516 // If imeId is empty, returns the first IME and subtype in the history 2517 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 2518 final String subtypeInTheHistory = imeAndSubtype.second; 2519 final String subtypeHashCode = getEnabledSubtypeForInputMethodAndSubtypeLocked( 2520 enabledImes, imeInTheHistory, subtypeInTheHistory); 2521 if (!TextUtils.isEmpty(subtypeHashCode)) { 2522 if (DEBUG) { 2523 Slog.d(TAG, "Enabled subtype found in the history:" + subtypeHashCode); 2524 } 2525 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 2526 } 2527 } 2528 } 2529 if (DEBUG) { 2530 Slog.d(TAG, "No enabled IME found in the history"); 2531 } 2532 return null; 2533 } 2534 2535 private String getEnabledSubtypeForInputMethodAndSubtypeLocked(List<Pair<String, 2536 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 2537 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 2538 if (enabledIme.first.equals(imeId)) { 2539 for (String s: enabledIme.second) { 2540 if (s.equals(subtypeHashCode)) { 2541 // If both imeId and subtypeId are enabled, return subtypeId. 2542 return s; 2543 } 2544 } 2545 // If imeId was enabled but subtypeId was disabled. 2546 return NOT_A_SUBTYPE_ID_STR; 2547 } 2548 } 2549 // If both imeId and subtypeId are disabled, return null 2550 return null; 2551 } 2552 2553 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 2554 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 2555 final String subtypeHistoryStr = getSubtypeHistoryStr(); 2556 if (TextUtils.isEmpty(subtypeHistoryStr)) { 2557 return imsList; 2558 } 2559 mInputMethodSplitter.setString(subtypeHistoryStr); 2560 while (mInputMethodSplitter.hasNext()) { 2561 String nextImsStr = mInputMethodSplitter.next(); 2562 mSubtypeSplitter.setString(nextImsStr); 2563 if (mSubtypeSplitter.hasNext()) { 2564 String subtypeId = NOT_A_SUBTYPE_ID_STR; 2565 // The first element is ime id. 2566 String imeId = mSubtypeSplitter.next(); 2567 while (mSubtypeSplitter.hasNext()) { 2568 subtypeId = mSubtypeSplitter.next(); 2569 break; 2570 } 2571 imsList.add(new Pair<String, String>(imeId, subtypeId)); 2572 } 2573 } 2574 return imsList; 2575 } 2576 2577 private String getSubtypeHistoryStr() { 2578 if (DEBUG) { 2579 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( 2580 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); 2581 } 2582 return Settings.Secure.getString( 2583 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); 2584 } 2585 2586 public void putSelectedInputMethod(String imeId) { 2587 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 2588 } 2589 2590 public void putSelectedSubtype(int subtypeId) { 2591 Settings.Secure.putInt( 2592 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 2593 } 2594 } 2595 2596 // ---------------------------------------------------------------------- 2597 2598 @Override 2599 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2600 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2601 != PackageManager.PERMISSION_GRANTED) { 2602 2603 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 2604 + Binder.getCallingPid() 2605 + ", uid=" + Binder.getCallingUid()); 2606 return; 2607 } 2608 2609 IInputMethod method; 2610 ClientState client; 2611 2612 final Printer p = new PrintWriterPrinter(pw); 2613 2614 synchronized (mMethodMap) { 2615 p.println("Current Input Method Manager state:"); 2616 int N = mMethodList.size(); 2617 p.println(" Input Methods:"); 2618 for (int i=0; i<N; i++) { 2619 InputMethodInfo info = mMethodList.get(i); 2620 p.println(" InputMethod #" + i + ":"); 2621 info.dump(p, " "); 2622 } 2623 p.println(" Clients:"); 2624 for (ClientState ci : mClients.values()) { 2625 p.println(" Client " + ci + ":"); 2626 p.println(" client=" + ci.client); 2627 p.println(" inputContext=" + ci.inputContext); 2628 p.println(" sessionRequested=" + ci.sessionRequested); 2629 p.println(" curSession=" + ci.curSession); 2630 } 2631 p.println(" mCurMethodId=" + mCurMethodId); 2632 client = mCurClient; 2633 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 2634 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 2635 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 2636 + " mBoundToMethod=" + mBoundToMethod); 2637 p.println(" mCurToken=" + mCurToken); 2638 p.println(" mCurIntent=" + mCurIntent); 2639 method = mCurMethod; 2640 p.println(" mCurMethod=" + mCurMethod); 2641 p.println(" mEnabledSession=" + mEnabledSession); 2642 p.println(" mShowRequested=" + mShowRequested 2643 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 2644 + " mShowForced=" + mShowForced 2645 + " mInputShown=" + mInputShown); 2646 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 2647 } 2648 2649 p.println(" "); 2650 if (client != null) { 2651 pw.flush(); 2652 try { 2653 client.client.asBinder().dump(fd, args); 2654 } catch (RemoteException e) { 2655 p.println("Input method client dead: " + e); 2656 } 2657 } else { 2658 p.println("No input method client."); 2659 } 2660 2661 p.println(" "); 2662 if (method != null) { 2663 pw.flush(); 2664 try { 2665 method.asBinder().dump(fd, args); 2666 } catch (RemoteException e) { 2667 p.println("Input method service dead: " + e); 2668 } 2669 } else { 2670 p.println("No input method service."); 2671 } 2672 } 2673} 2674