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