InputMethodManagerService.java revision 761eb3762f3602dd1859905ee4ba80f0ef6aec56
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.util.ArrayList; 86import java.util.Comparator; 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 TreeMap<InputMethodInfo, List<InputMethodSubtype>> sortedImmis = 1754 new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( 1755 new Comparator<InputMethodInfo>() { 1756 @Override 1757 public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { 1758 if (imi2 == null) return 0; 1759 if (imi1 == null) return 1; 1760 if (pm == null) { 1761 return imi1.getId().compareTo(imi2.getId()); 1762 } 1763 CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId(); 1764 CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId(); 1765 return imiId1.toString().compareTo(imiId2.toString()); 1766 } 1767 }); 1768 1769 sortedImmis.putAll(immis); 1770 1771 final ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>> imList = 1772 new ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>>(); 1773 1774 for (InputMethodInfo imi : sortedImmis.keySet()) { 1775 if (imi == null) continue; 1776 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); 1777 HashSet<String> enabledSubtypeSet = new HashSet<String>(); 1778 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { 1779 enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); 1780 } 1781 ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi); 1782 final CharSequence label = imi.loadLabel(pm); 1783 if (showSubtypes && enabledSubtypeSet.size() > 0) { 1784 final int subtypeCount = imi.getSubtypeCount(); 1785 for (int j = 0; j < subtypeCount; ++j) { 1786 InputMethodSubtype subtype = imi.getSubtypeAt(j); 1787 if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) { 1788 final CharSequence title; 1789 int nameResId = subtype.getNameResId(); 1790 String mode = subtype.getMode(); 1791 if (nameResId != 0) { 1792 title = TextUtils.concat(pm.getText(imi.getPackageName(), 1793 nameResId, imi.getServiceInfo().applicationInfo), 1794 (TextUtils.isEmpty(label) ? "" : " (" + label + ")")); 1795 } else { 1796 CharSequence language = subtype.getLocale(); 1797 // TODO: Use more friendly Title and UI 1798 title = label + "," + (mode == null ? "" : mode) + "," 1799 + (language == null ? "" : language); 1800 } 1801 imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>( 1802 title, new Pair<InputMethodInfo, Integer>(imi, j))); 1803 } 1804 } 1805 } else { 1806 imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>( 1807 label, new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID))); 1808 } 1809 } 1810 1811 final int N = imList.size(); 1812 mItems = new CharSequence[N]; 1813 for (int i = 0; i < N; ++i) { 1814 mItems[i] = imList.get(i).first; 1815 } 1816 mIms = new InputMethodInfo[N]; 1817 mSubtypeIds = new int[N]; 1818 int checkedItem = 0; 1819 for (int i = 0; i < N; ++i) { 1820 Pair<InputMethodInfo, Integer> value = imList.get(i).second; 1821 mIms[i] = value.first; 1822 mSubtypeIds[i] = value.second; 1823 if (mIms[i].getId().equals(lastInputMethodId)) { 1824 int subtypeId = mSubtypeIds[i]; 1825 if ((subtypeId == NOT_A_SUBTYPE_ID) 1826 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 1827 || (subtypeId == lastInputMethodSubtypeId)) { 1828 checkedItem = i; 1829 } 1830 } 1831 } 1832 1833 AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() { 1834 public void onClick(DialogInterface dialog, int which) { 1835 hideInputMethodMenu(); 1836 } 1837 }; 1838 1839 TypedArray a = context.obtainStyledAttributes(null, 1840 com.android.internal.R.styleable.DialogPreference, 1841 com.android.internal.R.attr.alertDialogStyle, 0); 1842 mDialogBuilder = new AlertDialog.Builder(context) 1843 .setTitle(com.android.internal.R.string.select_input_method) 1844 .setOnCancelListener(new OnCancelListener() { 1845 public void onCancel(DialogInterface dialog) { 1846 hideInputMethodMenu(); 1847 } 1848 }) 1849 .setIcon(a.getDrawable( 1850 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 1851 a.recycle(); 1852 1853 mDialogBuilder.setSingleChoiceItems(mItems, checkedItem, 1854 new AlertDialog.OnClickListener() { 1855 public void onClick(DialogInterface dialog, int which) { 1856 synchronized (mMethodMap) { 1857 if (mIms == null || mIms.length <= which 1858 || mSubtypeIds == null || mSubtypeIds.length <= which) { 1859 return; 1860 } 1861 InputMethodInfo im = mIms[which]; 1862 int subtypeId = mSubtypeIds[which]; 1863 hideInputMethodMenu(); 1864 if (im != null) { 1865 if ((subtypeId < 0) 1866 || (subtypeId >= im.getSubtypeCount())) { 1867 subtypeId = NOT_A_SUBTYPE_ID; 1868 } 1869 setInputMethodLocked(im.getId(), subtypeId); 1870 } 1871 } 1872 } 1873 }); 1874 1875 if (showSubtypes) { 1876 mDialogBuilder.setPositiveButton( 1877 com.android.internal.R.string.configure_input_methods, 1878 new DialogInterface.OnClickListener() { 1879 public void onClick(DialogInterface dialog, int whichButton) { 1880 showConfigureInputMethods(); 1881 } 1882 }); 1883 } 1884 mSwitchingDialog = mDialogBuilder.create(); 1885 mSwitchingDialog.getWindow().setType( 1886 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 1887 mSwitchingDialog.show(); 1888 } 1889 } 1890 1891 void hideInputMethodMenu() { 1892 synchronized (mMethodMap) { 1893 hideInputMethodMenuLocked(); 1894 } 1895 } 1896 1897 void hideInputMethodMenuLocked() { 1898 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 1899 1900 if (mSwitchingDialog != null) { 1901 mSwitchingDialog.dismiss(); 1902 mSwitchingDialog = null; 1903 } 1904 1905 mDialogBuilder = null; 1906 mItems = null; 1907 mIms = null; 1908 } 1909 1910 // ---------------------------------------------------------------------- 1911 1912 public boolean setInputMethodEnabled(String id, boolean enabled) { 1913 synchronized (mMethodMap) { 1914 if (mContext.checkCallingOrSelfPermission( 1915 android.Manifest.permission.WRITE_SECURE_SETTINGS) 1916 != PackageManager.PERMISSION_GRANTED) { 1917 throw new SecurityException( 1918 "Requires permission " 1919 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 1920 } 1921 1922 long ident = Binder.clearCallingIdentity(); 1923 try { 1924 return setInputMethodEnabledLocked(id, enabled); 1925 } finally { 1926 Binder.restoreCallingIdentity(ident); 1927 } 1928 } 1929 } 1930 1931 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 1932 // Make sure this is a valid input method. 1933 InputMethodInfo imm = mMethodMap.get(id); 1934 if (imm == null) { 1935 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1936 } 1937 1938 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 1939 .getEnabledInputMethodsAndSubtypeListLocked(); 1940 1941 if (enabled) { 1942 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 1943 if (pair.first.equals(id)) { 1944 // We are enabling this input method, but it is already enabled. 1945 // Nothing to do. The previous state was enabled. 1946 return true; 1947 } 1948 } 1949 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 1950 // Previous state was disabled. 1951 return false; 1952 } else { 1953 StringBuilder builder = new StringBuilder(); 1954 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 1955 builder, enabledInputMethodsList, id)) { 1956 // Disabled input method is currently selected, switch to another one. 1957 String selId = Settings.Secure.getString(mContext.getContentResolver(), 1958 Settings.Secure.DEFAULT_INPUT_METHOD); 1959 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 1960 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 1961 resetSelectedInputMethodAndSubtypeLocked(""); 1962 } 1963 // Previous state was enabled. 1964 return true; 1965 } else { 1966 // We are disabling the input method but it is already disabled. 1967 // Nothing to do. The previous state was disabled. 1968 return false; 1969 } 1970 } 1971 } 1972 1973 private boolean canAddToLastInputMethod(InputMethodSubtype subtype) { 1974 if (subtype == null) return true; 1975 return !subtype.containsExtraValueKey(SUBTYPE_EXTRAVALUE_EXCLUDE_FROM_LAST_IME); 1976 } 1977 1978 private void saveCurrentInputMethodAndSubtypeToHistory() { 1979 String subtypeId = NOT_A_SUBTYPE_ID_STR; 1980 if (mCurrentSubtype != null) { 1981 subtypeId = String.valueOf(mCurrentSubtype.hashCode()); 1982 } 1983 if (canAddToLastInputMethod(mCurrentSubtype)) { 1984 mSettings.addSubtypeToHistory(mCurMethodId, subtypeId); 1985 } 1986 } 1987 1988 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 1989 boolean setSubtypeOnly) { 1990 mOldSystemSettingsVersion = getSystemSettingsVersion(); 1991 // Update the history of InputMethod and Subtype 1992 saveCurrentInputMethodAndSubtypeToHistory(); 1993 1994 // Set Subtype here 1995 if (imi == null || subtypeId < 0) { 1996 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 1997 mCurrentSubtype = null; 1998 } else { 1999 if (subtypeId < imi.getSubtypeCount()) { 2000 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); 2001 mSettings.putSelectedSubtype(subtype.hashCode()); 2002 mCurrentSubtype = subtype; 2003 } else { 2004 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2005 mCurrentSubtype = null; 2006 } 2007 } 2008 2009 if (!setSubtypeOnly) { 2010 // Set InputMethod here 2011 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 2012 } 2013 } 2014 2015 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 2016 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 2017 int lastSubtypeId = NOT_A_SUBTYPE_ID; 2018 // newDefaultIme is empty when there is no candidate for the selected IME. 2019 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 2020 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 2021 if (subtypeHashCode != null) { 2022 try { 2023 lastSubtypeId = getSubtypeIdFromHashCode( 2024 imi, Integer.valueOf(subtypeHashCode)); 2025 } catch (NumberFormatException e) { 2026 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 2027 } 2028 } 2029 } 2030 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 2031 } 2032 2033 private int getSelectedInputMethodSubtypeId(String id) { 2034 InputMethodInfo imi = mMethodMap.get(id); 2035 if (imi == null) { 2036 return NOT_A_SUBTYPE_ID; 2037 } 2038 int subtypeId; 2039 try { 2040 subtypeId = Settings.Secure.getInt(mContext.getContentResolver(), 2041 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE); 2042 } catch (SettingNotFoundException e) { 2043 return NOT_A_SUBTYPE_ID; 2044 } 2045 return getSubtypeIdFromHashCode(imi, subtypeId); 2046 } 2047 2048 private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) { 2049 if (imi != null) { 2050 final int subtypeCount = imi.getSubtypeCount(); 2051 for (int i = 0; i < subtypeCount; ++i) { 2052 InputMethodSubtype ims = imi.getSubtypeAt(i); 2053 if (subtypeHashCode == ims.hashCode()) { 2054 return i; 2055 } 2056 } 2057 } 2058 return NOT_A_SUBTYPE_ID; 2059 } 2060 2061 private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked( 2062 Resources res, List<InputMethodSubtype> subtypes) { 2063 final String systemLocale = res.getConfiguration().locale.toString(); 2064 if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>(); 2065 HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap = 2066 new HashMap<String, InputMethodSubtype>(); 2067 final int N = subtypes.size(); 2068 boolean containsKeyboardSubtype = false; 2069 for (int i = 0; i < N; ++i) { 2070 InputMethodSubtype subtype = subtypes.get(i); 2071 final String locale = subtype.getLocale(); 2072 final String mode = subtype.getMode(); 2073 // When system locale starts with subtype's locale, that subtype will be applicable 2074 // for system locale 2075 // For instance, it's clearly applicable for cases like system locale = en_US and 2076 // subtype = en, but it is not necessarily considered applicable for cases like system 2077 // locale = en and subtype = en_US. 2078 // We just call systemLocale.startsWith(locale) in this function because there is no 2079 // need to find applicable subtypes aggressively unlike 2080 // findLastResortApplicableSubtypeLocked. 2081 if (systemLocale.startsWith(locale)) { 2082 InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode); 2083 // If more applicable subtypes are contained, skip. 2084 if (applicableSubtype != null 2085 && systemLocale.equals(applicableSubtype.getLocale())) continue; 2086 applicableModeAndSubtypesMap.put(mode, subtype); 2087 if (!containsKeyboardSubtype 2088 && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) { 2089 containsKeyboardSubtype = true; 2090 } 2091 } 2092 } 2093 ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>( 2094 applicableModeAndSubtypesMap.values()); 2095 if (!containsKeyboardSubtype) { 2096 InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked( 2097 res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true); 2098 if (lastResortKeyboardSubtype != null) { 2099 applicableSubtypes.add(lastResortKeyboardSubtype); 2100 } 2101 } 2102 return applicableSubtypes; 2103 } 2104 2105 /** 2106 * If there are no selected subtypes, tries finding the most applicable one according to the 2107 * given locale. 2108 * @param subtypes this function will search the most applicable subtype in subtypes 2109 * @param mode subtypes will be filtered by mode 2110 * @param locale subtypes will be filtered by locale 2111 * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype, 2112 * it will return the first subtype matched with mode 2113 * @return the most applicable subtypeId 2114 */ 2115 private static InputMethodSubtype findLastResortApplicableSubtypeLocked( 2116 Resources res, List<InputMethodSubtype> subtypes, String mode, String locale, 2117 boolean canIgnoreLocaleAsLastResort) { 2118 if (subtypes == null || subtypes.size() == 0) { 2119 return null; 2120 } 2121 if (TextUtils.isEmpty(locale)) { 2122 locale = res.getConfiguration().locale.toString(); 2123 } 2124 final String language = locale.substring(0, 2); 2125 boolean partialMatchFound = false; 2126 InputMethodSubtype applicableSubtype = null; 2127 InputMethodSubtype firstMatchedModeSubtype = null; 2128 final int N = subtypes.size(); 2129 for (int i = 0; i < N; ++i) { 2130 InputMethodSubtype subtype = subtypes.get(i); 2131 final String subtypeLocale = subtype.getLocale(); 2132 // An applicable subtype should match "mode". If mode is null, mode will be ignored, 2133 // and all subtypes with all modes can be candidates. 2134 if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) { 2135 if (firstMatchedModeSubtype == null) { 2136 firstMatchedModeSubtype = subtype; 2137 } 2138 if (locale.equals(subtypeLocale)) { 2139 // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US") 2140 applicableSubtype = subtype; 2141 break; 2142 } else if (!partialMatchFound && subtypeLocale.startsWith(language)) { 2143 // Partial match (e.g. system locale is "en_US" and subtype locale is "en") 2144 applicableSubtype = subtype; 2145 partialMatchFound = true; 2146 } 2147 } 2148 } 2149 2150 if (applicableSubtype == null && canIgnoreLocaleAsLastResort) { 2151 return firstMatchedModeSubtype; 2152 } 2153 2154 // The first subtype applicable to the system locale will be defined as the most applicable 2155 // subtype. 2156 if (DEBUG) { 2157 if (applicableSubtype != null) { 2158 Slog.d(TAG, "Applicable InputMethodSubtype was found: " 2159 + applicableSubtype.getMode() + "," + applicableSubtype.getLocale()); 2160 } 2161 } 2162 return applicableSubtype; 2163 } 2164 2165 // If there are no selected shortcuts, tries finding the most applicable ones. 2166 private Pair<InputMethodInfo, InputMethodSubtype> 2167 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2168 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2169 InputMethodInfo mostApplicableIMI = null; 2170 InputMethodSubtype mostApplicableSubtype = null; 2171 boolean foundInSystemIME = false; 2172 2173 // Search applicable subtype for each InputMethodInfo 2174 for (InputMethodInfo imi: imis) { 2175 final String imiId = imi.getId(); 2176 if (foundInSystemIME && !imiId.equals(mCurMethodId)) { 2177 continue; 2178 } 2179 InputMethodSubtype subtype = null; 2180 final List<InputMethodSubtype> enabledSubtypes = 2181 getEnabledInputMethodSubtypeList(imi, true); 2182 // 1. Search by the current subtype's locale from enabledSubtypes. 2183 if (mCurrentSubtype != null) { 2184 subtype = findLastResortApplicableSubtypeLocked( 2185 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); 2186 } 2187 // 2. Search by the system locale from enabledSubtypes. 2188 // 3. Search the first enabled subtype matched with mode from enabledSubtypes. 2189 if (subtype == null) { 2190 subtype = findLastResortApplicableSubtypeLocked( 2191 mRes, enabledSubtypes, mode, null, true); 2192 } 2193 // 4. Search by the current subtype's locale from all subtypes. 2194 if (subtype == null && mCurrentSubtype != null) { 2195 subtype = findLastResortApplicableSubtypeLocked( 2196 mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false); 2197 } 2198 // 5. Search by the system locale from all subtypes. 2199 // 6. Search the first enabled subtype matched with mode from all subtypes. 2200 if (subtype == null) { 2201 subtype = findLastResortApplicableSubtypeLocked( 2202 mRes, getSubtypes(imi), mode, null, true); 2203 } 2204 if (subtype != null) { 2205 if (imiId.equals(mCurMethodId)) { 2206 // The current input method is the most applicable IME. 2207 mostApplicableIMI = imi; 2208 mostApplicableSubtype = subtype; 2209 break; 2210 } else if (!foundInSystemIME) { 2211 // The system input method is 2nd applicable IME. 2212 mostApplicableIMI = imi; 2213 mostApplicableSubtype = subtype; 2214 if ((imi.getServiceInfo().applicationInfo.flags 2215 & ApplicationInfo.FLAG_SYSTEM) != 0) { 2216 foundInSystemIME = true; 2217 } 2218 } 2219 } 2220 } 2221 if (DEBUG) { 2222 if (mostApplicableIMI != null) { 2223 Slog.w(TAG, "Most applicable shortcut input method was:" 2224 + mostApplicableIMI.getId()); 2225 if (mostApplicableSubtype != null) { 2226 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 2227 + "," + mostApplicableSubtype.getMode() + "," 2228 + mostApplicableSubtype.getLocale()); 2229 } 2230 } 2231 } 2232 if (mostApplicableIMI != null) { 2233 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 2234 mostApplicableSubtype); 2235 } else { 2236 return null; 2237 } 2238 } 2239 2240 private static long getSystemSettingsVersion() { 2241 return SystemProperties.getLong(Settings.Secure.SYS_PROP_SETTING_VERSION, 0); 2242 } 2243 2244 /** 2245 * @return Return the current subtype of this input method. 2246 */ 2247 public InputMethodSubtype getCurrentInputMethodSubtype() { 2248 boolean subtypeIsSelected = false; 2249 try { 2250 subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(), 2251 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID; 2252 } catch (SettingNotFoundException e) { 2253 } 2254 synchronized (mMethodMap) { 2255 if (!subtypeIsSelected || mCurrentSubtype == null) { 2256 String lastInputMethodId = Settings.Secure.getString( 2257 mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD); 2258 int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId); 2259 if (subtypeId == NOT_A_SUBTYPE_ID) { 2260 InputMethodInfo imi = mMethodMap.get(lastInputMethodId); 2261 if (imi != null) { 2262 // If there are no selected subtypes, the framework will try to find 2263 // the most applicable subtype from explicitly or implicitly enabled 2264 // subtypes. 2265 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 2266 getEnabledInputMethodSubtypeList(imi, true); 2267 // If there is only one explicitly or implicitly enabled subtype, 2268 // just returns it. 2269 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 2270 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); 2271 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { 2272 mCurrentSubtype = findLastResortApplicableSubtypeLocked( 2273 mRes, explicitlyOrImplicitlyEnabledSubtypes, 2274 SUBTYPE_MODE_KEYBOARD, null, true); 2275 if (mCurrentSubtype == null) { 2276 mCurrentSubtype = findLastResortApplicableSubtypeLocked( 2277 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, 2278 true); 2279 } 2280 } 2281 } 2282 } else { 2283 mCurrentSubtype = 2284 getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId); 2285 } 2286 } 2287 return mCurrentSubtype; 2288 } 2289 } 2290 2291 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 2292 InputMethodSubtype subtype) { 2293 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 2294 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 2295 } else { 2296 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 2297 subtypes.add(subtype); 2298 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 2299 } 2300 } 2301 2302 // TODO: We should change the return type from List to List<Parcelable> 2303 public List getShortcutInputMethodsAndSubtypes() { 2304 synchronized (mMethodMap) { 2305 ArrayList<Object> ret = new ArrayList<Object>(); 2306 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 2307 // If there are no selected shortcut subtypes, the framework will try to find 2308 // the most applicable subtype from all subtypes whose mode is 2309 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 2310 Pair<InputMethodInfo, InputMethodSubtype> info = 2311 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 2312 SUBTYPE_MODE_VOICE); 2313 if (info != null) { 2314 ret.add(info.first); 2315 ret.add(info.second); 2316 } 2317 return ret; 2318 } 2319 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 2320 ret.add(imi); 2321 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 2322 ret.add(subtype); 2323 } 2324 } 2325 return ret; 2326 } 2327 } 2328 2329 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 2330 synchronized (mMethodMap) { 2331 if (subtype != null && mCurMethodId != null) { 2332 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 2333 int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode()); 2334 if (subtypeId != NOT_A_SUBTYPE_ID) { 2335 setInputMethodLocked(mCurMethodId, subtypeId); 2336 return true; 2337 } 2338 } 2339 return false; 2340 } 2341 } 2342 2343 /** 2344 * Utility class for putting and getting settings for InputMethod 2345 * TODO: Move all putters and getters of settings to this class. 2346 */ 2347 private static class InputMethodSettings { 2348 // The string for enabled input method is saved as follows: 2349 // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0") 2350 private static final char INPUT_METHOD_SEPARATER = ':'; 2351 private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';'; 2352 private final TextUtils.SimpleStringSplitter mInputMethodSplitter = 2353 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER); 2354 2355 private final TextUtils.SimpleStringSplitter mSubtypeSplitter = 2356 new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER); 2357 2358 private final Resources mRes; 2359 private final ContentResolver mResolver; 2360 private final HashMap<String, InputMethodInfo> mMethodMap; 2361 private final ArrayList<InputMethodInfo> mMethodList; 2362 2363 private String mEnabledInputMethodsStrCache; 2364 2365 private static void buildEnabledInputMethodsSettingString( 2366 StringBuilder builder, Pair<String, ArrayList<String>> pair) { 2367 String id = pair.first; 2368 ArrayList<String> subtypes = pair.second; 2369 builder.append(id); 2370 // Inputmethod and subtypes are saved in the settings as follows: 2371 // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1 2372 for (String subtypeId: subtypes) { 2373 builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId); 2374 } 2375 } 2376 2377 public InputMethodSettings( 2378 Resources res, ContentResolver resolver, 2379 HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) { 2380 mRes = res; 2381 mResolver = resolver; 2382 mMethodMap = methodMap; 2383 mMethodList = methodList; 2384 } 2385 2386 public List<InputMethodInfo> getEnabledInputMethodListLocked() { 2387 return createEnabledInputMethodListLocked( 2388 getEnabledInputMethodsAndSubtypeListLocked()); 2389 } 2390 2391 public List<Pair<InputMethodInfo, ArrayList<String>>> 2392 getEnabledInputMethodAndSubtypeHashCodeListLocked() { 2393 return createEnabledInputMethodAndSubtypeHashCodeListLocked( 2394 getEnabledInputMethodsAndSubtypeListLocked()); 2395 } 2396 2397 public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked( 2398 InputMethodInfo imi) { 2399 List<Pair<String, ArrayList<String>>> imsList = 2400 getEnabledInputMethodsAndSubtypeListLocked(); 2401 ArrayList<InputMethodSubtype> enabledSubtypes = 2402 new ArrayList<InputMethodSubtype>(); 2403 if (imi != null) { 2404 for (Pair<String, ArrayList<String>> imsPair : imsList) { 2405 InputMethodInfo info = mMethodMap.get(imsPair.first); 2406 if (info != null && info.getId().equals(imi.getId())) { 2407 final int subtypeCount = info.getSubtypeCount(); 2408 for (int i = 0; i < subtypeCount; ++i) { 2409 InputMethodSubtype ims = info.getSubtypeAt(i); 2410 for (String s: imsPair.second) { 2411 if (String.valueOf(ims.hashCode()).equals(s)) { 2412 enabledSubtypes.add(ims); 2413 } 2414 } 2415 } 2416 break; 2417 } 2418 } 2419 } 2420 return enabledSubtypes; 2421 } 2422 2423 // At the initial boot, the settings for input methods are not set, 2424 // so we need to enable IME in that case. 2425 public void enableAllIMEsIfThereIsNoEnabledIME() { 2426 if (TextUtils.isEmpty(getEnabledInputMethodsStr())) { 2427 StringBuilder sb = new StringBuilder(); 2428 final int N = mMethodList.size(); 2429 for (int i = 0; i < N; i++) { 2430 InputMethodInfo imi = mMethodList.get(i); 2431 Slog.i(TAG, "Adding: " + imi.getId()); 2432 if (i > 0) sb.append(':'); 2433 sb.append(imi.getId()); 2434 } 2435 putEnabledInputMethodsStr(sb.toString()); 2436 } 2437 } 2438 2439 private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() { 2440 ArrayList<Pair<String, ArrayList<String>>> imsList 2441 = new ArrayList<Pair<String, ArrayList<String>>>(); 2442 final String enabledInputMethodsStr = getEnabledInputMethodsStr(); 2443 if (TextUtils.isEmpty(enabledInputMethodsStr)) { 2444 return imsList; 2445 } 2446 mInputMethodSplitter.setString(enabledInputMethodsStr); 2447 while (mInputMethodSplitter.hasNext()) { 2448 String nextImsStr = mInputMethodSplitter.next(); 2449 mSubtypeSplitter.setString(nextImsStr); 2450 if (mSubtypeSplitter.hasNext()) { 2451 ArrayList<String> subtypeHashes = new ArrayList<String>(); 2452 // The first element is ime id. 2453 String imeId = mSubtypeSplitter.next(); 2454 while (mSubtypeSplitter.hasNext()) { 2455 subtypeHashes.add(mSubtypeSplitter.next()); 2456 } 2457 imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes)); 2458 } 2459 } 2460 return imsList; 2461 } 2462 2463 public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) { 2464 if (reloadInputMethodStr) { 2465 getEnabledInputMethodsStr(); 2466 } 2467 if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) { 2468 // Add in the newly enabled input method. 2469 putEnabledInputMethodsStr(id); 2470 } else { 2471 putEnabledInputMethodsStr( 2472 mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id); 2473 } 2474 } 2475 2476 /** 2477 * Build and put a string of EnabledInputMethods with removing specified Id. 2478 * @return the specified id was removed or not. 2479 */ 2480 public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2481 StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) { 2482 boolean isRemoved = false; 2483 boolean needsAppendSeparator = false; 2484 for (Pair<String, ArrayList<String>> ims: imsList) { 2485 String curId = ims.first; 2486 if (curId.equals(id)) { 2487 // We are disabling this input method, and it is 2488 // currently enabled. Skip it to remove from the 2489 // new list. 2490 isRemoved = true; 2491 } else { 2492 if (needsAppendSeparator) { 2493 builder.append(INPUT_METHOD_SEPARATER); 2494 } else { 2495 needsAppendSeparator = true; 2496 } 2497 buildEnabledInputMethodsSettingString(builder, ims); 2498 } 2499 } 2500 if (isRemoved) { 2501 // Update the setting with the new list of input methods. 2502 putEnabledInputMethodsStr(builder.toString()); 2503 } 2504 return isRemoved; 2505 } 2506 2507 private List<InputMethodInfo> createEnabledInputMethodListLocked( 2508 List<Pair<String, ArrayList<String>>> imsList) { 2509 final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>(); 2510 for (Pair<String, ArrayList<String>> ims: imsList) { 2511 InputMethodInfo info = mMethodMap.get(ims.first); 2512 if (info != null) { 2513 res.add(info); 2514 } 2515 } 2516 return res; 2517 } 2518 2519 private List<Pair<InputMethodInfo, ArrayList<String>>> 2520 createEnabledInputMethodAndSubtypeHashCodeListLocked( 2521 List<Pair<String, ArrayList<String>>> imsList) { 2522 final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res 2523 = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>(); 2524 for (Pair<String, ArrayList<String>> ims : imsList) { 2525 InputMethodInfo info = mMethodMap.get(ims.first); 2526 if (info != null) { 2527 res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second)); 2528 } 2529 } 2530 return res; 2531 } 2532 2533 private void putEnabledInputMethodsStr(String str) { 2534 Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str); 2535 mEnabledInputMethodsStrCache = str; 2536 } 2537 2538 private String getEnabledInputMethodsStr() { 2539 mEnabledInputMethodsStrCache = Settings.Secure.getString( 2540 mResolver, Settings.Secure.ENABLED_INPUT_METHODS); 2541 if (DEBUG) { 2542 Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache); 2543 } 2544 return mEnabledInputMethodsStrCache; 2545 } 2546 2547 private void saveSubtypeHistory( 2548 List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) { 2549 StringBuilder builder = new StringBuilder(); 2550 boolean isImeAdded = false; 2551 if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) { 2552 builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2553 newSubtypeId); 2554 isImeAdded = true; 2555 } 2556 for (Pair<String, String> ime: savedImes) { 2557 String imeId = ime.first; 2558 String subtypeId = ime.second; 2559 if (TextUtils.isEmpty(subtypeId)) { 2560 subtypeId = NOT_A_SUBTYPE_ID_STR; 2561 } 2562 if (isImeAdded) { 2563 builder.append(INPUT_METHOD_SEPARATER); 2564 } else { 2565 isImeAdded = true; 2566 } 2567 builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append( 2568 subtypeId); 2569 } 2570 // Remove the last INPUT_METHOD_SEPARATER 2571 putSubtypeHistoryStr(builder.toString()); 2572 } 2573 2574 public void addSubtypeToHistory(String imeId, String subtypeId) { 2575 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2576 for (Pair<String, String> ime: subtypeHistory) { 2577 if (ime.first.equals(imeId)) { 2578 if (DEBUG) { 2579 Slog.v(TAG, "Subtype found in the history: " + imeId + ", " 2580 + ime.second); 2581 } 2582 // We should break here 2583 subtypeHistory.remove(ime); 2584 break; 2585 } 2586 } 2587 if (DEBUG) { 2588 Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId); 2589 } 2590 saveSubtypeHistory(subtypeHistory, imeId, subtypeId); 2591 } 2592 2593 private void putSubtypeHistoryStr(String str) { 2594 if (DEBUG) { 2595 Slog.d(TAG, "putSubtypeHistoryStr: " + str); 2596 } 2597 Settings.Secure.putString( 2598 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str); 2599 } 2600 2601 public Pair<String, String> getLastInputMethodAndSubtypeLocked() { 2602 // Gets the first one from the history 2603 return getLastSubtypeForInputMethodLockedInternal(null); 2604 } 2605 2606 public String getLastSubtypeForInputMethodLocked(String imeId) { 2607 Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId); 2608 if (ime != null) { 2609 return ime.second; 2610 } else { 2611 return null; 2612 } 2613 } 2614 2615 private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) { 2616 List<Pair<String, ArrayList<String>>> enabledImes = 2617 getEnabledInputMethodsAndSubtypeListLocked(); 2618 List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked(); 2619 for (Pair<String, String> imeAndSubtype: subtypeHistory) { 2620 final String imeInTheHistory = imeAndSubtype.first; 2621 // If imeId is empty, returns the first IME and subtype in the history 2622 if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) { 2623 final String subtypeInTheHistory = imeAndSubtype.second; 2624 final String subtypeHashCode = 2625 getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked( 2626 enabledImes, imeInTheHistory, subtypeInTheHistory); 2627 if (!TextUtils.isEmpty(subtypeHashCode)) { 2628 if (DEBUG) { 2629 Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode); 2630 } 2631 return new Pair<String, String>(imeInTheHistory, subtypeHashCode); 2632 } 2633 } 2634 } 2635 if (DEBUG) { 2636 Slog.d(TAG, "No enabled IME found in the history"); 2637 } 2638 return null; 2639 } 2640 2641 private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String, 2642 ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) { 2643 for (Pair<String, ArrayList<String>> enabledIme: enabledImes) { 2644 if (enabledIme.first.equals(imeId)) { 2645 final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second; 2646 if (explicitlyEnabledSubtypes.size() == 0) { 2647 // If there are no explicitly enabled subtypes, applicable subtypes are 2648 // enabled implicitly. 2649 InputMethodInfo ime = mMethodMap.get(imeId); 2650 // If IME is enabled and no subtypes are enabled, applicable subtypes 2651 // are enabled implicitly, so needs to treat them to be enabled. 2652 if (ime != null && ime.getSubtypeCount() > 0) { 2653 List<InputMethodSubtype> implicitlySelectedSubtypes = 2654 getApplicableSubtypesLocked(mRes, getSubtypes(ime)); 2655 if (implicitlySelectedSubtypes != null) { 2656 final int N = implicitlySelectedSubtypes.size(); 2657 for (int i = 0; i < N; ++i) { 2658 final InputMethodSubtype st = implicitlySelectedSubtypes.get(i); 2659 if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) { 2660 return subtypeHashCode; 2661 } 2662 } 2663 } 2664 } 2665 } else { 2666 for (String s: explicitlyEnabledSubtypes) { 2667 if (s.equals(subtypeHashCode)) { 2668 // If both imeId and subtypeId are enabled, return subtypeId. 2669 return s; 2670 } 2671 } 2672 } 2673 // If imeId was enabled but subtypeId was disabled. 2674 return NOT_A_SUBTYPE_ID_STR; 2675 } 2676 } 2677 // If both imeId and subtypeId are disabled, return null 2678 return null; 2679 } 2680 2681 private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() { 2682 ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>(); 2683 final String subtypeHistoryStr = getSubtypeHistoryStr(); 2684 if (TextUtils.isEmpty(subtypeHistoryStr)) { 2685 return imsList; 2686 } 2687 mInputMethodSplitter.setString(subtypeHistoryStr); 2688 while (mInputMethodSplitter.hasNext()) { 2689 String nextImsStr = mInputMethodSplitter.next(); 2690 mSubtypeSplitter.setString(nextImsStr); 2691 if (mSubtypeSplitter.hasNext()) { 2692 String subtypeId = NOT_A_SUBTYPE_ID_STR; 2693 // The first element is ime id. 2694 String imeId = mSubtypeSplitter.next(); 2695 while (mSubtypeSplitter.hasNext()) { 2696 subtypeId = mSubtypeSplitter.next(); 2697 break; 2698 } 2699 imsList.add(new Pair<String, String>(imeId, subtypeId)); 2700 } 2701 } 2702 return imsList; 2703 } 2704 2705 private String getSubtypeHistoryStr() { 2706 if (DEBUG) { 2707 Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString( 2708 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY)); 2709 } 2710 return Settings.Secure.getString( 2711 mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY); 2712 } 2713 2714 public void putSelectedInputMethod(String imeId) { 2715 Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId); 2716 } 2717 2718 public void putSelectedSubtype(int subtypeId) { 2719 Settings.Secure.putInt( 2720 mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId); 2721 } 2722 } 2723 2724 // ---------------------------------------------------------------------- 2725 2726 @Override 2727 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 2728 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 2729 != PackageManager.PERMISSION_GRANTED) { 2730 2731 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 2732 + Binder.getCallingPid() 2733 + ", uid=" + Binder.getCallingUid()); 2734 return; 2735 } 2736 2737 IInputMethod method; 2738 ClientState client; 2739 2740 final Printer p = new PrintWriterPrinter(pw); 2741 2742 synchronized (mMethodMap) { 2743 p.println("Current Input Method Manager state:"); 2744 int N = mMethodList.size(); 2745 p.println(" Input Methods:"); 2746 for (int i=0; i<N; i++) { 2747 InputMethodInfo info = mMethodList.get(i); 2748 p.println(" InputMethod #" + i + ":"); 2749 info.dump(p, " "); 2750 } 2751 p.println(" Clients:"); 2752 for (ClientState ci : mClients.values()) { 2753 p.println(" Client " + ci + ":"); 2754 p.println(" client=" + ci.client); 2755 p.println(" inputContext=" + ci.inputContext); 2756 p.println(" sessionRequested=" + ci.sessionRequested); 2757 p.println(" curSession=" + ci.curSession); 2758 } 2759 p.println(" mCurMethodId=" + mCurMethodId); 2760 client = mCurClient; 2761 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 2762 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 2763 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 2764 + " mBoundToMethod=" + mBoundToMethod); 2765 p.println(" mCurToken=" + mCurToken); 2766 p.println(" mCurIntent=" + mCurIntent); 2767 method = mCurMethod; 2768 p.println(" mCurMethod=" + mCurMethod); 2769 p.println(" mEnabledSession=" + mEnabledSession); 2770 p.println(" mShowRequested=" + mShowRequested 2771 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 2772 + " mShowForced=" + mShowForced 2773 + " mInputShown=" + mInputShown); 2774 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 2775 } 2776 2777 p.println(" "); 2778 if (client != null) { 2779 pw.flush(); 2780 try { 2781 client.client.asBinder().dump(fd, args); 2782 } catch (RemoteException e) { 2783 p.println("Input method client dead: " + e); 2784 } 2785 } else { 2786 p.println("No input method client."); 2787 } 2788 2789 p.println(" "); 2790 if (method != null) { 2791 pw.flush(); 2792 try { 2793 method.asBinder().dump(fd, args); 2794 } catch (RemoteException e) { 2795 p.println("Input method service dead: " + e); 2796 } 2797 } else { 2798 p.println("No input method service."); 2799 } 2800 } 2801} 2802