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