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