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