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