InputMethodManagerService.java revision a59d0c81d54bb8f0653c0670c227f9bc36d9b69a
1/* 2 * 3 * Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 * use this file except in compliance with the License. You may obtain a copy of 5 * the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 * License for the specific language governing permissions and limitations under 13 * the License. 14 */ 15 16package com.android.server; 17 18import com.android.internal.content.PackageMonitor; 19import com.android.internal.inputmethod.InputMethodUtils; 20import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings; 21import com.android.internal.os.HandlerCaller; 22import com.android.internal.os.SomeArgs; 23import com.android.internal.util.FastXmlSerializer; 24import com.android.internal.view.IInputContext; 25import com.android.internal.view.IInputMethod; 26import com.android.internal.view.IInputMethodCallback; 27import com.android.internal.view.IInputMethodClient; 28import com.android.internal.view.IInputMethodManager; 29import com.android.internal.view.IInputMethodSession; 30import com.android.internal.view.InputBindResult; 31import com.android.server.EventLogTags; 32import com.android.server.wm.WindowManagerService; 33 34import org.xmlpull.v1.XmlPullParser; 35import org.xmlpull.v1.XmlPullParserException; 36import org.xmlpull.v1.XmlSerializer; 37 38import android.app.ActivityManagerNative; 39import android.app.AppGlobals; 40import android.app.AlertDialog; 41import android.app.IUserSwitchObserver; 42import android.app.KeyguardManager; 43import android.app.Notification; 44import android.app.NotificationManager; 45import android.app.PendingIntent; 46import android.content.BroadcastReceiver; 47import android.content.ComponentName; 48import android.content.ContentResolver; 49import android.content.Context; 50import android.content.DialogInterface; 51import android.content.DialogInterface.OnCancelListener; 52import android.content.Intent; 53import android.content.IntentFilter; 54import android.content.ServiceConnection; 55import android.content.pm.ApplicationInfo; 56import android.content.pm.IPackageManager; 57import android.content.pm.PackageManager; 58import android.content.pm.ResolveInfo; 59import android.content.pm.ServiceInfo; 60import android.content.res.Configuration; 61import android.content.res.Resources; 62import android.content.res.TypedArray; 63import android.database.ContentObserver; 64import android.inputmethodservice.InputMethodService; 65import android.os.Binder; 66import android.os.Environment; 67import android.os.Handler; 68import android.os.IBinder; 69import android.os.IInterface; 70import android.os.IRemoteCallback; 71import android.os.Message; 72import android.os.Process; 73import android.os.Parcel; 74import android.os.RemoteException; 75import android.os.ResultReceiver; 76import android.os.ServiceManager; 77import android.os.SystemClock; 78import android.os.UserHandle; 79import android.provider.Settings; 80import android.text.TextUtils; 81import android.text.style.SuggestionSpan; 82import android.util.AtomicFile; 83import android.util.EventLog; 84import android.util.LruCache; 85import android.util.Pair; 86import android.util.PrintWriterPrinter; 87import android.util.Printer; 88import android.util.Slog; 89import android.util.Xml; 90import android.view.IWindowManager; 91import android.view.LayoutInflater; 92import android.view.View; 93import android.view.ViewGroup; 94import android.view.WindowManager; 95import android.view.inputmethod.EditorInfo; 96import android.view.inputmethod.InputBinding; 97import android.view.inputmethod.InputMethod; 98import android.view.inputmethod.InputMethodInfo; 99import android.view.inputmethod.InputMethodManager; 100import android.view.inputmethod.InputMethodSubtype; 101import android.widget.ArrayAdapter; 102import android.widget.CompoundButton; 103import android.widget.CompoundButton.OnCheckedChangeListener; 104import android.widget.RadioButton; 105import android.widget.Switch; 106import android.widget.TextView; 107 108import java.io.File; 109import java.io.FileDescriptor; 110import java.io.FileInputStream; 111import java.io.FileOutputStream; 112import java.io.IOException; 113import java.io.PrintWriter; 114import java.util.ArrayList; 115import java.util.Collections; 116import java.util.Comparator; 117import java.util.HashMap; 118import java.util.HashSet; 119import java.util.List; 120import java.util.Locale; 121import java.util.TreeMap; 122 123/** 124 * This class provides a system service that manages input methods. 125 */ 126public class InputMethodManagerService extends IInputMethodManager.Stub 127 implements ServiceConnection, Handler.Callback { 128 static final boolean DEBUG = false; 129 static final String TAG = "InputMethodManagerService"; 130 131 static final int MSG_SHOW_IM_PICKER = 1; 132 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2; 133 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3; 134 static final int MSG_SHOW_IM_CONFIG = 4; 135 136 static final int MSG_UNBIND_INPUT = 1000; 137 static final int MSG_BIND_INPUT = 1010; 138 static final int MSG_SHOW_SOFT_INPUT = 1020; 139 static final int MSG_HIDE_SOFT_INPUT = 1030; 140 static final int MSG_ATTACH_TOKEN = 1040; 141 static final int MSG_CREATE_SESSION = 1050; 142 143 static final int MSG_START_INPUT = 2000; 144 static final int MSG_RESTART_INPUT = 2010; 145 146 static final int MSG_UNBIND_METHOD = 3000; 147 static final int MSG_BIND_METHOD = 3010; 148 static final int MSG_SET_ACTIVE = 3020; 149 150 static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000; 151 152 static final long TIME_TO_RECONNECT = 10*1000; 153 154 static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20; 155 156 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID; 157 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher"; 158 159 160 final Context mContext; 161 final Resources mRes; 162 final Handler mHandler; 163 final InputMethodSettings mSettings; 164 final SettingsObserver mSettingsObserver; 165 final IWindowManager mIWindowManager; 166 final HandlerCaller mCaller; 167 private InputMethodFileManager mFileManager; 168 private InputMethodAndSubtypeListManager mImListManager; 169 private final HardKeyboardListener mHardKeyboardListener; 170 private final WindowManagerService mWindowManagerService; 171 172 final InputBindResult mNoBinding = new InputBindResult(null, null, -1); 173 174 // All known input methods. mMethodMap also serves as the global 175 // lock for this class. 176 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>(); 177 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>(); 178 private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans = 179 new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE); 180 181 // Used to bring IME service up to visible adjustment while it is being shown. 182 final ServiceConnection mVisibleConnection = new ServiceConnection() { 183 @Override public void onServiceConnected(ComponentName name, IBinder service) { 184 } 185 186 @Override public void onServiceDisconnected(ComponentName name) { 187 } 188 }; 189 boolean mVisibleBound = false; 190 191 // Ongoing notification 192 private NotificationManager mNotificationManager; 193 private KeyguardManager mKeyguardManager; 194 private StatusBarManagerService mStatusBar; 195 private Notification mImeSwitcherNotification; 196 private PendingIntent mImeSwitchPendingIntent; 197 private boolean mShowOngoingImeSwitcherForPhones; 198 private boolean mNotificationShown; 199 private final boolean mImeSelectedOnBoot; 200 201 class SessionState { 202 final ClientState client; 203 final IInputMethod method; 204 final IInputMethodSession session; 205 206 @Override 207 public String toString() { 208 return "SessionState{uid " + client.uid + " pid " + client.pid 209 + " method " + Integer.toHexString( 210 System.identityHashCode(method)) 211 + " session " + Integer.toHexString( 212 System.identityHashCode(session)) 213 + "}"; 214 } 215 216 SessionState(ClientState _client, IInputMethod _method, 217 IInputMethodSession _session) { 218 client = _client; 219 method = _method; 220 session = _session; 221 } 222 } 223 224 class ClientState { 225 final IInputMethodClient client; 226 final IInputContext inputContext; 227 final int uid; 228 final int pid; 229 final InputBinding binding; 230 231 boolean sessionRequested; 232 SessionState curSession; 233 234 @Override 235 public String toString() { 236 return "ClientState{" + Integer.toHexString( 237 System.identityHashCode(this)) + " uid " + uid 238 + " pid " + pid + "}"; 239 } 240 241 ClientState(IInputMethodClient _client, IInputContext _inputContext, 242 int _uid, int _pid) { 243 client = _client; 244 inputContext = _inputContext; 245 uid = _uid; 246 pid = _pid; 247 binding = new InputBinding(null, inputContext.asBinder(), uid, pid); 248 } 249 } 250 251 final HashMap<IBinder, ClientState> mClients 252 = new HashMap<IBinder, ClientState>(); 253 254 /** 255 * Set once the system is ready to run third party code. 256 */ 257 boolean mSystemReady; 258 259 /** 260 * Id of the currently selected input method. 261 */ 262 String mCurMethodId; 263 264 /** 265 * The current binding sequence number, incremented every time there is 266 * a new bind performed. 267 */ 268 int mCurSeq; 269 270 /** 271 * The client that is currently bound to an input method. 272 */ 273 ClientState mCurClient; 274 275 /** 276 * The last window token that gained focus. 277 */ 278 IBinder mCurFocusedWindow; 279 280 /** 281 * The input context last provided by the current client. 282 */ 283 IInputContext mCurInputContext; 284 285 /** 286 * The attributes last provided by the current client. 287 */ 288 EditorInfo mCurAttribute; 289 290 /** 291 * The input method ID of the input method service that we are currently 292 * connected to or in the process of connecting to. 293 */ 294 String mCurId; 295 296 /** 297 * The current subtype of the current input method. 298 */ 299 private InputMethodSubtype mCurrentSubtype; 300 301 // This list contains the pairs of InputMethodInfo and InputMethodSubtype. 302 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>> 303 mShortcutInputMethodsAndSubtypes = 304 new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>(); 305 306 /** 307 * Set to true if our ServiceConnection is currently actively bound to 308 * a service (whether or not we have gotten its IBinder back yet). 309 */ 310 boolean mHaveConnection; 311 312 /** 313 * Set if the client has asked for the input method to be shown. 314 */ 315 boolean mShowRequested; 316 317 /** 318 * Set if we were explicitly told to show the input method. 319 */ 320 boolean mShowExplicitlyRequested; 321 322 /** 323 * Set if we were forced to be shown. 324 */ 325 boolean mShowForced; 326 327 /** 328 * Set if we last told the input method to show itself. 329 */ 330 boolean mInputShown; 331 332 /** 333 * The Intent used to connect to the current input method. 334 */ 335 Intent mCurIntent; 336 337 /** 338 * The token we have made for the currently active input method, to 339 * identify it in the future. 340 */ 341 IBinder mCurToken; 342 343 /** 344 * If non-null, this is the input method service we are currently connected 345 * to. 346 */ 347 IInputMethod mCurMethod; 348 349 /** 350 * Time that we last initiated a bind to the input method, to determine 351 * if we should try to disconnect and reconnect to it. 352 */ 353 long mLastBindTime; 354 355 /** 356 * Have we called mCurMethod.bindInput()? 357 */ 358 boolean mBoundToMethod; 359 360 /** 361 * Currently enabled session. Only touched by service thread, not 362 * protected by a lock. 363 */ 364 SessionState mEnabledSession; 365 366 /** 367 * True if the screen is on. The value is true initially. 368 */ 369 boolean mScreenOn = true; 370 371 int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT; 372 int mImeWindowVis; 373 374 private AlertDialog.Builder mDialogBuilder; 375 private AlertDialog mSwitchingDialog; 376 private View mSwitchingDialogTitleView; 377 private InputMethodInfo[] mIms; 378 private int[] mSubtypeIds; 379 private Locale mLastSystemLocale; 380 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor(); 381 private final IPackageManager mIPackageManager; 382 private boolean mInputBoundToKeyguard; 383 384 class SettingsObserver extends ContentObserver { 385 String mLastEnabled = ""; 386 387 SettingsObserver(Handler handler) { 388 super(handler); 389 ContentResolver resolver = mContext.getContentResolver(); 390 resolver.registerContentObserver(Settings.Secure.getUriFor( 391 Settings.Secure.DEFAULT_INPUT_METHOD), false, this); 392 resolver.registerContentObserver(Settings.Secure.getUriFor( 393 Settings.Secure.ENABLED_INPUT_METHODS), false, this); 394 resolver.registerContentObserver(Settings.Secure.getUriFor( 395 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this); 396 } 397 398 @Override public void onChange(boolean selfChange) { 399 synchronized (mMethodMap) { 400 boolean enabledChanged = false; 401 String newEnabled = mSettings.getEnabledInputMethodsStr(); 402 if (!mLastEnabled.equals(newEnabled)) { 403 mLastEnabled = newEnabled; 404 enabledChanged = true; 405 } 406 updateFromSettingsLocked(enabledChanged); 407 } 408 } 409 } 410 411 class ImmsBroadcastReceiver extends android.content.BroadcastReceiver { 412 private void updateActive() { 413 // Inform the current client of the change in active status 414 if (mCurClient != null && mCurClient.client != null) { 415 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 416 MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient)); 417 } 418 } 419 420 @Override 421 public void onReceive(Context context, Intent intent) { 422 final String action = intent.getAction(); 423 if (Intent.ACTION_SCREEN_ON.equals(action)) { 424 mScreenOn = true; 425 refreshImeWindowVisibilityLocked(); 426 updateActive(); 427 return; 428 } else if (Intent.ACTION_SCREEN_OFF.equals(action)) { 429 mScreenOn = false; 430 setImeWindowVisibilityStatusHiddenLocked(); 431 updateActive(); 432 return; 433 } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { 434 hideInputMethodMenu(); 435 // No need to updateActive 436 return; 437 } else { 438 Slog.w(TAG, "Unexpected intent " + intent); 439 } 440 } 441 } 442 443 class MyPackageMonitor extends PackageMonitor { 444 private boolean isChangingPackagesOfCurrentUser() { 445 final int userId = getChangingUserId(); 446 final boolean retval = userId == mSettings.getCurrentUserId(); 447 if (DEBUG) { 448 if (!retval) { 449 Slog.d(TAG, "--- ignore this call back from a background user: " + userId); 450 } 451 } 452 return retval; 453 } 454 455 @Override 456 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) { 457 if (!isChangingPackagesOfCurrentUser()) { 458 return false; 459 } 460 synchronized (mMethodMap) { 461 String curInputMethodId = mSettings.getSelectedInputMethod(); 462 final int N = mMethodList.size(); 463 if (curInputMethodId != null) { 464 for (int i=0; i<N; i++) { 465 InputMethodInfo imi = mMethodList.get(i); 466 if (imi.getId().equals(curInputMethodId)) { 467 for (String pkg : packages) { 468 if (imi.getPackageName().equals(pkg)) { 469 if (!doit) { 470 return true; 471 } 472 resetSelectedInputMethodAndSubtypeLocked(""); 473 chooseNewDefaultIMELocked(); 474 return true; 475 } 476 } 477 } 478 } 479 } 480 } 481 return false; 482 } 483 484 @Override 485 public void onSomePackagesChanged() { 486 if (!isChangingPackagesOfCurrentUser()) { 487 return; 488 } 489 synchronized (mMethodMap) { 490 InputMethodInfo curIm = null; 491 String curInputMethodId = mSettings.getSelectedInputMethod(); 492 final int N = mMethodList.size(); 493 if (curInputMethodId != null) { 494 for (int i=0; i<N; i++) { 495 InputMethodInfo imi = mMethodList.get(i); 496 final String imiId = imi.getId(); 497 if (imiId.equals(curInputMethodId)) { 498 curIm = imi; 499 } 500 501 int change = isPackageDisappearing(imi.getPackageName()); 502 if (isPackageModified(imi.getPackageName())) { 503 mFileManager.deleteAllInputMethodSubtypes(imiId); 504 } 505 if (change == PACKAGE_TEMPORARY_CHANGE 506 || change == PACKAGE_PERMANENT_CHANGE) { 507 Slog.i(TAG, "Input method uninstalled, disabling: " 508 + imi.getComponent()); 509 setInputMethodEnabledLocked(imi.getId(), false); 510 } 511 } 512 } 513 514 buildInputMethodListLocked( 515 mMethodList, mMethodMap, false /* resetDefaultEnabledIme */); 516 517 boolean changed = false; 518 519 if (curIm != null) { 520 int change = isPackageDisappearing(curIm.getPackageName()); 521 if (change == PACKAGE_TEMPORARY_CHANGE 522 || change == PACKAGE_PERMANENT_CHANGE) { 523 ServiceInfo si = null; 524 try { 525 si = mIPackageManager.getServiceInfo( 526 curIm.getComponent(), 0, mSettings.getCurrentUserId()); 527 } catch (RemoteException ex) { 528 } 529 if (si == null) { 530 // Uh oh, current input method is no longer around! 531 // Pick another one... 532 Slog.i(TAG, "Current input method removed: " + curInputMethodId); 533 setImeWindowVisibilityStatusHiddenLocked(); 534 if (!chooseNewDefaultIMELocked()) { 535 changed = true; 536 curIm = null; 537 Slog.i(TAG, "Unsetting current input method"); 538 resetSelectedInputMethodAndSubtypeLocked(""); 539 } 540 } 541 } 542 } 543 544 if (curIm == null) { 545 // We currently don't have a default input method... is 546 // one now available? 547 changed = chooseNewDefaultIMELocked(); 548 } 549 550 if (changed) { 551 updateFromSettingsLocked(false); 552 } 553 } 554 } 555 } 556 557 private static class MethodCallback extends IInputMethodCallback.Stub { 558 private final IInputMethod mMethod; 559 private final InputMethodManagerService mParentIMMS; 560 561 MethodCallback(final IInputMethod method, final InputMethodManagerService imms) { 562 mMethod = method; 563 mParentIMMS = imms; 564 } 565 566 @Override 567 public void finishedEvent(int seq, boolean handled) throws RemoteException { 568 } 569 570 @Override 571 public void sessionCreated(IInputMethodSession session) throws RemoteException { 572 mParentIMMS.onSessionCreated(mMethod, session); 573 } 574 } 575 576 private class HardKeyboardListener 577 implements WindowManagerService.OnHardKeyboardStatusChangeListener { 578 @Override 579 public void onHardKeyboardStatusChange(boolean available, boolean enabled) { 580 mHandler.sendMessage(mHandler.obtainMessage( 581 MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0)); 582 } 583 584 public void handleHardKeyboardStatusChange(boolean available, boolean enabled) { 585 if (DEBUG) { 586 Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = " 587 + enabled); 588 } 589 synchronized(mMethodMap) { 590 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null 591 && mSwitchingDialog.isShowing()) { 592 mSwitchingDialogTitleView.findViewById( 593 com.android.internal.R.id.hard_keyboard_section).setVisibility( 594 available ? View.VISIBLE : View.GONE); 595 } 596 } 597 } 598 } 599 600 public InputMethodManagerService(Context context, WindowManagerService windowManager) { 601 mIPackageManager = AppGlobals.getPackageManager(); 602 mContext = context; 603 mRes = context.getResources(); 604 mHandler = new Handler(this); 605 mIWindowManager = IWindowManager.Stub.asInterface( 606 ServiceManager.getService(Context.WINDOW_SERVICE)); 607 mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() { 608 @Override 609 public void executeMessage(Message msg) { 610 handleMessage(msg); 611 } 612 }, true /*asyncHandler*/); 613 mWindowManagerService = windowManager; 614 mHardKeyboardListener = new HardKeyboardListener(); 615 616 mImeSwitcherNotification = new Notification(); 617 mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default; 618 mImeSwitcherNotification.when = 0; 619 mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT; 620 mImeSwitcherNotification.tickerText = null; 621 mImeSwitcherNotification.defaults = 0; // please be quiet 622 mImeSwitcherNotification.sound = null; 623 mImeSwitcherNotification.vibrate = null; 624 625 // Tag this notification specially so SystemUI knows it's important 626 mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" }; 627 628 Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER); 629 mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); 630 631 mShowOngoingImeSwitcherForPhones = false; 632 633 final IntentFilter broadcastFilter = new IntentFilter(); 634 broadcastFilter.addAction(Intent.ACTION_SCREEN_ON); 635 broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF); 636 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); 637 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter); 638 639 mNotificationShown = false; 640 int userId = 0; 641 try { 642 ActivityManagerNative.getDefault().registerUserSwitchObserver( 643 new IUserSwitchObserver.Stub() { 644 @Override 645 public void onUserSwitching(int newUserId, IRemoteCallback reply) { 646 synchronized(mMethodMap) { 647 switchUserLocked(newUserId); 648 } 649 if (reply != null) { 650 try { 651 reply.sendResult(null); 652 } catch (RemoteException e) { 653 } 654 } 655 } 656 657 @Override 658 public void onUserSwitchComplete(int newUserId) throws RemoteException { 659 } 660 }); 661 userId = ActivityManagerNative.getDefault().getCurrentUser().id; 662 } catch (RemoteException e) { 663 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e); 664 } 665 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true); 666 667 // mSettings should be created before buildInputMethodListLocked 668 mSettings = new InputMethodSettings( 669 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId); 670 mFileManager = new InputMethodFileManager(mMethodMap, userId); 671 mImListManager = new InputMethodAndSubtypeListManager(context, this); 672 673 // Just checking if defaultImiId is empty or not 674 final String defaultImiId = mSettings.getSelectedInputMethod(); 675 if (DEBUG) { 676 Slog.d(TAG, "Initial default ime = " + defaultImiId); 677 } 678 mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId); 679 680 buildInputMethodListLocked(mMethodList, mMethodMap, 681 !mImeSelectedOnBoot /* resetDefaultEnabledIme */); 682 mSettings.enableAllIMEsIfThereIsNoEnabledIME(); 683 684 if (!mImeSelectedOnBoot) { 685 Slog.w(TAG, "No IME selected. Choose the most applicable IME."); 686 resetDefaultImeLocked(context); 687 } 688 689 mSettingsObserver = new SettingsObserver(mHandler); 690 updateFromSettingsLocked(true); 691 692 // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME 693 // according to the new system locale. 694 final IntentFilter filter = new IntentFilter(); 695 filter.addAction(Intent.ACTION_LOCALE_CHANGED); 696 mContext.registerReceiver( 697 new BroadcastReceiver() { 698 @Override 699 public void onReceive(Context context, Intent intent) { 700 synchronized(mMethodMap) { 701 checkCurrentLocaleChangedLocked(); 702 } 703 } 704 }, filter); 705 } 706 707 private void resetDefaultImeLocked(Context context) { 708 // Do not reset the default (current) IME when it is a 3rd-party IME 709 if (mCurMethodId != null 710 && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) { 711 return; 712 } 713 714 InputMethodInfo defIm = null; 715 for (InputMethodInfo imi : mMethodList) { 716 if (defIm == null) { 717 if (InputMethodUtils.isValidSystemDefaultIme( 718 mSystemReady, imi, context)) { 719 defIm = imi; 720 Slog.i(TAG, "Selected default: " + imi.getId()); 721 } 722 } 723 } 724 if (defIm == null && mMethodList.size() > 0) { 725 defIm = InputMethodUtils.getMostApplicableDefaultIME( 726 mSettings.getEnabledInputMethodListLocked()); 727 Slog.i(TAG, "No default found, using " + defIm.getId()); 728 } 729 if (defIm != null) { 730 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false); 731 } 732 } 733 734 private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged, 735 final boolean resetDefaultEnabledIme) { 736 if (!mSystemReady) { 737 // not system ready 738 return; 739 } 740 final Locale newLocale = mRes.getConfiguration().locale; 741 if (!updateOnlyWhenLocaleChanged 742 || (newLocale != null && !newLocale.equals(mLastSystemLocale))) { 743 if (!updateOnlyWhenLocaleChanged) { 744 hideCurrentInputLocked(0, null); 745 mCurMethodId = null; 746 unbindCurrentMethodLocked(true, false); 747 } 748 if (DEBUG) { 749 Slog.i(TAG, "Locale has been changed to " + newLocale); 750 } 751 // InputMethodAndSubtypeListManager should be reset when the locale is changed. 752 mImListManager = new InputMethodAndSubtypeListManager(mContext, this); 753 buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme); 754 if (!updateOnlyWhenLocaleChanged) { 755 final String selectedImiId = mSettings.getSelectedInputMethod(); 756 if (TextUtils.isEmpty(selectedImiId)) { 757 // This is the first time of the user switch and 758 // set the current ime to the proper one. 759 resetDefaultImeLocked(mContext); 760 } 761 } else { 762 // If the locale is changed, needs to reset the default ime 763 resetDefaultImeLocked(mContext); 764 } 765 updateFromSettingsLocked(true); 766 mLastSystemLocale = newLocale; 767 if (!updateOnlyWhenLocaleChanged) { 768 try { 769 startInputInnerLocked(); 770 } catch (RuntimeException e) { 771 Slog.w(TAG, "Unexpected exception", e); 772 } 773 } 774 } 775 } 776 777 private void checkCurrentLocaleChangedLocked() { 778 resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */, 779 true /* resetDefaultImeLocked */); 780 } 781 782 private void switchUserLocked(int newUserId) { 783 mSettings.setCurrentUserId(newUserId); 784 // InputMethodFileManager should be reset when the user is changed 785 mFileManager = new InputMethodFileManager(mMethodMap, newUserId); 786 final String defaultImiId = mSettings.getSelectedInputMethod(); 787 final boolean needsToResetDefaultIme = TextUtils.isEmpty(defaultImiId); 788 // For secondary users, the list of enabled IMEs may not have been updated since the 789 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may 790 // not be empty even if the IME has been uninstalled by the primary user. 791 // Even in such cases, IMMS works fine because it will find the most applicable 792 // IME for that user. 793 if (DEBUG) { 794 Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId); 795 } 796 resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */, 797 needsToResetDefaultIme); 798 } 799 800 @Override 801 public boolean onTransact(int code, Parcel data, Parcel reply, int flags) 802 throws RemoteException { 803 try { 804 return super.onTransact(code, data, reply, flags); 805 } catch (RuntimeException e) { 806 // The input method manager only throws security exceptions, so let's 807 // log all others. 808 if (!(e instanceof SecurityException)) { 809 Slog.e(TAG, "Input Method Manager Crash", e); 810 } 811 throw e; 812 } 813 } 814 815 public void systemReady(StatusBarManagerService statusBar) { 816 synchronized (mMethodMap) { 817 if (DEBUG) { 818 Slog.d(TAG, "--- systemReady"); 819 } 820 if (!mSystemReady) { 821 mSystemReady = true; 822 mKeyguardManager = 823 (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE); 824 mNotificationManager = (NotificationManager) 825 mContext.getSystemService(Context.NOTIFICATION_SERVICE); 826 mStatusBar = statusBar; 827 statusBar.setIconVisibility("ime", false); 828 updateImeWindowStatusLocked(); 829 mShowOngoingImeSwitcherForPhones = mRes.getBoolean( 830 com.android.internal.R.bool.show_ongoing_ime_switcher); 831 if (mShowOngoingImeSwitcherForPhones) { 832 mWindowManagerService.setOnHardKeyboardStatusChangeListener( 833 mHardKeyboardListener); 834 } 835 buildInputMethodListLocked(mMethodList, mMethodMap, 836 !mImeSelectedOnBoot /* resetDefaultEnabledIme */); 837 if (!mImeSelectedOnBoot) { 838 Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here."); 839 checkCurrentLocaleChangedLocked(); 840 } 841 mLastSystemLocale = mRes.getConfiguration().locale; 842 try { 843 startInputInnerLocked(); 844 } catch (RuntimeException e) { 845 Slog.w(TAG, "Unexpected exception", e); 846 } 847 } 848 } 849 } 850 851 private void setImeWindowVisibilityStatusHiddenLocked() { 852 mImeWindowVis = 0; 853 updateImeWindowStatusLocked(); 854 } 855 856 private void refreshImeWindowVisibilityLocked() { 857 final Configuration conf = mRes.getConfiguration(); 858 final boolean haveHardKeyboard = conf.keyboard 859 != Configuration.KEYBOARD_NOKEYS; 860 final boolean hardKeyShown = haveHardKeyboard 861 && conf.hardKeyboardHidden 862 != Configuration.HARDKEYBOARDHIDDEN_YES; 863 final boolean isScreenLocked = 864 mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 865 final boolean isScreenSecurelyLocked = 866 isScreenLocked && mKeyguardManager.isKeyguardSecure(); 867 final boolean inputShown = mInputShown && (!isScreenLocked || mInputBoundToKeyguard); 868 mImeWindowVis = (!isScreenSecurelyLocked && (inputShown || hardKeyShown)) ? 869 (InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE) : 0; 870 updateImeWindowStatusLocked(); 871 } 872 873 private void updateImeWindowStatusLocked() { 874 setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition); 875 } 876 877 // --------------------------------------------------------------------------------------- 878 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either 879 // 1) it comes from the system process 880 // 2) the calling process' user id is identical to the current user id IMMS thinks. 881 private boolean calledFromValidUser() { 882 final int uid = Binder.getCallingUid(); 883 final int userId = UserHandle.getUserId(uid); 884 if (DEBUG) { 885 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " 886 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID 887 + " calling userId = " + userId + ", foreground user id = " 888 + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()); 889 } 890 if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) { 891 return true; 892 } 893 894 // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the 895 // foreground user, not for the user of that process. Accordingly InputMethodManagerService 896 // must not manage background users' states in any functions. 897 // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded 898 // by a token. 899 if (mContext.checkCallingOrSelfPermission( 900 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) 901 == PackageManager.PERMISSION_GRANTED) { 902 if (DEBUG) { 903 Slog.d(TAG, "--- Access granted because the calling process has " 904 + "the INTERACT_ACROSS_USERS_FULL permission"); 905 } 906 return true; 907 } 908 Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace()); 909 return false; 910 } 911 912 private boolean bindCurrentInputMethodService( 913 Intent service, ServiceConnection conn, int flags) { 914 if (service == null || conn == null) { 915 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn); 916 return false; 917 } 918 return mContext.bindServiceAsUser(service, conn, flags, 919 new UserHandle(mSettings.getCurrentUserId())); 920 } 921 922 @Override 923 public List<InputMethodInfo> getInputMethodList() { 924 // TODO: Make this work even for non-current users? 925 if (!calledFromValidUser()) { 926 return Collections.emptyList(); 927 } 928 synchronized (mMethodMap) { 929 return new ArrayList<InputMethodInfo>(mMethodList); 930 } 931 } 932 933 @Override 934 public List<InputMethodInfo> getEnabledInputMethodList() { 935 // TODO: Make this work even for non-current users? 936 if (!calledFromValidUser()) { 937 return Collections.emptyList(); 938 } 939 synchronized (mMethodMap) { 940 return mSettings.getEnabledInputMethodListLocked(); 941 } 942 } 943 944 private HashMap<InputMethodInfo, List<InputMethodSubtype>> 945 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() { 946 HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes = 947 new HashMap<InputMethodInfo, List<InputMethodSubtype>>(); 948 for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) { 949 enabledInputMethodAndSubtypes.put( 950 imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true)); 951 } 952 return enabledInputMethodAndSubtypes; 953 } 954 955 /** 956 * @param imi if null, returns enabled subtypes for the current imi 957 * @return enabled subtypes of the specified imi 958 */ 959 @Override 960 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, 961 boolean allowsImplicitlySelectedSubtypes) { 962 // TODO: Make this work even for non-current users? 963 if (!calledFromValidUser()) { 964 return Collections.emptyList(); 965 } 966 synchronized (mMethodMap) { 967 if (imi == null && mCurMethodId != null) { 968 imi = mMethodMap.get(mCurMethodId); 969 } 970 return mSettings.getEnabledInputMethodSubtypeListLocked( 971 mContext, imi, allowsImplicitlySelectedSubtypes); 972 } 973 } 974 975 @Override 976 public void addClient(IInputMethodClient client, 977 IInputContext inputContext, int uid, int pid) { 978 if (!calledFromValidUser()) { 979 return; 980 } 981 synchronized (mMethodMap) { 982 mClients.put(client.asBinder(), new ClientState(client, 983 inputContext, uid, pid)); 984 } 985 } 986 987 @Override 988 public void removeClient(IInputMethodClient client) { 989 if (!calledFromValidUser()) { 990 return; 991 } 992 synchronized (mMethodMap) { 993 mClients.remove(client.asBinder()); 994 } 995 } 996 997 void executeOrSendMessage(IInterface target, Message msg) { 998 if (target.asBinder() instanceof Binder) { 999 mCaller.sendMessage(msg); 1000 } else { 1001 handleMessage(msg); 1002 msg.recycle(); 1003 } 1004 } 1005 1006 void unbindCurrentClientLocked() { 1007 if (mCurClient != null) { 1008 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = " 1009 + mCurClient.client.asBinder()); 1010 if (mBoundToMethod) { 1011 mBoundToMethod = false; 1012 if (mCurMethod != null) { 1013 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 1014 MSG_UNBIND_INPUT, mCurMethod)); 1015 } 1016 } 1017 1018 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1019 MSG_SET_ACTIVE, 0, mCurClient)); 1020 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1021 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1022 mCurClient.sessionRequested = false; 1023 mCurClient = null; 1024 1025 hideInputMethodMenuLocked(); 1026 } 1027 } 1028 1029 private int getImeShowFlags() { 1030 int flags = 0; 1031 if (mShowForced) { 1032 flags |= InputMethod.SHOW_FORCED 1033 | InputMethod.SHOW_EXPLICIT; 1034 } else if (mShowExplicitlyRequested) { 1035 flags |= InputMethod.SHOW_EXPLICIT; 1036 } 1037 return flags; 1038 } 1039 1040 private int getAppShowFlags() { 1041 int flags = 0; 1042 if (mShowForced) { 1043 flags |= InputMethodManager.SHOW_FORCED; 1044 } else if (!mShowExplicitlyRequested) { 1045 flags |= InputMethodManager.SHOW_IMPLICIT; 1046 } 1047 return flags; 1048 } 1049 1050 InputBindResult attachNewInputLocked(boolean initial) { 1051 if (!mBoundToMethod) { 1052 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1053 MSG_BIND_INPUT, mCurMethod, mCurClient.binding)); 1054 mBoundToMethod = true; 1055 } 1056 final SessionState session = mCurClient.curSession; 1057 if (initial) { 1058 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 1059 MSG_START_INPUT, session, mCurInputContext, mCurAttribute)); 1060 } else { 1061 executeOrSendMessage(session.method, mCaller.obtainMessageOOO( 1062 MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute)); 1063 } 1064 if (mShowRequested) { 1065 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input"); 1066 showCurrentInputLocked(getAppShowFlags(), null); 1067 } 1068 return new InputBindResult(session.session, mCurId, mCurSeq); 1069 } 1070 1071 InputBindResult startInputLocked(IInputMethodClient client, 1072 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1073 // If no method is currently selected, do nothing. 1074 if (mCurMethodId == null) { 1075 return mNoBinding; 1076 } 1077 1078 ClientState cs = mClients.get(client.asBinder()); 1079 if (cs == null) { 1080 throw new IllegalArgumentException("unknown client " 1081 + client.asBinder()); 1082 } 1083 1084 try { 1085 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 1086 // Check with the window manager to make sure this client actually 1087 // has a window with focus. If not, reject. This is thread safe 1088 // because if the focus changes some time before or after, the 1089 // next client receiving focus that has any interest in input will 1090 // be calling through here after that change happens. 1091 Slog.w(TAG, "Starting input on non-focused client " + cs.client 1092 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 1093 return null; 1094 } 1095 } catch (RemoteException e) { 1096 } 1097 1098 return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags); 1099 } 1100 1101 InputBindResult startInputUncheckedLocked(ClientState cs, 1102 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1103 // If no method is currently selected, do nothing. 1104 if (mCurMethodId == null) { 1105 return mNoBinding; 1106 } 1107 1108 if (mCurClient == null) { 1109 mInputBoundToKeyguard = mKeyguardManager != null && mKeyguardManager.isKeyguardLocked(); 1110 if (DEBUG) { 1111 Slog.v(TAG, "New bind. keyguard = " + mInputBoundToKeyguard); 1112 } 1113 } 1114 1115 if (mCurClient != cs) { 1116 // If the client is changing, we need to switch over to the new 1117 // one. 1118 unbindCurrentClientLocked(); 1119 if (DEBUG) Slog.v(TAG, "switching to client: client = " 1120 + cs.client.asBinder()); 1121 1122 // If the screen is on, inform the new client it is active 1123 if (mScreenOn) { 1124 executeOrSendMessage(cs.client, mCaller.obtainMessageIO( 1125 MSG_SET_ACTIVE, mScreenOn ? 1 : 0, cs)); 1126 } 1127 } 1128 1129 // Bump up the sequence for this client and attach it. 1130 mCurSeq++; 1131 if (mCurSeq <= 0) mCurSeq = 1; 1132 mCurClient = cs; 1133 mCurInputContext = inputContext; 1134 mCurAttribute = attribute; 1135 1136 // Check if the input method is changing. 1137 if (mCurId != null && mCurId.equals(mCurMethodId)) { 1138 if (cs.curSession != null) { 1139 // Fast case: if we are already connected to the input method, 1140 // then just return it. 1141 return attachNewInputLocked( 1142 (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0); 1143 } 1144 if (mHaveConnection) { 1145 if (mCurMethod != null) { 1146 if (!cs.sessionRequested) { 1147 cs.sessionRequested = true; 1148 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs); 1149 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1150 MSG_CREATE_SESSION, mCurMethod, 1151 new MethodCallback(mCurMethod, this))); 1152 } 1153 // Return to client, and we will get back with it when 1154 // we have had a session made for it. 1155 return new InputBindResult(null, mCurId, mCurSeq); 1156 } else if (SystemClock.uptimeMillis() 1157 < (mLastBindTime+TIME_TO_RECONNECT)) { 1158 // In this case we have connected to the service, but 1159 // don't yet have its interface. If it hasn't been too 1160 // long since we did the connection, we'll return to 1161 // the client and wait to get the service interface so 1162 // we can report back. If it has been too long, we want 1163 // to fall through so we can try a disconnect/reconnect 1164 // to see if we can get back in touch with the service. 1165 return new InputBindResult(null, mCurId, mCurSeq); 1166 } else { 1167 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, 1168 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0); 1169 } 1170 } 1171 } 1172 1173 return startInputInnerLocked(); 1174 } 1175 1176 InputBindResult startInputInnerLocked() { 1177 if (mCurMethodId == null) { 1178 return mNoBinding; 1179 } 1180 1181 if (!mSystemReady) { 1182 // If the system is not yet ready, we shouldn't be running third 1183 // party code. 1184 return new InputBindResult(null, mCurMethodId, mCurSeq); 1185 } 1186 1187 InputMethodInfo info = mMethodMap.get(mCurMethodId); 1188 if (info == null) { 1189 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 1190 } 1191 1192 unbindCurrentMethodLocked(false, true); 1193 1194 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE); 1195 mCurIntent.setComponent(info.getComponent()); 1196 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL, 1197 com.android.internal.R.string.input_method_binding_label); 1198 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( 1199 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); 1200 if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE 1201 | Context.BIND_NOT_VISIBLE)) { 1202 mLastBindTime = SystemClock.uptimeMillis(); 1203 mHaveConnection = true; 1204 mCurId = info.getId(); 1205 mCurToken = new Binder(); 1206 try { 1207 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken); 1208 mIWindowManager.addWindowToken(mCurToken, 1209 WindowManager.LayoutParams.TYPE_INPUT_METHOD); 1210 } catch (RemoteException e) { 1211 } 1212 return new InputBindResult(null, mCurId, mCurSeq); 1213 } else { 1214 mCurIntent = null; 1215 Slog.w(TAG, "Failure connecting to input method service: " 1216 + mCurIntent); 1217 } 1218 return null; 1219 } 1220 1221 @Override 1222 public InputBindResult startInput(IInputMethodClient client, 1223 IInputContext inputContext, EditorInfo attribute, int controlFlags) { 1224 if (!calledFromValidUser()) { 1225 return null; 1226 } 1227 synchronized (mMethodMap) { 1228 final long ident = Binder.clearCallingIdentity(); 1229 try { 1230 return startInputLocked(client, inputContext, attribute, controlFlags); 1231 } finally { 1232 Binder.restoreCallingIdentity(ident); 1233 } 1234 } 1235 } 1236 1237 @Override 1238 public void finishInput(IInputMethodClient client) { 1239 } 1240 1241 @Override 1242 public void onServiceConnected(ComponentName name, IBinder service) { 1243 synchronized (mMethodMap) { 1244 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) { 1245 mCurMethod = IInputMethod.Stub.asInterface(service); 1246 if (mCurToken == null) { 1247 Slog.w(TAG, "Service connected without a token!"); 1248 unbindCurrentMethodLocked(false, false); 1249 return; 1250 } 1251 // Remove commands relating to the previous service. Otherwise WindowManagerService 1252 // will reject the command because the token attached to these messages is invalid. 1253 mCaller.removeMessages(MSG_SHOW_SOFT_INPUT); 1254 mCaller.removeMessages(MSG_HIDE_SOFT_INPUT); 1255 if (true || DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken); 1256 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1257 MSG_ATTACH_TOKEN, mCurMethod, mCurToken)); 1258 if (mCurClient != null) { 1259 if (DEBUG) Slog.v(TAG, "Creating first session while with client " 1260 + mCurClient); 1261 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1262 MSG_CREATE_SESSION, mCurMethod, 1263 new MethodCallback(mCurMethod, this))); 1264 } 1265 } 1266 } 1267 } 1268 1269 void onSessionCreated(IInputMethod method, IInputMethodSession session) { 1270 synchronized (mMethodMap) { 1271 if (mCurMethod != null && method != null 1272 && mCurMethod.asBinder() == method.asBinder()) { 1273 if (mCurClient != null) { 1274 mCurClient.curSession = new SessionState(mCurClient, 1275 method, session); 1276 mCurClient.sessionRequested = false; 1277 InputBindResult res = attachNewInputLocked(true); 1278 if (res.method != null) { 1279 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO( 1280 MSG_BIND_METHOD, mCurClient.client, res)); 1281 } 1282 } 1283 } 1284 } 1285 } 1286 1287 void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) { 1288 if (mVisibleBound) { 1289 mContext.unbindService(mVisibleConnection); 1290 mVisibleBound = false; 1291 } 1292 1293 if (mHaveConnection) { 1294 mContext.unbindService(this); 1295 mHaveConnection = false; 1296 } 1297 1298 if (mCurToken != null) { 1299 try { 1300 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken); 1301 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) { 1302 // The current IME is shown. Hence an IME switch (transition) is happening. 1303 mWindowManagerService.saveLastInputMethodWindowForTransition(); 1304 } 1305 mIWindowManager.removeWindowToken(mCurToken); 1306 } catch (RemoteException e) { 1307 } 1308 mCurToken = null; 1309 } 1310 1311 mCurId = null; 1312 clearCurMethodLocked(); 1313 1314 if (reportToClient && mCurClient != null) { 1315 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1316 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1317 } 1318 } 1319 1320 private void finishSession(SessionState sessionState) { 1321 if (sessionState != null && sessionState.session != null) { 1322 try { 1323 sessionState.session.finishSession(); 1324 } catch (RemoteException e) { 1325 Slog.w(TAG, "Session failed to close due to remote exception", e); 1326 setImeWindowVisibilityStatusHiddenLocked(); 1327 } 1328 } 1329 } 1330 1331 void clearCurMethodLocked() { 1332 if (mCurMethod != null) { 1333 for (ClientState cs : mClients.values()) { 1334 cs.sessionRequested = false; 1335 finishSession(cs.curSession); 1336 cs.curSession = null; 1337 } 1338 1339 finishSession(mEnabledSession); 1340 mEnabledSession = null; 1341 mCurMethod = null; 1342 } 1343 if (mStatusBar != null) { 1344 mStatusBar.setIconVisibility("ime", false); 1345 } 1346 } 1347 1348 @Override 1349 public void onServiceDisconnected(ComponentName name) { 1350 synchronized (mMethodMap) { 1351 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name 1352 + " mCurIntent=" + mCurIntent); 1353 if (mCurMethod != null && mCurIntent != null 1354 && name.equals(mCurIntent.getComponent())) { 1355 clearCurMethodLocked(); 1356 // We consider this to be a new bind attempt, since the system 1357 // should now try to restart the service for us. 1358 mLastBindTime = SystemClock.uptimeMillis(); 1359 mShowRequested = mInputShown; 1360 mInputShown = false; 1361 if (mCurClient != null) { 1362 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO( 1363 MSG_UNBIND_METHOD, mCurSeq, mCurClient.client)); 1364 } 1365 } 1366 } 1367 } 1368 1369 @Override 1370 public void updateStatusIcon(IBinder token, String packageName, int iconId) { 1371 int uid = Binder.getCallingUid(); 1372 long ident = Binder.clearCallingIdentity(); 1373 try { 1374 if (token == null || mCurToken != token) { 1375 Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token); 1376 return; 1377 } 1378 1379 synchronized (mMethodMap) { 1380 if (iconId == 0) { 1381 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method"); 1382 if (mStatusBar != null) { 1383 mStatusBar.setIconVisibility("ime", false); 1384 } 1385 } else if (packageName != null) { 1386 if (DEBUG) Slog.d(TAG, "show a small icon for the input method"); 1387 CharSequence contentDescription = null; 1388 try { 1389 // Use PackageManager to load label 1390 final PackageManager packageManager = mContext.getPackageManager(); 1391 contentDescription = packageManager.getApplicationLabel( 1392 mIPackageManager.getApplicationInfo(packageName, 0, 1393 mSettings.getCurrentUserId())); 1394 } catch (RemoteException e) { 1395 /* ignore */ 1396 } 1397 if (mStatusBar != null) { 1398 mStatusBar.setIcon("ime", packageName, iconId, 0, 1399 contentDescription != null 1400 ? contentDescription.toString() : null); 1401 mStatusBar.setIconVisibility("ime", true); 1402 } 1403 } 1404 } 1405 } finally { 1406 Binder.restoreCallingIdentity(ident); 1407 } 1408 } 1409 1410 private boolean needsToShowImeSwitchOngoingNotification() { 1411 if (!mShowOngoingImeSwitcherForPhones) return false; 1412 if (isScreenLocked()) return false; 1413 synchronized (mMethodMap) { 1414 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 1415 final int N = imis.size(); 1416 if (N > 2) return true; 1417 if (N < 1) return false; 1418 int nonAuxCount = 0; 1419 int auxCount = 0; 1420 InputMethodSubtype nonAuxSubtype = null; 1421 InputMethodSubtype auxSubtype = null; 1422 for(int i = 0; i < N; ++i) { 1423 final InputMethodInfo imi = imis.get(i); 1424 final List<InputMethodSubtype> subtypes = 1425 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 1426 final int subtypeCount = subtypes.size(); 1427 if (subtypeCount == 0) { 1428 ++nonAuxCount; 1429 } else { 1430 for (int j = 0; j < subtypeCount; ++j) { 1431 final InputMethodSubtype subtype = subtypes.get(j); 1432 if (!subtype.isAuxiliary()) { 1433 ++nonAuxCount; 1434 nonAuxSubtype = subtype; 1435 } else { 1436 ++auxCount; 1437 auxSubtype = subtype; 1438 } 1439 } 1440 } 1441 } 1442 if (nonAuxCount > 1 || auxCount > 1) { 1443 return true; 1444 } else if (nonAuxCount == 1 && auxCount == 1) { 1445 if (nonAuxSubtype != null && auxSubtype != null 1446 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale()) 1447 || auxSubtype.overridesImplicitlyEnabledSubtype() 1448 || nonAuxSubtype.overridesImplicitlyEnabledSubtype()) 1449 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) { 1450 return false; 1451 } 1452 return true; 1453 } 1454 return false; 1455 } 1456 } 1457 1458 // Caution! This method is called in this class. Handle multi-user carefully 1459 @SuppressWarnings("deprecation") 1460 @Override 1461 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) { 1462 final long ident = Binder.clearCallingIdentity(); 1463 try { 1464 if (token == null || mCurToken != token) { 1465 int uid = Binder.getCallingUid(); 1466 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token); 1467 return; 1468 } 1469 1470 synchronized (mMethodMap) { 1471 mImeWindowVis = vis; 1472 mBackDisposition = backDisposition; 1473 if (mStatusBar != null) { 1474 mStatusBar.setImeWindowStatus(token, vis, backDisposition); 1475 } 1476 final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0; 1477 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 1478 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) { 1479 // Used to load label 1480 final PackageManager pm = mContext.getPackageManager(); 1481 final CharSequence title = mRes.getText( 1482 com.android.internal.R.string.select_input_method); 1483 final CharSequence imiLabel = imi.loadLabel(pm); 1484 final CharSequence summary = mCurrentSubtype != null 1485 ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext, 1486 imi.getPackageName(), imi.getServiceInfo().applicationInfo), 1487 (TextUtils.isEmpty(imiLabel) ? 1488 "" : " - " + imiLabel)) 1489 : imiLabel; 1490 1491 mImeSwitcherNotification.setLatestEventInfo( 1492 mContext, title, summary, mImeSwitchPendingIntent); 1493 if (mNotificationManager != null) { 1494 if (DEBUG) { 1495 Slog.d(TAG, "--- show notification: label = " + imiLabel 1496 + ", summary = " + summary); 1497 } 1498 mNotificationManager.notifyAsUser(null, 1499 com.android.internal.R.string.select_input_method, 1500 mImeSwitcherNotification, UserHandle.ALL); 1501 mNotificationShown = true; 1502 } 1503 } else { 1504 if (mNotificationShown && mNotificationManager != null) { 1505 if (DEBUG) { 1506 Slog.d(TAG, "--- hide notification"); 1507 } 1508 mNotificationManager.cancelAsUser(null, 1509 com.android.internal.R.string.select_input_method, UserHandle.ALL); 1510 mNotificationShown = false; 1511 } 1512 } 1513 } 1514 } finally { 1515 Binder.restoreCallingIdentity(ident); 1516 } 1517 } 1518 1519 @Override 1520 public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) { 1521 if (!calledFromValidUser()) { 1522 return; 1523 } 1524 synchronized (mMethodMap) { 1525 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 1526 for (int i = 0; i < spans.length; ++i) { 1527 SuggestionSpan ss = spans[i]; 1528 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) { 1529 mSecureSuggestionSpans.put(ss, currentImi); 1530 } 1531 } 1532 } 1533 } 1534 1535 @Override 1536 public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) { 1537 if (!calledFromValidUser()) { 1538 return false; 1539 } 1540 synchronized (mMethodMap) { 1541 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span); 1542 // TODO: Do not send the intent if the process of the targetImi is already dead. 1543 if (targetImi != null) { 1544 final String[] suggestions = span.getSuggestions(); 1545 if (index < 0 || index >= suggestions.length) return false; 1546 final String className = span.getNotificationTargetClassName(); 1547 final Intent intent = new Intent(); 1548 // Ensures that only a class in the original IME package will receive the 1549 // notification. 1550 intent.setClassName(targetImi.getPackageName(), className); 1551 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED); 1552 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString); 1553 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]); 1554 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode()); 1555 final long ident = Binder.clearCallingIdentity(); 1556 try { 1557 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 1558 } finally { 1559 Binder.restoreCallingIdentity(ident); 1560 } 1561 return true; 1562 } 1563 } 1564 return false; 1565 } 1566 1567 void updateFromSettingsLocked(boolean enabledMayChange) { 1568 if (enabledMayChange) { 1569 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 1570 for (int i=0; i<enabled.size(); i++) { 1571 // We allow the user to select "disabled until used" apps, so if they 1572 // are enabling one of those here we now need to make it enabled. 1573 InputMethodInfo imm = enabled.get(i); 1574 try { 1575 ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(), 1576 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 1577 mSettings.getCurrentUserId()); 1578 if (ai.enabledSetting 1579 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) { 1580 mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(), 1581 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT, 1582 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId()); 1583 } 1584 } catch (RemoteException e) { 1585 } 1586 } 1587 } 1588 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and 1589 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in 1590 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not 1591 // enabled. 1592 String id = mSettings.getSelectedInputMethod(); 1593 // There is no input method selected, try to choose new applicable input method. 1594 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) { 1595 id = mSettings.getSelectedInputMethod(); 1596 } 1597 if (!TextUtils.isEmpty(id)) { 1598 try { 1599 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id)); 1600 } catch (IllegalArgumentException e) { 1601 Slog.w(TAG, "Unknown input method from prefs: " + id, e); 1602 mCurMethodId = null; 1603 unbindCurrentMethodLocked(true, false); 1604 } 1605 mShortcutInputMethodsAndSubtypes.clear(); 1606 } else { 1607 // There is no longer an input method set, so stop any current one. 1608 mCurMethodId = null; 1609 unbindCurrentMethodLocked(true, false); 1610 } 1611 } 1612 1613 /* package */ void setInputMethodLocked(String id, int subtypeId) { 1614 InputMethodInfo info = mMethodMap.get(id); 1615 if (info == null) { 1616 throw new IllegalArgumentException("Unknown id: " + id); 1617 } 1618 1619 // See if we need to notify a subtype change within the same IME. 1620 if (id.equals(mCurMethodId)) { 1621 final int subtypeCount = info.getSubtypeCount(); 1622 if (subtypeCount <= 0) { 1623 return; 1624 } 1625 final InputMethodSubtype oldSubtype = mCurrentSubtype; 1626 final InputMethodSubtype newSubtype; 1627 if (subtypeId >= 0 && subtypeId < subtypeCount) { 1628 newSubtype = info.getSubtypeAt(subtypeId); 1629 } else { 1630 // If subtype is null, try to find the most applicable one from 1631 // getCurrentInputMethodSubtype. 1632 newSubtype = getCurrentInputMethodSubtypeLocked(); 1633 } 1634 if (newSubtype == null || oldSubtype == null) { 1635 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype 1636 + ", new subtype = " + newSubtype); 1637 return; 1638 } 1639 if (newSubtype != oldSubtype) { 1640 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true); 1641 if (mCurMethod != null) { 1642 try { 1643 refreshImeWindowVisibilityLocked(); 1644 mCurMethod.changeInputMethodSubtype(newSubtype); 1645 } catch (RemoteException e) { 1646 Slog.w(TAG, "Failed to call changeInputMethodSubtype"); 1647 } 1648 } 1649 } 1650 return; 1651 } 1652 1653 // Changing to a different IME. 1654 final long ident = Binder.clearCallingIdentity(); 1655 try { 1656 // Set a subtype to this input method. 1657 // subtypeId the name of a subtype which will be set. 1658 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false); 1659 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked() 1660 // because mCurMethodId is stored as a history in 1661 // setSelectedInputMethodAndSubtypeLocked(). 1662 mCurMethodId = id; 1663 1664 if (ActivityManagerNative.isSystemReady()) { 1665 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED); 1666 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); 1667 intent.putExtra("input_method_id", id); 1668 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT); 1669 } 1670 unbindCurrentClientLocked(); 1671 } finally { 1672 Binder.restoreCallingIdentity(ident); 1673 } 1674 } 1675 1676 @Override 1677 public boolean showSoftInput(IInputMethodClient client, int flags, 1678 ResultReceiver resultReceiver) { 1679 if (!calledFromValidUser()) { 1680 return false; 1681 } 1682 int uid = Binder.getCallingUid(); 1683 long ident = Binder.clearCallingIdentity(); 1684 try { 1685 synchronized (mMethodMap) { 1686 if (mCurClient == null || client == null 1687 || mCurClient.client.asBinder() != client.asBinder()) { 1688 try { 1689 // We need to check if this is the current client with 1690 // focus in the window manager, to allow this call to 1691 // be made before input is started in it. 1692 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1693 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client); 1694 return false; 1695 } 1696 } catch (RemoteException e) { 1697 return false; 1698 } 1699 } 1700 1701 if (true || DEBUG) Slog.v(TAG, "Client requesting input be shown"); 1702 return showCurrentInputLocked(flags, resultReceiver); 1703 } 1704 } finally { 1705 Binder.restoreCallingIdentity(ident); 1706 } 1707 } 1708 1709 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1710 mShowRequested = true; 1711 if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) { 1712 mShowExplicitlyRequested = true; 1713 } 1714 if ((flags&InputMethodManager.SHOW_FORCED) != 0) { 1715 mShowExplicitlyRequested = true; 1716 mShowForced = true; 1717 } 1718 1719 if (!mSystemReady) { 1720 return false; 1721 } 1722 1723 boolean res = false; 1724 if (mCurMethod != null) { 1725 if (true ||DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken, 1726 new RuntimeException("here").fillInStackTrace()); 1727 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO( 1728 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod, 1729 resultReceiver)); 1730 mInputShown = true; 1731 if (mHaveConnection && !mVisibleBound) { 1732 bindCurrentInputMethodService( 1733 mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE); 1734 mVisibleBound = true; 1735 } 1736 res = true; 1737 } else if (mHaveConnection && SystemClock.uptimeMillis() 1738 >= (mLastBindTime+TIME_TO_RECONNECT)) { 1739 // The client has asked to have the input method shown, but 1740 // we have been sitting here too long with a connection to the 1741 // service and no interface received, so let's disconnect/connect 1742 // to try to prod things along. 1743 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId, 1744 SystemClock.uptimeMillis()-mLastBindTime,1); 1745 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()"); 1746 mContext.unbindService(this); 1747 bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE 1748 | Context.BIND_NOT_VISIBLE); 1749 } else { 1750 if (DEBUG) { 1751 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = " 1752 + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis())); 1753 } 1754 } 1755 1756 return res; 1757 } 1758 1759 @Override 1760 public boolean hideSoftInput(IInputMethodClient client, int flags, 1761 ResultReceiver resultReceiver) { 1762 if (!calledFromValidUser()) { 1763 return false; 1764 } 1765 int uid = Binder.getCallingUid(); 1766 long ident = Binder.clearCallingIdentity(); 1767 try { 1768 synchronized (mMethodMap) { 1769 if (mCurClient == null || client == null 1770 || mCurClient.client.asBinder() != client.asBinder()) { 1771 try { 1772 // We need to check if this is the current client with 1773 // focus in the window manager, to allow this call to 1774 // be made before input is started in it. 1775 if (!mIWindowManager.inputMethodClientHasFocus(client)) { 1776 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid " 1777 + uid + ": " + client); 1778 setImeWindowVisibilityStatusHiddenLocked(); 1779 return false; 1780 } 1781 } catch (RemoteException e) { 1782 setImeWindowVisibilityStatusHiddenLocked(); 1783 return false; 1784 } 1785 } 1786 1787 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden"); 1788 return hideCurrentInputLocked(flags, resultReceiver); 1789 } 1790 } finally { 1791 Binder.restoreCallingIdentity(ident); 1792 } 1793 } 1794 1795 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) { 1796 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0 1797 && (mShowExplicitlyRequested || mShowForced)) { 1798 if (true ||DEBUG) Slog.v(TAG, 1799 "Not hiding: explicit show not cancelled by non-explicit hide"); 1800 return false; 1801 } 1802 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) { 1803 if (true ||DEBUG) Slog.v(TAG, 1804 "Not hiding: forced show not cancelled by not-always hide"); 1805 return false; 1806 } 1807 boolean res; 1808 if (mInputShown && mCurMethod != null) { 1809 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO( 1810 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver)); 1811 res = true; 1812 } else { 1813 res = false; 1814 } 1815 if (mHaveConnection && mVisibleBound) { 1816 mContext.unbindService(mVisibleConnection); 1817 mVisibleBound = false; 1818 } 1819 mInputShown = false; 1820 mShowRequested = false; 1821 mShowExplicitlyRequested = false; 1822 mShowForced = false; 1823 return res; 1824 } 1825 1826 @Override 1827 public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken, 1828 int controlFlags, int softInputMode, int windowFlags, 1829 EditorInfo attribute, IInputContext inputContext) { 1830 // Needs to check the validity before clearing calling identity 1831 final boolean calledFromValidUser = calledFromValidUser(); 1832 1833 InputBindResult res = null; 1834 long ident = Binder.clearCallingIdentity(); 1835 try { 1836 synchronized (mMethodMap) { 1837 if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder() 1838 + " controlFlags=#" + Integer.toHexString(controlFlags) 1839 + " softInputMode=#" + Integer.toHexString(softInputMode) 1840 + " windowFlags=#" + Integer.toHexString(windowFlags)); 1841 1842 ClientState cs = mClients.get(client.asBinder()); 1843 if (cs == null) { 1844 throw new IllegalArgumentException("unknown client " 1845 + client.asBinder()); 1846 } 1847 1848 try { 1849 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) { 1850 // Check with the window manager to make sure this client actually 1851 // has a window with focus. If not, reject. This is thread safe 1852 // because if the focus changes some time before or after, the 1853 // next client receiving focus that has any interest in input will 1854 // be calling through here after that change happens. 1855 Slog.w(TAG, "Focus gain on non-focused client " + cs.client 1856 + " (uid=" + cs.uid + " pid=" + cs.pid + ")"); 1857 return null; 1858 } 1859 } catch (RemoteException e) { 1860 } 1861 1862 if (!calledFromValidUser) { 1863 Slog.w(TAG, "A background user is requesting window. Hiding IME."); 1864 Slog.w(TAG, "If you want to interect with IME, you need " 1865 + "android.permission.INTERACT_ACROSS_USERS_FULL"); 1866 hideCurrentInputLocked(0, null); 1867 return null; 1868 } 1869 1870 if (mCurFocusedWindow == windowToken) { 1871 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client 1872 + " attribute=" + attribute + ", token = " + windowToken); 1873 if (attribute != null) { 1874 return startInputUncheckedLocked(cs, inputContext, attribute, 1875 controlFlags); 1876 } 1877 return null; 1878 } 1879 mCurFocusedWindow = windowToken; 1880 1881 // Should we auto-show the IME even if the caller has not 1882 // specified what should be done with it? 1883 // We only do this automatically if the window can resize 1884 // to accommodate the IME (so what the user sees will give 1885 // them good context without input information being obscured 1886 // by the IME) or if running on a large screen where there 1887 // is more room for the target window + IME. 1888 final boolean doAutoShow = 1889 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST) 1890 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE 1891 || mRes.getConfiguration().isLayoutSizeAtLeast( 1892 Configuration.SCREENLAYOUT_SIZE_LARGE); 1893 final boolean isTextEditor = 1894 (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0; 1895 1896 // We want to start input before showing the IME, but after closing 1897 // it. We want to do this after closing it to help the IME disappear 1898 // more quickly (not get stuck behind it initializing itself for the 1899 // new focused input, even if its window wants to hide the IME). 1900 boolean didStart = false; 1901 1902 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) { 1903 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED: 1904 if (!isTextEditor || !doAutoShow) { 1905 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) { 1906 // There is no focus view, and this window will 1907 // be behind any soft input window, so hide the 1908 // soft input window if it is shown. 1909 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input"); 1910 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null); 1911 } 1912 } else if (isTextEditor && doAutoShow && (softInputMode & 1913 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1914 // There is a focus view, and we are navigating forward 1915 // into the window, so show the input window for the user. 1916 // We only do this automatically if the window can resize 1917 // to accommodate the IME (so what the user sees will give 1918 // them good context without input information being obscured 1919 // by the IME) or if running on a large screen where there 1920 // is more room for the target window + IME. 1921 if (DEBUG) Slog.v(TAG, "Unspecified window will show input"); 1922 if (attribute != null) { 1923 res = startInputUncheckedLocked(cs, inputContext, attribute, 1924 controlFlags); 1925 didStart = true; 1926 } 1927 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1928 } 1929 break; 1930 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED: 1931 // Do nothing. 1932 break; 1933 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN: 1934 if ((softInputMode & 1935 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1936 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward"); 1937 hideCurrentInputLocked(0, null); 1938 } 1939 break; 1940 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN: 1941 if (DEBUG) Slog.v(TAG, "Window asks to hide input"); 1942 hideCurrentInputLocked(0, null); 1943 break; 1944 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE: 1945 if ((softInputMode & 1946 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) { 1947 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward"); 1948 if (attribute != null) { 1949 res = startInputUncheckedLocked(cs, inputContext, attribute, 1950 controlFlags); 1951 didStart = true; 1952 } 1953 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1954 } 1955 break; 1956 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE: 1957 if (DEBUG) Slog.v(TAG, "Window asks to always show input"); 1958 if (attribute != null) { 1959 res = startInputUncheckedLocked(cs, inputContext, attribute, 1960 controlFlags); 1961 didStart = true; 1962 } 1963 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null); 1964 break; 1965 } 1966 1967 if (!didStart && attribute != null) { 1968 res = startInputUncheckedLocked(cs, inputContext, attribute, 1969 controlFlags); 1970 } 1971 } 1972 } finally { 1973 Binder.restoreCallingIdentity(ident); 1974 } 1975 1976 return res; 1977 } 1978 1979 @Override 1980 public void showInputMethodPickerFromClient(IInputMethodClient client) { 1981 if (!calledFromValidUser()) { 1982 return; 1983 } 1984 synchronized (mMethodMap) { 1985 if (mCurClient == null || client == null 1986 || mCurClient.client.asBinder() != client.asBinder()) { 1987 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid " 1988 + Binder.getCallingUid() + ": " + client); 1989 } 1990 1991 // Always call subtype picker, because subtype picker is a superset of input method 1992 // picker. 1993 mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER); 1994 } 1995 } 1996 1997 @Override 1998 public void setInputMethod(IBinder token, String id) { 1999 if (!calledFromValidUser()) { 2000 return; 2001 } 2002 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID); 2003 } 2004 2005 @Override 2006 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) { 2007 if (!calledFromValidUser()) { 2008 return; 2009 } 2010 synchronized (mMethodMap) { 2011 if (subtype != null) { 2012 setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode( 2013 mMethodMap.get(id), subtype.hashCode())); 2014 } else { 2015 setInputMethod(token, id); 2016 } 2017 } 2018 } 2019 2020 @Override 2021 public void showInputMethodAndSubtypeEnablerFromClient( 2022 IInputMethodClient client, String inputMethodId) { 2023 if (!calledFromValidUser()) { 2024 return; 2025 } 2026 synchronized (mMethodMap) { 2027 if (mCurClient == null || client == null 2028 || mCurClient.client.asBinder() != client.asBinder()) { 2029 Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client); 2030 } 2031 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO( 2032 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId)); 2033 } 2034 } 2035 2036 @Override 2037 public boolean switchToLastInputMethod(IBinder token) { 2038 if (!calledFromValidUser()) { 2039 return false; 2040 } 2041 synchronized (mMethodMap) { 2042 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 2043 final InputMethodInfo lastImi; 2044 if (lastIme != null) { 2045 lastImi = mMethodMap.get(lastIme.first); 2046 } else { 2047 lastImi = null; 2048 } 2049 String targetLastImiId = null; 2050 int subtypeId = NOT_A_SUBTYPE_ID; 2051 if (lastIme != null && lastImi != null) { 2052 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId); 2053 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 2054 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID 2055 : mCurrentSubtype.hashCode(); 2056 // If the last IME is the same as the current IME and the last subtype is not 2057 // defined, there is no need to switch to the last IME. 2058 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) { 2059 targetLastImiId = lastIme.first; 2060 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 2061 } 2062 } 2063 2064 if (TextUtils.isEmpty(targetLastImiId) 2065 && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) { 2066 // This is a safety net. If the currentSubtype can't be added to the history 2067 // and the framework couldn't find the last ime, we will make the last ime be 2068 // the most applicable enabled keyboard subtype of the system imes. 2069 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked(); 2070 if (enabled != null) { 2071 final int N = enabled.size(); 2072 final String locale = mCurrentSubtype == null 2073 ? mRes.getConfiguration().locale.toString() 2074 : mCurrentSubtype.getLocale(); 2075 for (int i = 0; i < N; ++i) { 2076 final InputMethodInfo imi = enabled.get(i); 2077 if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) { 2078 InputMethodSubtype keyboardSubtype = 2079 InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes, 2080 InputMethodUtils.getSubtypes(imi), 2081 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true); 2082 if (keyboardSubtype != null) { 2083 targetLastImiId = imi.getId(); 2084 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2085 imi, keyboardSubtype.hashCode()); 2086 if(keyboardSubtype.getLocale().equals(locale)) { 2087 break; 2088 } 2089 } 2090 } 2091 } 2092 } 2093 } 2094 2095 if (!TextUtils.isEmpty(targetLastImiId)) { 2096 if (DEBUG) { 2097 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second 2098 + ", from: " + mCurMethodId + ", " + subtypeId); 2099 } 2100 setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId); 2101 return true; 2102 } else { 2103 return false; 2104 } 2105 } 2106 } 2107 2108 @Override 2109 public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) { 2110 if (!calledFromValidUser()) { 2111 return false; 2112 } 2113 synchronized (mMethodMap) { 2114 final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( 2115 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype); 2116 if (nextSubtype == null) { 2117 return false; 2118 } 2119 setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId); 2120 return true; 2121 } 2122 } 2123 2124 @Override 2125 public InputMethodSubtype getLastInputMethodSubtype() { 2126 if (!calledFromValidUser()) { 2127 return null; 2128 } 2129 synchronized (mMethodMap) { 2130 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked(); 2131 // TODO: Handle the case of the last IME with no subtypes 2132 if (lastIme == null || TextUtils.isEmpty(lastIme.first) 2133 || TextUtils.isEmpty(lastIme.second)) return null; 2134 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first); 2135 if (lastImi == null) return null; 2136 try { 2137 final int lastSubtypeHash = Integer.valueOf(lastIme.second); 2138 final int lastSubtypeId = 2139 InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash); 2140 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) { 2141 return null; 2142 } 2143 return lastImi.getSubtypeAt(lastSubtypeId); 2144 } catch (NumberFormatException e) { 2145 return null; 2146 } 2147 } 2148 } 2149 2150 @Override 2151 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) { 2152 if (!calledFromValidUser()) { 2153 return; 2154 } 2155 // By this IPC call, only a process which shares the same uid with the IME can add 2156 // additional input method subtypes to the IME. 2157 if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return; 2158 synchronized (mMethodMap) { 2159 final InputMethodInfo imi = mMethodMap.get(imiId); 2160 if (imi == null) return; 2161 final String[] packageInfos; 2162 try { 2163 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid()); 2164 } catch (RemoteException e) { 2165 Slog.e(TAG, "Failed to get package infos"); 2166 return; 2167 } 2168 if (packageInfos != null) { 2169 final int packageNum = packageInfos.length; 2170 for (int i = 0; i < packageNum; ++i) { 2171 if (packageInfos[i].equals(imi.getPackageName())) { 2172 mFileManager.addInputMethodSubtypes(imi, subtypes); 2173 final long ident = Binder.clearCallingIdentity(); 2174 try { 2175 buildInputMethodListLocked(mMethodList, mMethodMap, 2176 false /* resetDefaultEnabledIme */); 2177 } finally { 2178 Binder.restoreCallingIdentity(ident); 2179 } 2180 return; 2181 } 2182 } 2183 } 2184 } 2185 return; 2186 } 2187 2188 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) { 2189 synchronized (mMethodMap) { 2190 if (token == null) { 2191 if (mContext.checkCallingOrSelfPermission( 2192 android.Manifest.permission.WRITE_SECURE_SETTINGS) 2193 != PackageManager.PERMISSION_GRANTED) { 2194 throw new SecurityException( 2195 "Using null token requires permission " 2196 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 2197 } 2198 } else if (mCurToken != token) { 2199 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid() 2200 + " token: " + token); 2201 return; 2202 } 2203 2204 final long ident = Binder.clearCallingIdentity(); 2205 try { 2206 setInputMethodLocked(id, subtypeId); 2207 } finally { 2208 Binder.restoreCallingIdentity(ident); 2209 } 2210 } 2211 } 2212 2213 @Override 2214 public void hideMySoftInput(IBinder token, int flags) { 2215 if (!calledFromValidUser()) { 2216 return; 2217 } 2218 synchronized (mMethodMap) { 2219 if (token == null || mCurToken != token) { 2220 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid " 2221 + Binder.getCallingUid() + " token: " + token); 2222 return; 2223 } 2224 long ident = Binder.clearCallingIdentity(); 2225 try { 2226 hideCurrentInputLocked(flags, null); 2227 } finally { 2228 Binder.restoreCallingIdentity(ident); 2229 } 2230 } 2231 } 2232 2233 @Override 2234 public void showMySoftInput(IBinder token, int flags) { 2235 if (!calledFromValidUser()) { 2236 return; 2237 } 2238 synchronized (mMethodMap) { 2239 if (token == null || mCurToken != token) { 2240 Slog.w(TAG, "Ignoring showMySoftInput of uid " 2241 + Binder.getCallingUid() + " token: " + token); 2242 return; 2243 } 2244 long ident = Binder.clearCallingIdentity(); 2245 try { 2246 showCurrentInputLocked(flags, null); 2247 } finally { 2248 Binder.restoreCallingIdentity(ident); 2249 } 2250 } 2251 } 2252 2253 void setEnabledSessionInMainThread(SessionState session) { 2254 if (mEnabledSession != session) { 2255 if (mEnabledSession != null) { 2256 try { 2257 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession); 2258 mEnabledSession.method.setSessionEnabled( 2259 mEnabledSession.session, false); 2260 } catch (RemoteException e) { 2261 } 2262 } 2263 mEnabledSession = session; 2264 try { 2265 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession); 2266 session.method.setSessionEnabled( 2267 session.session, true); 2268 } catch (RemoteException e) { 2269 } 2270 } 2271 } 2272 2273 @Override 2274 public boolean handleMessage(Message msg) { 2275 SomeArgs args; 2276 switch (msg.what) { 2277 case MSG_SHOW_IM_PICKER: 2278 showInputMethodMenu(); 2279 return true; 2280 2281 case MSG_SHOW_IM_SUBTYPE_PICKER: 2282 showInputMethodSubtypeMenu(); 2283 return true; 2284 2285 case MSG_SHOW_IM_SUBTYPE_ENABLER: 2286 args = (SomeArgs)msg.obj; 2287 showInputMethodAndSubtypeEnabler((String)args.arg1); 2288 args.recycle(); 2289 return true; 2290 2291 case MSG_SHOW_IM_CONFIG: 2292 showConfigureInputMethods(); 2293 return true; 2294 2295 // --------------------------------------------------------- 2296 2297 case MSG_UNBIND_INPUT: 2298 try { 2299 ((IInputMethod)msg.obj).unbindInput(); 2300 } catch (RemoteException e) { 2301 // There is nothing interesting about the method dying. 2302 } 2303 return true; 2304 case MSG_BIND_INPUT: 2305 args = (SomeArgs)msg.obj; 2306 try { 2307 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2); 2308 } catch (RemoteException e) { 2309 } 2310 args.recycle(); 2311 return true; 2312 case MSG_SHOW_SOFT_INPUT: 2313 args = (SomeArgs)msg.obj; 2314 try { 2315 if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput(" 2316 + msg.arg1 + ", " + args.arg2 + ")"); 2317 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, 2318 (ResultReceiver)args.arg2); 2319 } catch (RemoteException e) { 2320 } 2321 args.recycle(); 2322 return true; 2323 case MSG_HIDE_SOFT_INPUT: 2324 args = (SomeArgs)msg.obj; 2325 try { 2326 if (true || DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, " 2327 + args.arg2 + ")"); 2328 ((IInputMethod)args.arg1).hideSoftInput(0, 2329 (ResultReceiver)args.arg2); 2330 } catch (RemoteException e) { 2331 } 2332 args.recycle(); 2333 return true; 2334 case MSG_ATTACH_TOKEN: 2335 args = (SomeArgs)msg.obj; 2336 try { 2337 if (true || DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2); 2338 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2); 2339 } catch (RemoteException e) { 2340 } 2341 args.recycle(); 2342 return true; 2343 case MSG_CREATE_SESSION: 2344 args = (SomeArgs)msg.obj; 2345 try { 2346 ((IInputMethod)args.arg1).createSession( 2347 (IInputMethodCallback)args.arg2); 2348 } catch (RemoteException e) { 2349 } 2350 args.recycle(); 2351 return true; 2352 // --------------------------------------------------------- 2353 2354 case MSG_START_INPUT: 2355 args = (SomeArgs)msg.obj; 2356 try { 2357 SessionState session = (SessionState)args.arg1; 2358 setEnabledSessionInMainThread(session); 2359 session.method.startInput((IInputContext)args.arg2, 2360 (EditorInfo)args.arg3); 2361 } catch (RemoteException e) { 2362 } 2363 args.recycle(); 2364 return true; 2365 case MSG_RESTART_INPUT: 2366 args = (SomeArgs)msg.obj; 2367 try { 2368 SessionState session = (SessionState)args.arg1; 2369 setEnabledSessionInMainThread(session); 2370 session.method.restartInput((IInputContext)args.arg2, 2371 (EditorInfo)args.arg3); 2372 } catch (RemoteException e) { 2373 } 2374 args.recycle(); 2375 return true; 2376 2377 // --------------------------------------------------------- 2378 2379 case MSG_UNBIND_METHOD: 2380 try { 2381 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1); 2382 } catch (RemoteException e) { 2383 // There is nothing interesting about the last client dying. 2384 } 2385 return true; 2386 case MSG_BIND_METHOD: 2387 args = (SomeArgs)msg.obj; 2388 try { 2389 ((IInputMethodClient)args.arg1).onBindMethod( 2390 (InputBindResult)args.arg2); 2391 } catch (RemoteException e) { 2392 Slog.w(TAG, "Client died receiving input method " + args.arg2); 2393 } 2394 args.recycle(); 2395 return true; 2396 case MSG_SET_ACTIVE: 2397 try { 2398 ((ClientState)msg.obj).client.setActive(msg.arg1 != 0); 2399 } catch (RemoteException e) { 2400 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid " 2401 + ((ClientState)msg.obj).pid + " uid " 2402 + ((ClientState)msg.obj).uid); 2403 } 2404 return true; 2405 2406 // -------------------------------------------------------------- 2407 case MSG_HARD_KEYBOARD_SWITCH_CHANGED: 2408 mHardKeyboardListener.handleHardKeyboardStatusChange( 2409 msg.arg1 == 1, msg.arg2 == 1); 2410 return true; 2411 } 2412 return false; 2413 } 2414 2415 private boolean chooseNewDefaultIMELocked() { 2416 final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME( 2417 mSettings.getEnabledInputMethodListLocked()); 2418 if (imi != null) { 2419 if (DEBUG) { 2420 Slog.d(TAG, "New default IME was selected: " + imi.getId()); 2421 } 2422 resetSelectedInputMethodAndSubtypeLocked(imi.getId()); 2423 return true; 2424 } 2425 2426 return false; 2427 } 2428 2429 void buildInputMethodListLocked(ArrayList<InputMethodInfo> list, 2430 HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) { 2431 if (DEBUG) { 2432 Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme 2433 + " \n ------ \n" + getStackTrace()); 2434 } 2435 list.clear(); 2436 map.clear(); 2437 2438 // Use for queryIntentServicesAsUser 2439 final PackageManager pm = mContext.getPackageManager(); 2440 String disabledSysImes = mSettings.getDisabledSystemInputMethods(); 2441 if (disabledSysImes == null) disabledSysImes = ""; 2442 2443 final List<ResolveInfo> services = pm.queryIntentServicesAsUser( 2444 new Intent(InputMethod.SERVICE_INTERFACE), 2445 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS, 2446 mSettings.getCurrentUserId()); 2447 2448 final HashMap<String, List<InputMethodSubtype>> additionalSubtypes = 2449 mFileManager.getAllAdditionalInputMethodSubtypes(); 2450 for (int i = 0; i < services.size(); ++i) { 2451 ResolveInfo ri = services.get(i); 2452 ServiceInfo si = ri.serviceInfo; 2453 ComponentName compName = new ComponentName(si.packageName, si.name); 2454 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals( 2455 si.permission)) { 2456 Slog.w(TAG, "Skipping input method " + compName 2457 + ": it does not require the permission " 2458 + android.Manifest.permission.BIND_INPUT_METHOD); 2459 continue; 2460 } 2461 2462 if (DEBUG) Slog.d(TAG, "Checking " + compName); 2463 2464 try { 2465 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes); 2466 list.add(p); 2467 final String id = p.getId(); 2468 map.put(id, p); 2469 2470 if (DEBUG) { 2471 Slog.d(TAG, "Found an input method " + p); 2472 } 2473 2474 } catch (XmlPullParserException e) { 2475 Slog.w(TAG, "Unable to load input method " + compName, e); 2476 } catch (IOException e) { 2477 Slog.w(TAG, "Unable to load input method " + compName, e); 2478 } 2479 } 2480 2481 if (resetDefaultEnabledIme) { 2482 final ArrayList<InputMethodInfo> defaultEnabledIme = 2483 InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list); 2484 for (int i = 0; i < defaultEnabledIme.size(); ++i) { 2485 final InputMethodInfo imi = defaultEnabledIme.get(i); 2486 if (DEBUG) { 2487 Slog.d(TAG, "--- enable ime = " + imi); 2488 } 2489 setInputMethodEnabledLocked(imi.getId(), true); 2490 } 2491 } 2492 2493 final String defaultImiId = mSettings.getSelectedInputMethod(); 2494 if (!TextUtils.isEmpty(defaultImiId)) { 2495 if (!map.containsKey(defaultImiId)) { 2496 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME."); 2497 if (chooseNewDefaultIMELocked()) { 2498 updateFromSettingsLocked(true); 2499 } 2500 } else { 2501 // Double check that the default IME is certainly enabled. 2502 setInputMethodEnabledLocked(defaultImiId, true); 2503 } 2504 } 2505 } 2506 2507 // ---------------------------------------------------------------------- 2508 2509 private void showInputMethodMenu() { 2510 showInputMethodMenuInternal(false); 2511 } 2512 2513 private void showInputMethodSubtypeMenu() { 2514 showInputMethodMenuInternal(true); 2515 } 2516 2517 private void showInputMethodAndSubtypeEnabler(String inputMethodId) { 2518 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS); 2519 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2520 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2521 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2522 if (!TextUtils.isEmpty(inputMethodId)) { 2523 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId); 2524 } 2525 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); 2526 } 2527 2528 private void showConfigureInputMethods() { 2529 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS); 2530 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 2531 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED 2532 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 2533 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT); 2534 } 2535 2536 private boolean isScreenLocked() { 2537 return mKeyguardManager != null 2538 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure(); 2539 } 2540 private void showInputMethodMenuInternal(boolean showSubtypes) { 2541 if (DEBUG) Slog.v(TAG, "Show switching menu"); 2542 2543 final Context context = mContext; 2544 final boolean isScreenLocked = isScreenLocked(); 2545 2546 final String lastInputMethodId = mSettings.getSelectedInputMethod(); 2547 int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId); 2548 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId); 2549 2550 synchronized (mMethodMap) { 2551 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 2552 getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 2553 if (immis == null || immis.size() == 0) { 2554 return; 2555 } 2556 2557 hideInputMethodMenuLocked(); 2558 2559 final List<ImeSubtypeListItem> imList = 2560 mImListManager.getSortedInputMethodAndSubtypeList( 2561 showSubtypes, mInputShown, isScreenLocked); 2562 2563 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) { 2564 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked(); 2565 if (currentSubtype != null) { 2566 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId); 2567 lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2568 currentImi, currentSubtype.hashCode()); 2569 } 2570 } 2571 2572 final int N = imList.size(); 2573 mIms = new InputMethodInfo[N]; 2574 mSubtypeIds = new int[N]; 2575 int checkedItem = 0; 2576 for (int i = 0; i < N; ++i) { 2577 final ImeSubtypeListItem item = imList.get(i); 2578 mIms[i] = item.mImi; 2579 mSubtypeIds[i] = item.mSubtypeId; 2580 if (mIms[i].getId().equals(lastInputMethodId)) { 2581 int subtypeId = mSubtypeIds[i]; 2582 if ((subtypeId == NOT_A_SUBTYPE_ID) 2583 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0) 2584 || (subtypeId == lastInputMethodSubtypeId)) { 2585 checkedItem = i; 2586 } 2587 } 2588 } 2589 final TypedArray a = context.obtainStyledAttributes(null, 2590 com.android.internal.R.styleable.DialogPreference, 2591 com.android.internal.R.attr.alertDialogStyle, 0); 2592 mDialogBuilder = new AlertDialog.Builder(context) 2593 .setOnCancelListener(new OnCancelListener() { 2594 @Override 2595 public void onCancel(DialogInterface dialog) { 2596 hideInputMethodMenu(); 2597 } 2598 }) 2599 .setIcon(a.getDrawable( 2600 com.android.internal.R.styleable.DialogPreference_dialogTitle)); 2601 a.recycle(); 2602 final LayoutInflater inflater = 2603 (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2604 final View tv = inflater.inflate( 2605 com.android.internal.R.layout.input_method_switch_dialog_title, null); 2606 mDialogBuilder.setCustomTitle(tv); 2607 2608 // Setup layout for a toggle switch of the hardware keyboard 2609 mSwitchingDialogTitleView = tv; 2610 mSwitchingDialogTitleView.findViewById( 2611 com.android.internal.R.id.hard_keyboard_section).setVisibility( 2612 mWindowManagerService.isHardKeyboardAvailable() ? 2613 View.VISIBLE : View.GONE); 2614 final Switch hardKeySwitch = ((Switch)mSwitchingDialogTitleView.findViewById( 2615 com.android.internal.R.id.hard_keyboard_switch)); 2616 hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled()); 2617 hardKeySwitch.setOnCheckedChangeListener( 2618 new OnCheckedChangeListener() { 2619 @Override 2620 public void onCheckedChanged( 2621 CompoundButton buttonView, boolean isChecked) { 2622 mWindowManagerService.setHardKeyboardEnabled(isChecked); 2623 // Ensure that the input method dialog is dismissed when changing 2624 // the hardware keyboard state. 2625 hideInputMethodMenu(); 2626 } 2627 }); 2628 2629 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context, 2630 com.android.internal.R.layout.simple_list_item_2_single_choice, imList, 2631 checkedItem); 2632 2633 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, 2634 new AlertDialog.OnClickListener() { 2635 @Override 2636 public void onClick(DialogInterface dialog, int which) { 2637 synchronized (mMethodMap) { 2638 if (mIms == null || mIms.length <= which 2639 || mSubtypeIds == null || mSubtypeIds.length <= which) { 2640 return; 2641 } 2642 InputMethodInfo im = mIms[which]; 2643 int subtypeId = mSubtypeIds[which]; 2644 adapter.mCheckedItem = which; 2645 adapter.notifyDataSetChanged(); 2646 hideInputMethodMenu(); 2647 if (im != null) { 2648 if ((subtypeId < 0) 2649 || (subtypeId >= im.getSubtypeCount())) { 2650 subtypeId = NOT_A_SUBTYPE_ID; 2651 } 2652 setInputMethodLocked(im.getId(), subtypeId); 2653 } 2654 } 2655 } 2656 }); 2657 2658 if (showSubtypes && !isScreenLocked) { 2659 mDialogBuilder.setPositiveButton( 2660 com.android.internal.R.string.configure_input_methods, 2661 new DialogInterface.OnClickListener() { 2662 @Override 2663 public void onClick(DialogInterface dialog, int whichButton) { 2664 showConfigureInputMethods(); 2665 } 2666 }); 2667 } 2668 mSwitchingDialog = mDialogBuilder.create(); 2669 mSwitchingDialog.setCanceledOnTouchOutside(true); 2670 mSwitchingDialog.getWindow().setType( 2671 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG); 2672 mSwitchingDialog.getWindow().getAttributes().privateFlags |= 2673 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; 2674 mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method"); 2675 mSwitchingDialog.show(); 2676 } 2677 } 2678 2679 private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> { 2680 public final CharSequence mImeName; 2681 public final CharSequence mSubtypeName; 2682 public final InputMethodInfo mImi; 2683 public final int mSubtypeId; 2684 private final boolean mIsSystemLocale; 2685 private final boolean mIsSystemLanguage; 2686 2687 public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName, 2688 InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) { 2689 mImeName = imeName; 2690 mSubtypeName = subtypeName; 2691 mImi = imi; 2692 mSubtypeId = subtypeId; 2693 if (TextUtils.isEmpty(subtypeLocale)) { 2694 mIsSystemLocale = false; 2695 mIsSystemLanguage = false; 2696 } else { 2697 mIsSystemLocale = subtypeLocale.equals(systemLocale); 2698 mIsSystemLanguage = mIsSystemLocale 2699 || subtypeLocale.startsWith(systemLocale.substring(0, 2)); 2700 } 2701 } 2702 2703 @Override 2704 public int compareTo(ImeSubtypeListItem other) { 2705 if (TextUtils.isEmpty(mImeName)) { 2706 return 1; 2707 } 2708 if (TextUtils.isEmpty(other.mImeName)) { 2709 return -1; 2710 } 2711 if (!TextUtils.equals(mImeName, other.mImeName)) { 2712 return mImeName.toString().compareTo(other.mImeName.toString()); 2713 } 2714 if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) { 2715 return 0; 2716 } 2717 if (mIsSystemLocale) { 2718 return -1; 2719 } 2720 if (other.mIsSystemLocale) { 2721 return 1; 2722 } 2723 if (mIsSystemLanguage) { 2724 return -1; 2725 } 2726 if (other.mIsSystemLanguage) { 2727 return 1; 2728 } 2729 if (TextUtils.isEmpty(mSubtypeName)) { 2730 return 1; 2731 } 2732 if (TextUtils.isEmpty(other.mSubtypeName)) { 2733 return -1; 2734 } 2735 return mSubtypeName.toString().compareTo(other.mSubtypeName.toString()); 2736 } 2737 } 2738 2739 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> { 2740 private final LayoutInflater mInflater; 2741 private final int mTextViewResourceId; 2742 private final List<ImeSubtypeListItem> mItemsList; 2743 public int mCheckedItem; 2744 public ImeSubtypeListAdapter(Context context, int textViewResourceId, 2745 List<ImeSubtypeListItem> itemsList, int checkedItem) { 2746 super(context, textViewResourceId, itemsList); 2747 mTextViewResourceId = textViewResourceId; 2748 mItemsList = itemsList; 2749 mCheckedItem = checkedItem; 2750 mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2751 } 2752 2753 @Override 2754 public View getView(int position, View convertView, ViewGroup parent) { 2755 final View view = convertView != null ? convertView 2756 : mInflater.inflate(mTextViewResourceId, null); 2757 if (position < 0 || position >= mItemsList.size()) return view; 2758 final ImeSubtypeListItem item = mItemsList.get(position); 2759 final CharSequence imeName = item.mImeName; 2760 final CharSequence subtypeName = item.mSubtypeName; 2761 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1); 2762 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2); 2763 if (TextUtils.isEmpty(subtypeName)) { 2764 firstTextView.setText(imeName); 2765 secondTextView.setVisibility(View.GONE); 2766 } else { 2767 firstTextView.setText(subtypeName); 2768 secondTextView.setText(imeName); 2769 secondTextView.setVisibility(View.VISIBLE); 2770 } 2771 final RadioButton radioButton = 2772 (RadioButton)view.findViewById(com.android.internal.R.id.radio); 2773 radioButton.setChecked(position == mCheckedItem); 2774 return view; 2775 } 2776 } 2777 2778 void hideInputMethodMenu() { 2779 synchronized (mMethodMap) { 2780 hideInputMethodMenuLocked(); 2781 } 2782 } 2783 2784 void hideInputMethodMenuLocked() { 2785 if (DEBUG) Slog.v(TAG, "Hide switching menu"); 2786 2787 if (mSwitchingDialog != null) { 2788 mSwitchingDialog.dismiss(); 2789 mSwitchingDialog = null; 2790 } 2791 2792 mDialogBuilder = null; 2793 mIms = null; 2794 } 2795 2796 // ---------------------------------------------------------------------- 2797 2798 @Override 2799 public boolean setInputMethodEnabled(String id, boolean enabled) { 2800 // TODO: Make this work even for non-current users? 2801 if (!calledFromValidUser()) { 2802 return false; 2803 } 2804 synchronized (mMethodMap) { 2805 if (mContext.checkCallingOrSelfPermission( 2806 android.Manifest.permission.WRITE_SECURE_SETTINGS) 2807 != PackageManager.PERMISSION_GRANTED) { 2808 throw new SecurityException( 2809 "Requires permission " 2810 + android.Manifest.permission.WRITE_SECURE_SETTINGS); 2811 } 2812 2813 long ident = Binder.clearCallingIdentity(); 2814 try { 2815 return setInputMethodEnabledLocked(id, enabled); 2816 } finally { 2817 Binder.restoreCallingIdentity(ident); 2818 } 2819 } 2820 } 2821 2822 boolean setInputMethodEnabledLocked(String id, boolean enabled) { 2823 // Make sure this is a valid input method. 2824 InputMethodInfo imm = mMethodMap.get(id); 2825 if (imm == null) { 2826 throw new IllegalArgumentException("Unknown id: " + mCurMethodId); 2827 } 2828 2829 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings 2830 .getEnabledInputMethodsAndSubtypeListLocked(); 2831 2832 if (enabled) { 2833 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) { 2834 if (pair.first.equals(id)) { 2835 // We are enabling this input method, but it is already enabled. 2836 // Nothing to do. The previous state was enabled. 2837 return true; 2838 } 2839 } 2840 mSettings.appendAndPutEnabledInputMethodLocked(id, false); 2841 // Previous state was disabled. 2842 return false; 2843 } else { 2844 StringBuilder builder = new StringBuilder(); 2845 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked( 2846 builder, enabledInputMethodsList, id)) { 2847 // Disabled input method is currently selected, switch to another one. 2848 final String selId = mSettings.getSelectedInputMethod(); 2849 if (id.equals(selId) && !chooseNewDefaultIMELocked()) { 2850 Slog.i(TAG, "Can't find new IME, unsetting the current input method."); 2851 resetSelectedInputMethodAndSubtypeLocked(""); 2852 } 2853 // Previous state was enabled. 2854 return true; 2855 } else { 2856 // We are disabling the input method but it is already disabled. 2857 // Nothing to do. The previous state was disabled. 2858 return false; 2859 } 2860 } 2861 } 2862 2863 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId, 2864 boolean setSubtypeOnly) { 2865 // Update the history of InputMethod and Subtype 2866 mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype); 2867 2868 // Set Subtype here 2869 if (imi == null || subtypeId < 0) { 2870 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2871 mCurrentSubtype = null; 2872 } else { 2873 if (subtypeId < imi.getSubtypeCount()) { 2874 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId); 2875 mSettings.putSelectedSubtype(subtype.hashCode()); 2876 mCurrentSubtype = subtype; 2877 } else { 2878 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID); 2879 // If the subtype is not specified, choose the most applicable one 2880 mCurrentSubtype = getCurrentInputMethodSubtypeLocked(); 2881 } 2882 } 2883 2884 // Workaround. 2885 // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked 2886 // IMEs are not recognized and considered uninstalled. 2887 // Actually, we can't move everything after SystemReady because 2888 // IMMS needs to run in the encryption lock screen. So, we just skip changing 2889 // the default IME here and try cheking the default IME again in systemReady(). 2890 // TODO: Do nothing before system ready and implement a separated logic for 2891 // the encryption lock screen. 2892 // TODO: ASEC should be ready before IMMS is instantiated. 2893 if (mSystemReady && !setSubtypeOnly) { 2894 // Set InputMethod here 2895 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : ""); 2896 } 2897 } 2898 2899 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) { 2900 InputMethodInfo imi = mMethodMap.get(newDefaultIme); 2901 int lastSubtypeId = NOT_A_SUBTYPE_ID; 2902 // newDefaultIme is empty when there is no candidate for the selected IME. 2903 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) { 2904 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme); 2905 if (subtypeHashCode != null) { 2906 try { 2907 lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode( 2908 imi, Integer.valueOf(subtypeHashCode)); 2909 } catch (NumberFormatException e) { 2910 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e); 2911 } 2912 } 2913 } 2914 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false); 2915 } 2916 2917 // If there are no selected shortcuts, tries finding the most applicable ones. 2918 private Pair<InputMethodInfo, InputMethodSubtype> 2919 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) { 2920 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked(); 2921 InputMethodInfo mostApplicableIMI = null; 2922 InputMethodSubtype mostApplicableSubtype = null; 2923 boolean foundInSystemIME = false; 2924 2925 // Search applicable subtype for each InputMethodInfo 2926 for (InputMethodInfo imi: imis) { 2927 final String imiId = imi.getId(); 2928 if (foundInSystemIME && !imiId.equals(mCurMethodId)) { 2929 continue; 2930 } 2931 InputMethodSubtype subtype = null; 2932 final List<InputMethodSubtype> enabledSubtypes = 2933 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 2934 // 1. Search by the current subtype's locale from enabledSubtypes. 2935 if (mCurrentSubtype != null) { 2936 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 2937 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false); 2938 } 2939 // 2. Search by the system locale from enabledSubtypes. 2940 // 3. Search the first enabled subtype matched with mode from enabledSubtypes. 2941 if (subtype == null) { 2942 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 2943 mRes, enabledSubtypes, mode, null, true); 2944 } 2945 final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes = 2946 InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode); 2947 final ArrayList<InputMethodSubtype> subtypesForSearch = 2948 overridingImplicitlyEnabledSubtypes.isEmpty() 2949 ? InputMethodUtils.getSubtypes(imi) 2950 : overridingImplicitlyEnabledSubtypes; 2951 // 4. Search by the current subtype's locale from all subtypes. 2952 if (subtype == null && mCurrentSubtype != null) { 2953 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 2954 mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false); 2955 } 2956 // 5. Search by the system locale from all subtypes. 2957 // 6. Search the first enabled subtype matched with mode from all subtypes. 2958 if (subtype == null) { 2959 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 2960 mRes, subtypesForSearch, mode, null, true); 2961 } 2962 if (subtype != null) { 2963 if (imiId.equals(mCurMethodId)) { 2964 // The current input method is the most applicable IME. 2965 mostApplicableIMI = imi; 2966 mostApplicableSubtype = subtype; 2967 break; 2968 } else if (!foundInSystemIME) { 2969 // The system input method is 2nd applicable IME. 2970 mostApplicableIMI = imi; 2971 mostApplicableSubtype = subtype; 2972 if ((imi.getServiceInfo().applicationInfo.flags 2973 & ApplicationInfo.FLAG_SYSTEM) != 0) { 2974 foundInSystemIME = true; 2975 } 2976 } 2977 } 2978 } 2979 if (DEBUG) { 2980 if (mostApplicableIMI != null) { 2981 Slog.w(TAG, "Most applicable shortcut input method was:" 2982 + mostApplicableIMI.getId()); 2983 if (mostApplicableSubtype != null) { 2984 Slog.w(TAG, "Most applicable shortcut input method subtype was:" 2985 + "," + mostApplicableSubtype.getMode() + "," 2986 + mostApplicableSubtype.getLocale()); 2987 } 2988 } 2989 } 2990 if (mostApplicableIMI != null) { 2991 return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI, 2992 mostApplicableSubtype); 2993 } else { 2994 return null; 2995 } 2996 } 2997 2998 /** 2999 * @return Return the current subtype of this input method. 3000 */ 3001 @Override 3002 public InputMethodSubtype getCurrentInputMethodSubtype() { 3003 // TODO: Make this work even for non-current users? 3004 if (!calledFromValidUser()) { 3005 return null; 3006 } 3007 synchronized (mMethodMap) { 3008 return getCurrentInputMethodSubtypeLocked(); 3009 } 3010 } 3011 3012 private InputMethodSubtype getCurrentInputMethodSubtypeLocked() { 3013 if (mCurMethodId == null) { 3014 return null; 3015 } 3016 final boolean subtypeIsSelected = mSettings.isSubtypeSelected(); 3017 final InputMethodInfo imi = mMethodMap.get(mCurMethodId); 3018 if (imi == null || imi.getSubtypeCount() == 0) { 3019 return null; 3020 } 3021 if (!subtypeIsSelected || mCurrentSubtype == null 3022 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) { 3023 int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId); 3024 if (subtypeId == NOT_A_SUBTYPE_ID) { 3025 // If there are no selected subtypes, the framework will try to find 3026 // the most applicable subtype from explicitly or implicitly enabled 3027 // subtypes. 3028 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes = 3029 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true); 3030 // If there is only one explicitly or implicitly enabled subtype, 3031 // just returns it. 3032 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) { 3033 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0); 3034 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) { 3035 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3036 mRes, explicitlyOrImplicitlyEnabledSubtypes, 3037 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true); 3038 if (mCurrentSubtype == null) { 3039 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked( 3040 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null, 3041 true); 3042 } 3043 } 3044 } else { 3045 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId); 3046 } 3047 } 3048 return mCurrentSubtype; 3049 } 3050 3051 private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi, 3052 InputMethodSubtype subtype) { 3053 if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) { 3054 mShortcutInputMethodsAndSubtypes.get(imi).add(subtype); 3055 } else { 3056 ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 3057 subtypes.add(subtype); 3058 mShortcutInputMethodsAndSubtypes.put(imi, subtypes); 3059 } 3060 } 3061 3062 // TODO: We should change the return type from List to List<Parcelable> 3063 @SuppressWarnings("rawtypes") 3064 @Override 3065 public List getShortcutInputMethodsAndSubtypes() { 3066 synchronized (mMethodMap) { 3067 ArrayList<Object> ret = new ArrayList<Object>(); 3068 if (mShortcutInputMethodsAndSubtypes.size() == 0) { 3069 // If there are no selected shortcut subtypes, the framework will try to find 3070 // the most applicable subtype from all subtypes whose mode is 3071 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode. 3072 Pair<InputMethodInfo, InputMethodSubtype> info = 3073 findLastResortApplicableShortcutInputMethodAndSubtypeLocked( 3074 InputMethodUtils.SUBTYPE_MODE_VOICE); 3075 if (info != null) { 3076 ret.add(info.first); 3077 ret.add(info.second); 3078 } 3079 return ret; 3080 } 3081 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) { 3082 ret.add(imi); 3083 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) { 3084 ret.add(subtype); 3085 } 3086 } 3087 return ret; 3088 } 3089 } 3090 3091 @Override 3092 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) { 3093 // TODO: Make this work even for non-current users? 3094 if (!calledFromValidUser()) { 3095 return false; 3096 } 3097 synchronized (mMethodMap) { 3098 if (subtype != null && mCurMethodId != null) { 3099 InputMethodInfo imi = mMethodMap.get(mCurMethodId); 3100 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()); 3101 if (subtypeId != NOT_A_SUBTYPE_ID) { 3102 setInputMethodLocked(mCurMethodId, subtypeId); 3103 return true; 3104 } 3105 } 3106 return false; 3107 } 3108 } 3109 3110 private static class InputMethodAndSubtypeListManager { 3111 private final Context mContext; 3112 // Used to load label 3113 private final PackageManager mPm; 3114 private final InputMethodManagerService mImms; 3115 private final String mSystemLocaleStr; 3116 public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) { 3117 mContext = context; 3118 mPm = context.getPackageManager(); 3119 mImms = imms; 3120 final Locale locale = context.getResources().getConfiguration().locale; 3121 mSystemLocaleStr = locale != null ? locale.toString() : ""; 3122 } 3123 3124 private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis = 3125 new TreeMap<InputMethodInfo, List<InputMethodSubtype>>( 3126 new Comparator<InputMethodInfo>() { 3127 @Override 3128 public int compare(InputMethodInfo imi1, InputMethodInfo imi2) { 3129 if (imi2 == null) return 0; 3130 if (imi1 == null) return 1; 3131 if (mPm == null) { 3132 return imi1.getId().compareTo(imi2.getId()); 3133 } 3134 CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId(); 3135 CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId(); 3136 return imiId1.toString().compareTo(imiId2.toString()); 3137 } 3138 }); 3139 3140 public ImeSubtypeListItem getNextInputMethod( 3141 boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) { 3142 if (imi == null) { 3143 return null; 3144 } 3145 final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList(); 3146 if (imList.size() <= 1) { 3147 return null; 3148 } 3149 final int N = imList.size(); 3150 final int currentSubtypeId = subtype != null 3151 ? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode()) 3152 : NOT_A_SUBTYPE_ID; 3153 for (int i = 0; i < N; ++i) { 3154 final ImeSubtypeListItem isli = imList.get(i); 3155 if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) { 3156 if (!onlyCurrentIme) { 3157 return imList.get((i + 1) % N); 3158 } 3159 for (int j = 0; j < N - 1; ++j) { 3160 final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N); 3161 if (candidate.mImi.equals(imi)) { 3162 return candidate; 3163 } 3164 } 3165 return null; 3166 } 3167 } 3168 return null; 3169 } 3170 3171 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() { 3172 return getSortedInputMethodAndSubtypeList(true, false, false); 3173 } 3174 3175 public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes, 3176 boolean inputShown, boolean isScreenLocked) { 3177 final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>(); 3178 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis = 3179 mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(); 3180 if (immis == null || immis.size() == 0) { 3181 return Collections.emptyList(); 3182 } 3183 mSortedImmis.clear(); 3184 mSortedImmis.putAll(immis); 3185 for (InputMethodInfo imi : mSortedImmis.keySet()) { 3186 if (imi == null) continue; 3187 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi); 3188 HashSet<String> enabledSubtypeSet = new HashSet<String>(); 3189 for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) { 3190 enabledSubtypeSet.add(String.valueOf(subtype.hashCode())); 3191 } 3192 final CharSequence imeLabel = imi.loadLabel(mPm); 3193 if (showSubtypes && enabledSubtypeSet.size() > 0) { 3194 final int subtypeCount = imi.getSubtypeCount(); 3195 if (DEBUG) { 3196 Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId()); 3197 } 3198 for (int j = 0; j < subtypeCount; ++j) { 3199 final InputMethodSubtype subtype = imi.getSubtypeAt(j); 3200 final String subtypeHashCode = String.valueOf(subtype.hashCode()); 3201 // We show all enabled IMEs and subtypes when an IME is shown. 3202 if (enabledSubtypeSet.contains(subtypeHashCode) 3203 && ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) { 3204 final CharSequence subtypeLabel = 3205 subtype.overridesImplicitlyEnabledSubtype() ? null 3206 : subtype.getDisplayName(mContext, imi.getPackageName(), 3207 imi.getServiceInfo().applicationInfo); 3208 imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j, 3209 subtype.getLocale(), mSystemLocaleStr)); 3210 3211 // Removing this subtype from enabledSubtypeSet because we no longer 3212 // need to add an entry of this subtype to imList to avoid duplicated 3213 // entries. 3214 enabledSubtypeSet.remove(subtypeHashCode); 3215 } 3216 } 3217 } else { 3218 imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID, 3219 null, mSystemLocaleStr)); 3220 } 3221 } 3222 Collections.sort(imList); 3223 return imList; 3224 } 3225 } 3226 3227 // TODO: Cache the state for each user and reset when the cached user is removed. 3228 private static class InputMethodFileManager { 3229 private static final String SYSTEM_PATH = "system"; 3230 private static final String INPUT_METHOD_PATH = "inputmethod"; 3231 private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml"; 3232 private static final String NODE_SUBTYPES = "subtypes"; 3233 private static final String NODE_SUBTYPE = "subtype"; 3234 private static final String NODE_IMI = "imi"; 3235 private static final String ATTR_ID = "id"; 3236 private static final String ATTR_LABEL = "label"; 3237 private static final String ATTR_ICON = "icon"; 3238 private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale"; 3239 private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode"; 3240 private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue"; 3241 private static final String ATTR_IS_AUXILIARY = "isAuxiliary"; 3242 private final AtomicFile mAdditionalInputMethodSubtypeFile; 3243 private final HashMap<String, InputMethodInfo> mMethodMap; 3244 private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap = 3245 new HashMap<String, List<InputMethodSubtype>>(); 3246 public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) { 3247 if (methodMap == null) { 3248 throw new NullPointerException("methodMap is null"); 3249 } 3250 mMethodMap = methodMap; 3251 final File systemDir = userId == UserHandle.USER_OWNER 3252 ? new File(Environment.getDataDirectory(), SYSTEM_PATH) 3253 : Environment.getUserSystemDirectory(userId); 3254 final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH); 3255 if (!inputMethodDir.mkdirs()) { 3256 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath()); 3257 } 3258 final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME); 3259 mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile); 3260 if (!subtypeFile.exists()) { 3261 // If "subtypes.xml" doesn't exist, create a blank file. 3262 writeAdditionalInputMethodSubtypes( 3263 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap); 3264 } else { 3265 readAdditionalInputMethodSubtypes( 3266 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile); 3267 } 3268 } 3269 3270 private void deleteAllInputMethodSubtypes(String imiId) { 3271 synchronized (mMethodMap) { 3272 mAdditionalSubtypesMap.remove(imiId); 3273 writeAdditionalInputMethodSubtypes( 3274 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); 3275 } 3276 } 3277 3278 public void addInputMethodSubtypes( 3279 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) { 3280 synchronized (mMethodMap) { 3281 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>(); 3282 final int N = additionalSubtypes.length; 3283 for (int i = 0; i < N; ++i) { 3284 final InputMethodSubtype subtype = additionalSubtypes[i]; 3285 if (!subtypes.contains(subtype)) { 3286 subtypes.add(subtype); 3287 } else { 3288 Slog.w(TAG, "Duplicated subtype definition found: " 3289 + subtype.getLocale() + ", " + subtype.getMode()); 3290 } 3291 } 3292 mAdditionalSubtypesMap.put(imi.getId(), subtypes); 3293 writeAdditionalInputMethodSubtypes( 3294 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap); 3295 } 3296 } 3297 3298 public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() { 3299 synchronized (mMethodMap) { 3300 return mAdditionalSubtypesMap; 3301 } 3302 } 3303 3304 private static void writeAdditionalInputMethodSubtypes( 3305 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile, 3306 HashMap<String, InputMethodInfo> methodMap) { 3307 // Safety net for the case that this function is called before methodMap is set. 3308 final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0; 3309 FileOutputStream fos = null; 3310 try { 3311 fos = subtypesFile.startWrite(); 3312 final XmlSerializer out = new FastXmlSerializer(); 3313 out.setOutput(fos, "utf-8"); 3314 out.startDocument(null, true); 3315 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); 3316 out.startTag(null, NODE_SUBTYPES); 3317 for (String imiId : allSubtypes.keySet()) { 3318 if (isSetMethodMap && !methodMap.containsKey(imiId)) { 3319 Slog.w(TAG, "IME uninstalled or not valid.: " + imiId); 3320 continue; 3321 } 3322 out.startTag(null, NODE_IMI); 3323 out.attribute(null, ATTR_ID, imiId); 3324 final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId); 3325 final int N = subtypesList.size(); 3326 for (int i = 0; i < N; ++i) { 3327 final InputMethodSubtype subtype = subtypesList.get(i); 3328 out.startTag(null, NODE_SUBTYPE); 3329 out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId())); 3330 out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId())); 3331 out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale()); 3332 out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode()); 3333 out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue()); 3334 out.attribute(null, ATTR_IS_AUXILIARY, 3335 String.valueOf(subtype.isAuxiliary() ? 1 : 0)); 3336 out.endTag(null, NODE_SUBTYPE); 3337 } 3338 out.endTag(null, NODE_IMI); 3339 } 3340 out.endTag(null, NODE_SUBTYPES); 3341 out.endDocument(); 3342 subtypesFile.finishWrite(fos); 3343 } catch (java.io.IOException e) { 3344 Slog.w(TAG, "Error writing subtypes", e); 3345 if (fos != null) { 3346 subtypesFile.failWrite(fos); 3347 } 3348 } 3349 } 3350 3351 private static void readAdditionalInputMethodSubtypes( 3352 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) { 3353 if (allSubtypes == null || subtypesFile == null) return; 3354 allSubtypes.clear(); 3355 FileInputStream fis = null; 3356 try { 3357 fis = subtypesFile.openRead(); 3358 final XmlPullParser parser = Xml.newPullParser(); 3359 parser.setInput(fis, null); 3360 int type = parser.getEventType(); 3361 // Skip parsing until START_TAG 3362 while ((type = parser.next()) != XmlPullParser.START_TAG 3363 && type != XmlPullParser.END_DOCUMENT) {} 3364 String firstNodeName = parser.getName(); 3365 if (!NODE_SUBTYPES.equals(firstNodeName)) { 3366 throw new XmlPullParserException("Xml doesn't start with subtypes"); 3367 } 3368 final int depth =parser.getDepth(); 3369 String currentImiId = null; 3370 ArrayList<InputMethodSubtype> tempSubtypesArray = null; 3371 while (((type = parser.next()) != XmlPullParser.END_TAG 3372 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) { 3373 if (type != XmlPullParser.START_TAG) 3374 continue; 3375 final String nodeName = parser.getName(); 3376 if (NODE_IMI.equals(nodeName)) { 3377 currentImiId = parser.getAttributeValue(null, ATTR_ID); 3378 if (TextUtils.isEmpty(currentImiId)) { 3379 Slog.w(TAG, "Invalid imi id found in subtypes.xml"); 3380 continue; 3381 } 3382 tempSubtypesArray = new ArrayList<InputMethodSubtype>(); 3383 allSubtypes.put(currentImiId, tempSubtypesArray); 3384 } else if (NODE_SUBTYPE.equals(nodeName)) { 3385 if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) { 3386 Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId); 3387 continue; 3388 } 3389 final int icon = Integer.valueOf( 3390 parser.getAttributeValue(null, ATTR_ICON)); 3391 final int label = Integer.valueOf( 3392 parser.getAttributeValue(null, ATTR_LABEL)); 3393 final String imeSubtypeLocale = 3394 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE); 3395 final String imeSubtypeMode = 3396 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE); 3397 final String imeSubtypeExtraValue = 3398 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE); 3399 final boolean isAuxiliary = "1".equals(String.valueOf( 3400 parser.getAttributeValue(null, ATTR_IS_AUXILIARY))); 3401 final InputMethodSubtype subtype = 3402 new InputMethodSubtype(label, icon, imeSubtypeLocale, 3403 imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary); 3404 tempSubtypesArray.add(subtype); 3405 } 3406 } 3407 } catch (XmlPullParserException e) { 3408 Slog.w(TAG, "Error reading subtypes: " + e); 3409 return; 3410 } catch (java.io.IOException e) { 3411 Slog.w(TAG, "Error reading subtypes: " + e); 3412 return; 3413 } catch (NumberFormatException e) { 3414 Slog.w(TAG, "Error reading subtypes: " + e); 3415 return; 3416 } finally { 3417 if (fis != null) { 3418 try { 3419 fis.close(); 3420 } catch (java.io.IOException e1) { 3421 Slog.w(TAG, "Failed to close."); 3422 } 3423 } 3424 } 3425 } 3426 } 3427 3428 // ---------------------------------------------------------------------- 3429 // Utilities for debug 3430 private static String getStackTrace() { 3431 final StringBuilder sb = new StringBuilder(); 3432 try { 3433 throw new RuntimeException(); 3434 } catch (RuntimeException e) { 3435 final StackTraceElement[] frames = e.getStackTrace(); 3436 // Start at 1 because the first frame is here and we don't care about it 3437 for (int j = 1; j < frames.length; ++j) { 3438 sb.append(frames[j].toString() + "\n"); 3439 } 3440 } 3441 return sb.toString(); 3442 } 3443 3444 @Override 3445 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 3446 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) 3447 != PackageManager.PERMISSION_GRANTED) { 3448 3449 pw.println("Permission Denial: can't dump InputMethodManager from from pid=" 3450 + Binder.getCallingPid() 3451 + ", uid=" + Binder.getCallingUid()); 3452 return; 3453 } 3454 3455 IInputMethod method; 3456 ClientState client; 3457 3458 final Printer p = new PrintWriterPrinter(pw); 3459 3460 synchronized (mMethodMap) { 3461 p.println("Current Input Method Manager state:"); 3462 int N = mMethodList.size(); 3463 p.println(" Input Methods:"); 3464 for (int i=0; i<N; i++) { 3465 InputMethodInfo info = mMethodList.get(i); 3466 p.println(" InputMethod #" + i + ":"); 3467 info.dump(p, " "); 3468 } 3469 p.println(" Clients:"); 3470 for (ClientState ci : mClients.values()) { 3471 p.println(" Client " + ci + ":"); 3472 p.println(" client=" + ci.client); 3473 p.println(" inputContext=" + ci.inputContext); 3474 p.println(" sessionRequested=" + ci.sessionRequested); 3475 p.println(" curSession=" + ci.curSession); 3476 } 3477 p.println(" mCurMethodId=" + mCurMethodId); 3478 client = mCurClient; 3479 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq); 3480 p.println(" mCurFocusedWindow=" + mCurFocusedWindow); 3481 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection 3482 + " mBoundToMethod=" + mBoundToMethod); 3483 p.println(" mCurToken=" + mCurToken); 3484 p.println(" mCurIntent=" + mCurIntent); 3485 method = mCurMethod; 3486 p.println(" mCurMethod=" + mCurMethod); 3487 p.println(" mEnabledSession=" + mEnabledSession); 3488 p.println(" mShowRequested=" + mShowRequested 3489 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested 3490 + " mShowForced=" + mShowForced 3491 + " mInputShown=" + mInputShown); 3492 p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn); 3493 } 3494 3495 p.println(" "); 3496 if (client != null) { 3497 pw.flush(); 3498 try { 3499 client.client.asBinder().dump(fd, args); 3500 } catch (RemoteException e) { 3501 p.println("Input method client dead: " + e); 3502 } 3503 } else { 3504 p.println("No input method client."); 3505 } 3506 3507 p.println(" "); 3508 if (method != null) { 3509 pw.flush(); 3510 try { 3511 method.asBinder().dump(fd, args); 3512 } catch (RemoteException e) { 3513 p.println("Input method service dead: " + e); 3514 } 3515 } else { 3516 p.println("No input method service."); 3517 } 3518 } 3519} 3520