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