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