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
7 * http://www.apache.org/licenses/LICENSE-2.0
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
16 package com.android.server;
18 import static java.lang.annotation.RetentionPolicy.SOURCE;
20 import com.android.internal.content.PackageMonitor;
21 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
22 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
23 import com.android.internal.inputmethod.InputMethodUtils;
24 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
25 import com.android.internal.os.HandlerCaller;
26 import com.android.internal.os.SomeArgs;
27 import com.android.internal.util.FastXmlSerializer;
28 import com.android.internal.view.IInputContext;
29 import com.android.internal.view.IInputMethod;
30 import com.android.internal.view.IInputMethodClient;
31 import com.android.internal.view.IInputMethodManager;
32 import com.android.internal.view.IInputMethodSession;
33 import com.android.internal.view.IInputSessionCallback;
34 import com.android.internal.view.InputBindResult;
35 import com.android.internal.view.InputMethodClient;
36 import com.android.server.statusbar.StatusBarManagerService;
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 import org.xmlpull.v1.XmlSerializer;
42 import android.annotation.IntDef;
43 import android.annotation.NonNull;
44 import android.annotation.Nullable;
45 import android.annotation.UserIdInt;
46 import android.app.ActivityManagerNative;
47 import android.app.AlertDialog;
48 import android.app.AppGlobals;
49 import android.app.AppOpsManager;
50 import android.app.KeyguardManager;
51 import android.app.Notification;
52 import android.app.NotificationManager;
53 import android.app.PendingIntent;
54 import android.content.BroadcastReceiver;
55 import android.content.ComponentName;
56 import android.content.ContentResolver;
57 import android.content.Context;
58 import android.content.DialogInterface;
59 import android.content.DialogInterface.OnCancelListener;
60 import android.content.DialogInterface.OnClickListener;
61 import android.content.Intent;
62 import android.content.IntentFilter;
63 import android.content.ServiceConnection;
64 import android.content.pm.ApplicationInfo;
65 import android.content.pm.IPackageManager;
66 import android.content.pm.PackageManager;
67 import android.content.pm.ResolveInfo;
68 import android.content.pm.ServiceInfo;
69 import android.content.res.Configuration;
70 import android.content.res.Resources;
71 import android.content.res.TypedArray;
72 import android.database.ContentObserver;
73 import android.graphics.drawable.Drawable;
74 import android.hardware.input.InputManagerInternal;
75 import android.inputmethodservice.InputMethodService;
76 import android.net.Uri;
77 import android.os.Binder;
78 import android.os.Bundle;
79 import android.os.Debug;
80 import android.os.Environment;
81 import android.os.Handler;
82 import android.os.IBinder;
83 import android.os.IInterface;
84 import android.os.Message;
85 import android.os.LocaleList;
86 import android.os.Parcel;
87 import android.os.Process;
88 import android.os.RemoteException;
89 import android.os.ResultReceiver;
90 import android.os.ServiceManager;
91 import android.os.SystemClock;
92 import android.os.UserHandle;
93 import android.os.UserManager;
94 import android.provider.Settings;
95 import android.text.TextUtils;
96 import android.text.style.SuggestionSpan;
97 import android.util.ArrayMap;
98 import android.util.ArraySet;
99 import android.util.AtomicFile;
100 import android.util.EventLog;
101 import android.util.LruCache;
102 import android.util.Pair;
103 import android.util.PrintWriterPrinter;
104 import android.util.Printer;
105 import android.util.Slog;
106 import android.util.Xml;
107 import android.view.ContextThemeWrapper;
108 import android.view.IWindowManager;
109 import android.view.InputChannel;
110 import android.view.LayoutInflater;
111 import android.view.View;
112 import android.view.ViewGroup;
113 import android.view.WindowManager;
114 import android.view.WindowManagerInternal;
115 import android.view.inputmethod.EditorInfo;
116 import android.view.inputmethod.InputBinding;
117 import android.view.inputmethod.InputConnectionInspector;
118 import android.view.inputmethod.InputMethod;
119 import android.view.inputmethod.InputMethodInfo;
120 import android.view.inputmethod.InputMethodManager;
121 import android.view.inputmethod.InputMethodManagerInternal;
122 import android.view.inputmethod.InputMethodSubtype;
123 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
124 import android.widget.ArrayAdapter;
125 import android.widget.CompoundButton;
126 import android.widget.CompoundButton.OnCheckedChangeListener;
127 import android.widget.RadioButton;
128 import android.widget.Switch;
129 import android.widget.TextView;
130 import android.widget.Toast;
133 import java.io.FileDescriptor;
134 import java.io.FileInputStream;
135 import java.io.FileOutputStream;
136 import java.io.IOException;
137 import java.io.PrintWriter;
138 import java.lang.annotation.Retention;
139 import java.nio.charset.StandardCharsets;
140 import java.util.ArrayList;
141 import java.util.Collections;
142 import java.util.HashMap;
143 import java.util.List;
146 * This class provides a system service that manages input methods.
148 public class InputMethodManagerService extends IInputMethodManager.Stub
149 implements ServiceConnection, Handler.Callback {
150 static final boolean DEBUG = false;
151 static final boolean DEBUG_RESTORE = DEBUG || false;
152 static final String TAG = "InputMethodManagerService";
154 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
155 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
156 static final int MSG_SHOW_IM_CONFIG = 3;
158 static final int MSG_UNBIND_INPUT = 1000;
159 static final int MSG_BIND_INPUT = 1010;
160 static final int MSG_SHOW_SOFT_INPUT = 1020;
161 static final int MSG_HIDE_SOFT_INPUT = 1030;
162 static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
163 static final int MSG_ATTACH_TOKEN = 1040;
164 static final int MSG_CREATE_SESSION = 1050;
166 static final int MSG_START_INPUT = 2000;
167 static final int MSG_RESTART_INPUT = 2010;
169 static final int MSG_UNBIND_CLIENT = 3000;
170 static final int MSG_BIND_CLIENT = 3010;
171 static final int MSG_SET_ACTIVE = 3020;
172 static final int MSG_SET_INTERACTIVE = 3030;
173 static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
174 static final int MSG_SWITCH_IME = 3050;
176 static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
178 static final long TIME_TO_RECONNECT = 3 * 1000;
180 static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
182 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
183 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
186 @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
187 private @interface HardKeyboardBehavior {
188 int WIRELESS_AFFORDANCE = 0;
189 int WIRED_AFFORDANCE = 1;
192 final Context mContext;
193 final Resources mRes;
194 final Handler mHandler;
195 final InputMethodSettings mSettings;
196 final SettingsObserver mSettingsObserver;
197 final IWindowManager mIWindowManager;
198 final WindowManagerInternal mWindowManagerInternal;
199 final HandlerCaller mCaller;
200 final boolean mHasFeature;
201 private InputMethodFileManager mFileManager;
202 private final HardKeyboardListener mHardKeyboardListener;
203 private final AppOpsManager mAppOpsManager;
204 private final UserManager mUserManager;
206 final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
208 // All known input methods. mMethodMap also serves as the global
209 // lock for this class.
210 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
211 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
212 private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
213 new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
214 private final InputMethodSubtypeSwitchingController mSwitchingController;
216 // Used to bring IME service up to visible adjustment while it is being shown.
217 final ServiceConnection mVisibleConnection = new ServiceConnection() {
218 @Override public void onServiceConnected(ComponentName name, IBinder service) {
221 @Override public void onServiceDisconnected(ComponentName name) {
224 boolean mVisibleBound = false;
226 // Ongoing notification
227 private NotificationManager mNotificationManager;
228 private KeyguardManager mKeyguardManager;
229 private @Nullable StatusBarManagerService mStatusBar;
230 private Notification.Builder mImeSwitcherNotification;
231 private PendingIntent mImeSwitchPendingIntent;
232 private boolean mShowOngoingImeSwitcherForPhones;
233 private boolean mNotificationShown;
234 private final boolean mImeSelectedOnBoot;
236 static class SessionState {
237 final ClientState client;
238 final IInputMethod method;
240 IInputMethodSession session;
241 InputChannel channel;
244 public String toString() {
245 return "SessionState{uid " + client.uid + " pid " + client.pid
246 + " method " + Integer.toHexString(
247 System.identityHashCode(method))
248 + " session " + Integer.toHexString(
249 System.identityHashCode(session))
250 + " channel " + channel
254 SessionState(ClientState _client, IInputMethod _method,
255 IInputMethodSession _session, InputChannel _channel) {
263 static final class ClientState {
264 final IInputMethodClient client;
265 final IInputContext inputContext;
268 final InputBinding binding;
270 boolean sessionRequested;
271 SessionState curSession;
274 public String toString() {
275 return "ClientState{" + Integer.toHexString(
276 System.identityHashCode(this)) + " uid " + uid
277 + " pid " + pid + "}";
280 ClientState(IInputMethodClient _client, IInputContext _inputContext,
281 int _uid, int _pid) {
283 inputContext = _inputContext;
286 binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
290 final HashMap<IBinder, ClientState> mClients = new HashMap<>();
293 * Set once the system is ready to run third party code.
295 boolean mSystemReady;
298 * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
299 * method. This is to be synchronized with the secure settings keyed with
300 * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
302 * <p>This can be transiently {@code null} when the system is re-initializing input method
303 * settings, e.g., the system locale is just changed.</p>
305 * <p>Note that {@link #mCurId} is used to track which IME is being connected to
306 * {@link InputMethodManagerService}.</p>
314 * The current binding sequence number, incremented every time there is
315 * a new bind performed.
320 * The client that is currently bound to an input method.
322 ClientState mCurClient;
325 * The last window token that we confirmed to be focused. This is always updated upon reports
326 * from the input method client. If the window state is already changed before the report is
327 * handled, this field just keeps the last value.
329 IBinder mCurFocusedWindow;
332 * The client by which {@link #mCurFocusedWindow} was reported. Used only for debugging.
334 ClientState mCurFocusedWindowClient;
337 * The input context last provided by the current client.
339 IInputContext mCurInputContext;
342 * The missing method flags for the input context last provided by the current client.
344 * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
346 int mCurInputContextMissingMethods;
349 * The attributes last provided by the current client.
351 EditorInfo mCurAttribute;
354 * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
355 * connected to or in the process of connecting to.
357 * <p>This can be {@code null} when no input method is connected.</p>
365 * The current subtype of the current input method.
367 private InputMethodSubtype mCurrentSubtype;
369 // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
370 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
371 mShortcutInputMethodsAndSubtypes = new HashMap<>();
373 // Was the keyguard locked when this client became current?
374 private boolean mCurClientInKeyguard;
377 * Set to true if our ServiceConnection is currently actively bound to
378 * a service (whether or not we have gotten its IBinder back yet).
380 boolean mHaveConnection;
383 * Set if the client has asked for the input method to be shown.
385 boolean mShowRequested;
388 * Set if we were explicitly told to show the input method.
390 boolean mShowExplicitlyRequested;
393 * Set if we were forced to be shown.
398 * Set if we last told the input method to show itself.
403 * The Intent used to connect to the current input method.
408 * The token we have made for the currently active input method, to
409 * identify it in the future.
414 * If non-null, this is the input method service we are currently connected
417 IInputMethod mCurMethod;
420 * Time that we last initiated a bind to the input method, to determine
421 * if we should try to disconnect and reconnect to it.
426 * Have we called mCurMethod.bindInput()?
428 boolean mBoundToMethod;
431 * Currently enabled session. Only touched by service thread, not
432 * protected by a lock.
434 SessionState mEnabledSession;
437 * True if the device is currently interactive with user. The value is true initially.
439 boolean mIsInteractive = true;
441 int mCurUserActionNotificationSequenceNumber = 0;
443 int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
446 * A set of status bits regarding the active IME.
448 * <p>This value is a combination of following two bits:</p>
450 * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
452 * If this bit is ON, connected IME is ready to accept touch/key events.
454 * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
456 * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
459 * <em>Do not update this value outside of setImeWindowStatus.</em>
463 private AlertDialog.Builder mDialogBuilder;
464 private AlertDialog mSwitchingDialog;
465 private View mSwitchingDialogTitleView;
466 private Toast mSubtypeSwitchedByShortCutToast;
467 private InputMethodInfo[] mIms;
468 private int[] mSubtypeIds;
469 private LocaleList mLastSystemLocales;
470 private boolean mShowImeWithHardKeyboard;
471 private boolean mAccessibilityRequestingNoSoftKeyboard;
472 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
473 private final IPackageManager mIPackageManager;
474 private final String mSlotIme;
475 @HardKeyboardBehavior
476 private final int mHardKeyboardBehavior;
478 class SettingsObserver extends ContentObserver {
480 boolean mRegistered = false;
482 String mLastEnabled = "";
485 * <em>This constructor must be called within the lock.</em>
487 SettingsObserver(Handler handler) {
491 public void registerContentObserverLocked(@UserIdInt int userId) {
492 if (mRegistered && mUserId == userId) {
495 ContentResolver resolver = mContext.getContentResolver();
497 mContext.getContentResolver().unregisterContentObserver(this);
500 if (mUserId != userId) {
504 resolver.registerContentObserver(Settings.Secure.getUriFor(
505 Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
506 resolver.registerContentObserver(Settings.Secure.getUriFor(
507 Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
508 resolver.registerContentObserver(Settings.Secure.getUriFor(
509 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
510 resolver.registerContentObserver(Settings.Secure.getUriFor(
511 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
512 resolver.registerContentObserver(Settings.Secure.getUriFor(
513 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
517 @Override public void onChange(boolean selfChange, Uri uri) {
518 final Uri showImeUri = Settings.Secure.getUriFor(
519 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
520 final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
521 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
522 synchronized (mMethodMap) {
523 if (showImeUri.equals(uri)) {
524 updateKeyboardFromSettingsLocked();
525 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
526 mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
527 mContext.getContentResolver(),
528 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
530 if (mAccessibilityRequestingNoSoftKeyboard) {
531 final boolean showRequested = mShowRequested;
532 hideCurrentInputLocked(0, null);
533 mShowRequested = showRequested;
534 } else if (mShowRequested) {
535 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
538 boolean enabledChanged = false;
539 String newEnabled = mSettings.getEnabledInputMethodsStr();
540 if (!mLastEnabled.equals(newEnabled)) {
541 mLastEnabled = newEnabled;
542 enabledChanged = true;
544 updateInputMethodsFromSettingsLocked(enabledChanged);
550 public String toString() {
551 return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
552 + " mLastEnabled=" + mLastEnabled + "}";
556 class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
558 public void onReceive(Context context, Intent intent) {
559 final String action = intent.getAction();
560 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
561 hideInputMethodMenu();
562 // No need to update mIsInteractive
564 } else if (Intent.ACTION_USER_ADDED.equals(action)
565 || Intent.ACTION_USER_REMOVED.equals(action)) {
566 updateCurrentProfileIds();
568 } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
569 final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
570 if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) {
571 final String prevValue = intent.getStringExtra(
572 Intent.EXTRA_SETTING_PREVIOUS_VALUE);
573 final String newValue = intent.getStringExtra(
574 Intent.EXTRA_SETTING_NEW_VALUE);
575 restoreEnabledInputMethods(mContext, prevValue, newValue);
578 Slog.w(TAG, "Unexpected intent " + intent);
583 // Apply the results of a restore operation to the set of enabled IMEs. Note that this
584 // does not attempt to validate on the fly with any installed device policy, so must only
585 // be run in the context of initial device setup.
587 // TODO: Move this method to InputMethodUtils with adding unit tests.
588 static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
590 Slog.i(TAG, "Restoring enabled input methods:");
591 Slog.i(TAG, "prev=" + prevValue);
592 Slog.i(TAG, " new=" + newValue);
594 // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore
595 ArrayMap<String, ArraySet<String>> prevMap =
596 InputMethodUtils.parseInputMethodsAndSubtypesString(prevValue);
597 ArrayMap<String, ArraySet<String>> newMap =
598 InputMethodUtils.parseInputMethodsAndSubtypesString(newValue);
600 // Merge the restored ime+subtype enabled states into the live state
601 for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) {
602 final String imeId = entry.getKey();
603 ArraySet<String> prevSubtypes = prevMap.get(imeId);
604 if (prevSubtypes == null) {
605 prevSubtypes = new ArraySet<>(2);
606 prevMap.put(imeId, prevSubtypes);
608 prevSubtypes.addAll(entry.getValue());
611 final String mergedImesAndSubtypesString =
612 InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap);
614 Slog.i(TAG, "Merged IME string:");
615 Slog.i(TAG, " " + mergedImesAndSubtypesString);
617 Settings.Secure.putString(context.getContentResolver(),
618 Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
621 class MyPackageMonitor extends PackageMonitor {
622 private boolean isChangingPackagesOfCurrentUser() {
623 final int userId = getChangingUserId();
624 final boolean retval = userId == mSettings.getCurrentUserId();
627 Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
634 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
635 if (!isChangingPackagesOfCurrentUser()) {
638 synchronized (mMethodMap) {
639 String curInputMethodId = mSettings.getSelectedInputMethod();
640 final int N = mMethodList.size();
641 if (curInputMethodId != null) {
642 for (int i=0; i<N; i++) {
643 InputMethodInfo imi = mMethodList.get(i);
644 if (imi.getId().equals(curInputMethodId)) {
645 for (String pkg : packages) {
646 if (imi.getPackageName().equals(pkg)) {
650 resetSelectedInputMethodAndSubtypeLocked("");
651 chooseNewDefaultIMELocked();
663 public void onSomePackagesChanged() {
664 if (!isChangingPackagesOfCurrentUser()) {
667 synchronized (mMethodMap) {
668 InputMethodInfo curIm = null;
669 String curInputMethodId = mSettings.getSelectedInputMethod();
670 final int N = mMethodList.size();
671 if (curInputMethodId != null) {
672 for (int i=0; i<N; i++) {
673 InputMethodInfo imi = mMethodList.get(i);
674 final String imiId = imi.getId();
675 if (imiId.equals(curInputMethodId)) {
679 int change = isPackageDisappearing(imi.getPackageName());
680 if (isPackageModified(imi.getPackageName())) {
681 mFileManager.deleteAllInputMethodSubtypes(imiId);
683 if (change == PACKAGE_TEMPORARY_CHANGE
684 || change == PACKAGE_PERMANENT_CHANGE) {
685 Slog.i(TAG, "Input method uninstalled, disabling: "
686 + imi.getComponent());
687 setInputMethodEnabledLocked(imi.getId(), false);
692 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
694 boolean changed = false;
697 int change = isPackageDisappearing(curIm.getPackageName());
698 if (change == PACKAGE_TEMPORARY_CHANGE
699 || change == PACKAGE_PERMANENT_CHANGE) {
700 ServiceInfo si = null;
702 si = mIPackageManager.getServiceInfo(
703 curIm.getComponent(), 0, mSettings.getCurrentUserId());
704 } catch (RemoteException ex) {
707 // Uh oh, current input method is no longer around!
708 // Pick another one...
709 Slog.i(TAG, "Current input method removed: " + curInputMethodId);
710 updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
711 if (!chooseNewDefaultIMELocked()) {
714 Slog.i(TAG, "Unsetting current input method");
715 resetSelectedInputMethodAndSubtypeLocked("");
722 // We currently don't have a default input method... is
723 // one now available?
724 changed = chooseNewDefaultIMELocked();
725 } else if (!changed && isPackageModified(curIm.getPackageName())) {
726 // Even if the current input method is still available, mCurrentSubtype could
727 // be obsolete when the package is modified in practice.
732 updateFromSettingsLocked(false);
738 private static final class MethodCallback extends IInputSessionCallback.Stub {
739 private final InputMethodManagerService mParentIMMS;
740 private final IInputMethod mMethod;
741 private final InputChannel mChannel;
743 MethodCallback(InputMethodManagerService imms, IInputMethod method,
744 InputChannel channel) {
751 public void sessionCreated(IInputMethodSession session) {
752 long ident = Binder.clearCallingIdentity();
754 mParentIMMS.onSessionCreated(mMethod, session, mChannel);
756 Binder.restoreCallingIdentity(ident);
761 private class HardKeyboardListener
762 implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
764 public void onHardKeyboardStatusChange(boolean available) {
765 mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
769 public void handleHardKeyboardStatusChange(boolean available) {
771 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
773 synchronized(mMethodMap) {
774 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
775 && mSwitchingDialog.isShowing()) {
776 mSwitchingDialogTitleView.findViewById(
777 com.android.internal.R.id.hard_keyboard_section).setVisibility(
778 available ? View.VISIBLE : View.GONE);
784 public static final class Lifecycle extends SystemService {
785 private InputMethodManagerService mService;
787 public Lifecycle(Context context) {
789 mService = new InputMethodManagerService(context);
793 public void onStart() {
794 LocalServices.addService(InputMethodManagerInternal.class,
795 new LocalServiceImpl(mService.mHandler));
796 publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
800 public void onSwitchUser(@UserIdInt int userHandle) {
801 // Called on the system server's main looper thread.
802 // TODO: Dispatch this to a worker thread as needed.
803 mService.onSwitchUser(userHandle);
807 public void onBootPhase(int phase) {
808 // Called on the system server's main looper thread.
809 // TODO: Dispatch this to a worker thread as needed.
810 if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
811 StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
812 .getService(Context.STATUS_BAR_SERVICE);
813 mService.systemRunning(statusBarService);
818 public void onUnlockUser(@UserIdInt int userHandle) {
819 // Called on the system server's main looper thread.
820 // TODO: Dispatch this to a worker thread as needed.
821 mService.onUnlockUser(userHandle);
825 void onUnlockUser(@UserIdInt int userId) {
826 synchronized(mMethodMap) {
827 final int currentUserId = mSettings.getCurrentUserId();
829 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
831 if (userId != currentUserId) {
834 mSettings.switchCurrentUser(currentUserId, !mSystemReady);
835 // We need to rebuild IMEs.
836 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
837 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
841 void onSwitchUser(@UserIdInt int userId) {
842 synchronized (mMethodMap) {
843 switchUserLocked(userId);
847 public InputMethodManagerService(Context context) {
848 mIPackageManager = AppGlobals.getPackageManager();
850 mRes = context.getResources();
851 mHandler = new Handler(this);
852 // Note: SettingsObserver doesn't register observers in its constructor.
853 mSettingsObserver = new SettingsObserver(mHandler);
854 mIWindowManager = IWindowManager.Stub.asInterface(
855 ServiceManager.getService(Context.WINDOW_SERVICE));
856 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
857 mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
859 public void executeMessage(Message msg) {
862 }, true /*asyncHandler*/);
863 mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
864 mUserManager = mContext.getSystemService(UserManager.class);
865 mHardKeyboardListener = new HardKeyboardListener();
866 mHasFeature = context.getPackageManager().hasSystemFeature(
867 PackageManager.FEATURE_INPUT_METHODS);
868 mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
869 mHardKeyboardBehavior = mContext.getResources().getInteger(
870 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
872 Bundle extras = new Bundle();
873 extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
874 mImeSwitcherNotification = new Notification.Builder(mContext)
875 .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
879 .setCategory(Notification.CATEGORY_SYSTEM)
880 .setColor(com.android.internal.R.color.system_notification_accent_color);
882 Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
883 mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
885 mShowOngoingImeSwitcherForPhones = false;
887 final IntentFilter broadcastFilter = new IntentFilter();
888 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
889 broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
890 broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
891 broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
892 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
894 mNotificationShown = false;
897 userId = ActivityManagerNative.getDefault().getCurrentUser().id;
898 } catch (RemoteException e) {
899 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
901 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
903 // mSettings should be created before buildInputMethodListLocked
904 mSettings = new InputMethodSettings(
905 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
907 updateCurrentProfileIds();
908 mFileManager = new InputMethodFileManager(mMethodMap, userId);
909 synchronized (mMethodMap) {
910 mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
914 // Just checking if defaultImiId is empty or not
915 final String defaultImiId = mSettings.getSelectedInputMethod();
917 Slog.d(TAG, "Initial default ime = " + defaultImiId);
919 mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
921 synchronized (mMethodMap) {
922 buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
924 mSettings.enableAllIMEsIfThereIsNoEnabledIME();
926 if (!mImeSelectedOnBoot) {
927 Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
928 synchronized (mMethodMap) {
929 resetDefaultImeLocked(context);
933 synchronized (mMethodMap) {
934 mSettingsObserver.registerContentObserverLocked(userId);
935 updateFromSettingsLocked(true);
938 // IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
939 // according to the new system locale.
940 final IntentFilter filter = new IntentFilter();
941 filter.addAction(Intent.ACTION_LOCALE_CHANGED);
942 mContext.registerReceiver(
943 new BroadcastReceiver() {
945 public void onReceive(Context context, Intent intent) {
946 synchronized(mMethodMap) {
947 resetStateIfCurrentLocaleChangedLocked();
953 private void resetDefaultImeLocked(Context context) {
954 // Do not reset the default (current) IME when it is a 3rd-party IME
955 if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
958 final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
959 context, mSystemReady, mSettings.getEnabledInputMethodListLocked());
960 if (suitableImes.isEmpty()) {
961 Slog.i(TAG, "No default found");
964 final InputMethodInfo defIm = suitableImes.get(0);
965 Slog.i(TAG, "Default found, using " + defIm.getId());
966 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
969 private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
970 final boolean resetDefaultEnabledIme) {
975 final LocaleList newLocales = mRes.getConfiguration().getLocales();
976 if (!updateOnlyWhenLocaleChanged
977 || (newLocales != null && !newLocales.equals(mLastSystemLocales))) {
978 if (!updateOnlyWhenLocaleChanged) {
979 hideCurrentInputLocked(0, null);
980 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_RESET_IME);
983 Slog.i(TAG, "LocaleList has been changed to " + newLocales);
985 buildInputMethodListLocked(resetDefaultEnabledIme);
986 if (!updateOnlyWhenLocaleChanged) {
987 final String selectedImiId = mSettings.getSelectedInputMethod();
988 if (TextUtils.isEmpty(selectedImiId)) {
989 // This is the first time of the user switch and
990 // set the current ime to the proper one.
991 resetDefaultImeLocked(mContext);
994 // If the locale is changed, needs to reset the default ime
995 resetDefaultImeLocked(mContext);
997 updateFromSettingsLocked(true);
998 mLastSystemLocales = newLocales;
999 if (!updateOnlyWhenLocaleChanged) {
1001 startInputInnerLocked();
1002 } catch (RuntimeException e) {
1003 Slog.w(TAG, "Unexpected exception", e);
1009 private void resetStateIfCurrentLocaleChangedLocked() {
1010 resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */,
1011 true /* resetDefaultImeLocked */);
1014 private void switchUserLocked(int newUserId) {
1015 if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
1016 + " currentUserId=" + mSettings.getCurrentUserId());
1018 // ContentObserver should be registered again when the user is changed
1019 mSettingsObserver.registerContentObserverLocked(newUserId);
1021 // If the system is not ready or the device is not yed unlocked by the user, then we use
1022 // copy-on-write settings.
1023 final boolean useCopyOnWriteSettings =
1024 !mSystemReady || !mUserManager.isUserUnlockingOrUnlocked(newUserId);
1025 mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
1026 updateCurrentProfileIds();
1027 // InputMethodFileManager should be reset when the user is changed
1028 mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
1029 final String defaultImiId = mSettings.getSelectedInputMethod();
1031 if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
1032 + " defaultImiId=" + defaultImiId);
1034 // For secondary users, the list of enabled IMEs may not have been updated since the
1035 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
1036 // not be empty even if the IME has been uninstalled by the primary user.
1037 // Even in such cases, IMMS works fine because it will find the most applicable
1038 // IME for that user.
1039 final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
1040 resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */,
1041 initialUserSwitch /* needsToResetDefaultIme */);
1042 if (initialUserSwitch) {
1043 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1044 mSettings.getEnabledInputMethodListLocked(), newUserId,
1045 mContext.getBasePackageName());
1048 if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
1049 + " selectedIme=" + mSettings.getSelectedInputMethod());
1052 void updateCurrentProfileIds() {
1053 mSettings.setCurrentProfileIds(
1054 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
1058 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
1059 throws RemoteException {
1061 return super.onTransact(code, data, reply, flags);
1062 } catch (RuntimeException e) {
1063 // The input method manager only throws security exceptions, so let's
1065 if (!(e instanceof SecurityException)) {
1066 Slog.wtf(TAG, "Input Method Manager Crash", e);
1072 public void systemRunning(StatusBarManagerService statusBar) {
1073 synchronized (mMethodMap) {
1075 Slog.d(TAG, "--- systemReady");
1077 if (!mSystemReady) {
1078 mSystemReady = true;
1079 final int currentUserId = mSettings.getCurrentUserId();
1080 mSettings.switchCurrentUser(currentUserId,
1081 !mUserManager.isUserUnlockingOrUnlocked(currentUserId));
1082 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
1083 mNotificationManager = mContext.getSystemService(NotificationManager.class);
1084 mStatusBar = statusBar;
1085 if (mStatusBar != null) {
1086 mStatusBar.setIconVisibility(mSlotIme, false);
1088 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
1089 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
1090 com.android.internal.R.bool.show_ongoing_ime_switcher);
1091 if (mShowOngoingImeSwitcherForPhones) {
1092 mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
1093 mHardKeyboardListener);
1095 buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
1096 if (!mImeSelectedOnBoot) {
1097 Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
1098 resetStateIfCurrentLocaleChangedLocked();
1099 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1100 mSettings.getEnabledInputMethodListLocked(),
1101 mSettings.getCurrentUserId(), mContext.getBasePackageName());
1103 mLastSystemLocales = mRes.getConfiguration().getLocales();
1105 startInputInnerLocked();
1106 } catch (RuntimeException e) {
1107 Slog.w(TAG, "Unexpected exception", e);
1113 // ---------------------------------------------------------------------------------------
1114 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
1115 // 1) it comes from the system process
1116 // 2) the calling process' user id is identical to the current user id IMMS thinks.
1117 private boolean calledFromValidUser() {
1118 final int uid = Binder.getCallingUid();
1119 final int userId = UserHandle.getUserId(uid);
1121 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
1122 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
1123 + " calling userId = " + userId + ", foreground user id = "
1124 + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
1125 + InputMethodUtils.getApiCallStack());
1127 if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
1131 // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
1132 // foreground user, not for the user of that process. Accordingly InputMethodManagerService
1133 // must not manage background users' states in any functions.
1134 // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
1136 if (mContext.checkCallingOrSelfPermission(
1137 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
1138 == PackageManager.PERMISSION_GRANTED) {
1140 Slog.d(TAG, "--- Access granted because the calling process has "
1141 + "the INTERACT_ACROSS_USERS_FULL permission");
1145 Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
1146 + Debug.getCallers(10));
1152 * Returns true iff the caller is identified to be the current input method with the token.
1153 * @param token The window token given to the input method when it was started.
1154 * @return true if and only if non-null valid token is specified.
1156 private boolean calledWithValidToken(IBinder token) {
1157 if (token == null || mCurToken != token) {
1163 private boolean bindCurrentInputMethodService(
1164 Intent service, ServiceConnection conn, int flags) {
1165 if (service == null || conn == null) {
1166 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
1169 return mContext.bindServiceAsUser(service, conn, flags,
1170 new UserHandle(mSettings.getCurrentUserId()));
1174 public List<InputMethodInfo> getInputMethodList() {
1175 // TODO: Make this work even for non-current users?
1176 if (!calledFromValidUser()) {
1177 return Collections.emptyList();
1179 synchronized (mMethodMap) {
1180 return new ArrayList<>(mMethodList);
1185 public List<InputMethodInfo> getEnabledInputMethodList() {
1186 // TODO: Make this work even for non-current users?
1187 if (!calledFromValidUser()) {
1188 return Collections.emptyList();
1190 synchronized (mMethodMap) {
1191 return mSettings.getEnabledInputMethodListLocked();
1196 * @param imiId if null, returns enabled subtypes for the current imi
1197 * @return enabled subtypes of the specified imi
1200 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1201 boolean allowsImplicitlySelectedSubtypes) {
1202 // TODO: Make this work even for non-current users?
1203 if (!calledFromValidUser()) {
1204 return Collections.emptyList();
1206 synchronized (mMethodMap) {
1207 final InputMethodInfo imi;
1208 if (imiId == null && mCurMethodId != null) {
1209 imi = mMethodMap.get(mCurMethodId);
1211 imi = mMethodMap.get(imiId);
1214 return Collections.emptyList();
1216 return mSettings.getEnabledInputMethodSubtypeListLocked(
1217 mContext, imi, allowsImplicitlySelectedSubtypes);
1222 public void addClient(IInputMethodClient client,
1223 IInputContext inputContext, int uid, int pid) {
1224 if (!calledFromValidUser()) {
1227 synchronized (mMethodMap) {
1228 mClients.put(client.asBinder(), new ClientState(client,
1229 inputContext, uid, pid));
1234 public void removeClient(IInputMethodClient client) {
1235 if (!calledFromValidUser()) {
1238 synchronized (mMethodMap) {
1239 ClientState cs = mClients.remove(client.asBinder());
1241 clearClientSessionLocked(cs);
1242 if (mCurClient == cs) {
1245 if (mCurFocusedWindowClient == cs) {
1246 mCurFocusedWindowClient = null;
1252 void executeOrSendMessage(IInterface target, Message msg) {
1253 if (target.asBinder() instanceof Binder) {
1254 mCaller.sendMessage(msg);
1261 void unbindCurrentClientLocked(
1262 /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1263 if (mCurClient != null) {
1264 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
1265 + mCurClient.client.asBinder());
1266 if (mBoundToMethod) {
1267 mBoundToMethod = false;
1268 if (mCurMethod != null) {
1269 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1270 MSG_UNBIND_INPUT, mCurMethod));
1274 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1275 MSG_SET_ACTIVE, 0, mCurClient));
1276 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1277 MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
1278 mCurClient.sessionRequested = false;
1281 hideInputMethodMenuLocked();
1285 private int getImeShowFlags() {
1288 flags |= InputMethod.SHOW_FORCED
1289 | InputMethod.SHOW_EXPLICIT;
1290 } else if (mShowExplicitlyRequested) {
1291 flags |= InputMethod.SHOW_EXPLICIT;
1296 private int getAppShowFlags() {
1299 flags |= InputMethodManager.SHOW_FORCED;
1300 } else if (!mShowExplicitlyRequested) {
1301 flags |= InputMethodManager.SHOW_IMPLICIT;
1306 InputBindResult attachNewInputLocked(boolean initial) {
1307 if (!mBoundToMethod) {
1308 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1309 MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1310 mBoundToMethod = true;
1312 final SessionState session = mCurClient.curSession;
1314 executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
1315 MSG_START_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
1318 executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
1319 MSG_RESTART_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
1322 if (mShowRequested) {
1323 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1324 showCurrentInputLocked(getAppShowFlags(), null);
1326 return new InputBindResult(session.session,
1327 (session.channel != null ? session.channel.dup() : null),
1328 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
1331 InputBindResult startInputLocked(
1332 /* @InputMethodClient.StartInputReason */ final int startInputReason,
1333 IInputMethodClient client, IInputContext inputContext,
1334 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1335 @Nullable EditorInfo attribute, int controlFlags) {
1336 // If no method is currently selected, do nothing.
1337 if (mCurMethodId == null) {
1341 ClientState cs = mClients.get(client.asBinder());
1343 throw new IllegalArgumentException("unknown client "
1344 + client.asBinder());
1347 if (attribute == null) {
1348 Slog.w(TAG, "Ignoring startInput with null EditorInfo."
1349 + " uid=" + cs.uid + " pid=" + cs.pid);
1354 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1355 // Check with the window manager to make sure this client actually
1356 // has a window with focus. If not, reject. This is thread safe
1357 // because if the focus changes some time before or after, the
1358 // next client receiving focus that has any interest in input will
1359 // be calling through here after that change happens.
1360 Slog.w(TAG, "Starting input on non-focused client " + cs.client
1361 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1364 } catch (RemoteException e) {
1367 return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
1371 InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
1372 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1373 @NonNull EditorInfo attribute, int controlFlags) {
1374 // If no method is currently selected, do nothing.
1375 if (mCurMethodId == null) {
1379 if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
1380 attribute.packageName)) {
1381 Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
1382 + " uid=" + cs.uid + " package=" + attribute.packageName);
1386 if (mCurClient != cs) {
1387 // Was the keyguard locked when switching over to the new client?
1388 mCurClientInKeyguard = isKeyguardLocked();
1389 // If the client is changing, we need to switch over to the new
1391 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
1392 if (DEBUG) Slog.v(TAG, "switching to client: client="
1393 + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
1395 // If the screen is on, inform the new client it is active
1396 if (mIsInteractive) {
1397 executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
1398 MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
1402 // Bump up the sequence for this client and attach it.
1404 if (mCurSeq <= 0) mCurSeq = 1;
1406 mCurInputContext = inputContext;
1407 mCurInputContextMissingMethods = missingMethods;
1408 mCurAttribute = attribute;
1410 // Check if the input method is changing.
1411 if (mCurId != null && mCurId.equals(mCurMethodId)) {
1412 if (cs.curSession != null) {
1413 // Fast case: if we are already connected to the input method,
1414 // then just return it.
1415 return attachNewInputLocked(
1416 (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
1418 if (mHaveConnection) {
1419 if (mCurMethod != null) {
1420 // Return to client, and we will get back with it when
1421 // we have had a session made for it.
1422 requestClientSessionLocked(cs);
1423 return new InputBindResult(null, null, mCurId, mCurSeq,
1424 mCurUserActionNotificationSequenceNumber);
1425 } else if (SystemClock.uptimeMillis()
1426 < (mLastBindTime+TIME_TO_RECONNECT)) {
1427 // In this case we have connected to the service, but
1428 // don't yet have its interface. If it hasn't been too
1429 // long since we did the connection, we'll return to
1430 // the client and wait to get the service interface so
1431 // we can report back. If it has been too long, we want
1432 // to fall through so we can try a disconnect/reconnect
1433 // to see if we can get back in touch with the service.
1434 return new InputBindResult(null, null, mCurId, mCurSeq,
1435 mCurUserActionNotificationSequenceNumber);
1437 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1438 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1443 return startInputInnerLocked();
1446 InputBindResult startInputInnerLocked() {
1447 if (mCurMethodId == null) {
1451 if (!mSystemReady) {
1452 // If the system is not yet ready, we shouldn't be running third
1454 return new InputBindResult(null, null, mCurMethodId, mCurSeq,
1455 mCurUserActionNotificationSequenceNumber);
1458 InputMethodInfo info = mMethodMap.get(mCurMethodId);
1460 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1463 unbindCurrentMethodLocked(true);
1465 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1466 mCurIntent.setComponent(info.getComponent());
1467 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1468 com.android.internal.R.string.input_method_binding_label);
1469 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1470 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1471 if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1472 | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND
1473 | Context.BIND_SHOWING_UI)) {
1474 mLastBindTime = SystemClock.uptimeMillis();
1475 mHaveConnection = true;
1476 mCurId = info.getId();
1477 mCurToken = new Binder();
1479 if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1480 mIWindowManager.addWindowToken(mCurToken,
1481 WindowManager.LayoutParams.TYPE_INPUT_METHOD);
1482 } catch (RemoteException e) {
1484 return new InputBindResult(null, null, mCurId, mCurSeq,
1485 mCurUserActionNotificationSequenceNumber);
1488 Slog.w(TAG, "Failure connecting to input method service: "
1494 private InputBindResult startInput(
1495 /* @InputMethodClient.StartInputReason */ final int startInputReason,
1496 IInputMethodClient client, IInputContext inputContext,
1497 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1498 @Nullable EditorInfo attribute, int controlFlags) {
1499 if (!calledFromValidUser()) {
1502 synchronized (mMethodMap) {
1504 Slog.v(TAG, "startInput: reason="
1505 + InputMethodClient.getStartInputReason(startInputReason)
1506 + " client = " + client.asBinder()
1507 + " inputContext=" + inputContext
1508 + " missingMethods="
1509 + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
1510 + " attribute=" + attribute
1511 + " controlFlags=#" + Integer.toHexString(controlFlags));
1513 final long ident = Binder.clearCallingIdentity();
1515 return startInputLocked(startInputReason, client, inputContext, missingMethods,
1516 attribute, controlFlags);
1518 Binder.restoreCallingIdentity(ident);
1524 public void finishInput(IInputMethodClient client) {
1528 public void onServiceConnected(ComponentName name, IBinder service) {
1529 synchronized (mMethodMap) {
1530 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
1531 mCurMethod = IInputMethod.Stub.asInterface(service);
1532 if (mCurToken == null) {
1533 Slog.w(TAG, "Service connected without a token!");
1534 unbindCurrentMethodLocked(false);
1537 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
1538 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1539 MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
1540 if (mCurClient != null) {
1541 clearClientSessionLocked(mCurClient);
1542 requestClientSessionLocked(mCurClient);
1548 void onSessionCreated(IInputMethod method, IInputMethodSession session,
1549 InputChannel channel) {
1550 synchronized (mMethodMap) {
1551 if (mCurMethod != null && method != null
1552 && mCurMethod.asBinder() == method.asBinder()) {
1553 if (mCurClient != null) {
1554 clearClientSessionLocked(mCurClient);
1555 mCurClient.curSession = new SessionState(mCurClient,
1556 method, session, channel);
1557 InputBindResult res = attachNewInputLocked(true);
1558 if (res.method != null) {
1559 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
1560 MSG_BIND_CLIENT, mCurClient.client, res));
1567 // Session abandoned. Close its associated input channel.
1571 void unbindCurrentMethodLocked(boolean savePosition) {
1572 if (mVisibleBound) {
1573 mContext.unbindService(mVisibleConnection);
1574 mVisibleBound = false;
1577 if (mHaveConnection) {
1578 mContext.unbindService(this);
1579 mHaveConnection = false;
1582 if (mCurToken != null) {
1584 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
1585 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
1586 // The current IME is shown. Hence an IME switch (transition) is happening.
1587 mWindowManagerInternal.saveLastInputMethodWindowForTransition();
1589 mIWindowManager.removeWindowToken(mCurToken);
1590 } catch (RemoteException e) {
1596 clearCurMethodLocked();
1599 void resetCurrentMethodAndClient(
1600 /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1601 mCurMethodId = null;
1602 unbindCurrentMethodLocked(false);
1603 unbindCurrentClientLocked(unbindClientReason);
1606 void requestClientSessionLocked(ClientState cs) {
1607 if (!cs.sessionRequested) {
1608 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
1609 InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
1610 cs.sessionRequested = true;
1611 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
1612 MSG_CREATE_SESSION, mCurMethod, channels[1],
1613 new MethodCallback(this, mCurMethod, channels[0])));
1617 void clearClientSessionLocked(ClientState cs) {
1618 finishSessionLocked(cs.curSession);
1619 cs.curSession = null;
1620 cs.sessionRequested = false;
1623 private void finishSessionLocked(SessionState sessionState) {
1624 if (sessionState != null) {
1625 if (sessionState.session != null) {
1627 sessionState.session.finishSession();
1628 } catch (RemoteException e) {
1629 Slog.w(TAG, "Session failed to close due to remote exception", e);
1630 updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
1632 sessionState.session = null;
1634 if (sessionState.channel != null) {
1635 sessionState.channel.dispose();
1636 sessionState.channel = null;
1641 void clearCurMethodLocked() {
1642 if (mCurMethod != null) {
1643 for (ClientState cs : mClients.values()) {
1644 clearClientSessionLocked(cs);
1647 finishSessionLocked(mEnabledSession);
1648 mEnabledSession = null;
1651 if (mStatusBar != null) {
1652 mStatusBar.setIconVisibility(mSlotIme, false);
1657 public void onServiceDisconnected(ComponentName name) {
1658 synchronized (mMethodMap) {
1659 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1660 + " mCurIntent=" + mCurIntent);
1661 if (mCurMethod != null && mCurIntent != null
1662 && name.equals(mCurIntent.getComponent())) {
1663 clearCurMethodLocked();
1664 // We consider this to be a new bind attempt, since the system
1665 // should now try to restart the service for us.
1666 mLastBindTime = SystemClock.uptimeMillis();
1667 mShowRequested = mInputShown;
1668 mInputShown = false;
1669 if (mCurClient != null) {
1670 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1671 MSG_UNBIND_CLIENT, InputMethodClient.UNBIND_REASON_DISCONNECT_IME,
1672 mCurSeq, mCurClient.client));
1679 public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1680 long ident = Binder.clearCallingIdentity();
1682 synchronized (mMethodMap) {
1683 if (!calledWithValidToken(token)) {
1684 final int uid = Binder.getCallingUid();
1685 Slog.e(TAG, "Ignoring updateStatusIcon due to an invalid token. uid:" + uid
1686 + " token:" + token);
1690 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1691 if (mStatusBar != null) {
1692 mStatusBar.setIconVisibility(mSlotIme, false);
1694 } else if (packageName != null) {
1695 if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1696 CharSequence contentDescription = null;
1698 // Use PackageManager to load label
1699 final PackageManager packageManager = mContext.getPackageManager();
1700 contentDescription = packageManager.getApplicationLabel(
1701 mIPackageManager.getApplicationInfo(packageName, 0,
1702 mSettings.getCurrentUserId()));
1703 } catch (RemoteException e) {
1706 if (mStatusBar != null) {
1707 mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
1708 contentDescription != null
1709 ? contentDescription.toString() : null);
1710 mStatusBar.setIconVisibility(mSlotIme, true);
1715 Binder.restoreCallingIdentity(ident);
1719 private boolean shouldShowImeSwitcherLocked(int visibility) {
1720 if (!mShowOngoingImeSwitcherForPhones) return false;
1721 if (mSwitchingDialog != null) return false;
1722 if (isScreenLocked()) return false;
1723 if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
1724 if (mWindowManagerInternal.isHardKeyboardAvailable()) {
1725 if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
1726 // When physical keyboard is attached, we show the ime switcher (or notification if
1727 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
1728 // exists in the IME switcher dialog. Might be OK to remove this condition once
1729 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
1732 } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
1736 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1737 final int N = imis.size();
1738 if (N > 2) return true;
1739 if (N < 1) return false;
1740 int nonAuxCount = 0;
1742 InputMethodSubtype nonAuxSubtype = null;
1743 InputMethodSubtype auxSubtype = null;
1744 for(int i = 0; i < N; ++i) {
1745 final InputMethodInfo imi = imis.get(i);
1746 final List<InputMethodSubtype> subtypes =
1747 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
1748 final int subtypeCount = subtypes.size();
1749 if (subtypeCount == 0) {
1752 for (int j = 0; j < subtypeCount; ++j) {
1753 final InputMethodSubtype subtype = subtypes.get(j);
1754 if (!subtype.isAuxiliary()) {
1756 nonAuxSubtype = subtype;
1759 auxSubtype = subtype;
1764 if (nonAuxCount > 1 || auxCount > 1) {
1766 } else if (nonAuxCount == 1 && auxCount == 1) {
1767 if (nonAuxSubtype != null && auxSubtype != null
1768 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1769 || auxSubtype.overridesImplicitlyEnabledSubtype()
1770 || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1771 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1779 private boolean isKeyguardLocked() {
1780 return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1783 @SuppressWarnings("deprecation")
1785 public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1786 if (!calledWithValidToken(token)) {
1787 final int uid = Binder.getCallingUid();
1788 Slog.e(TAG, "Ignoring setImeWindowStatus due to an invalid token. uid:" + uid
1789 + " token:" + token);
1793 synchronized (mMethodMap) {
1794 mImeWindowVis = vis;
1795 mBackDisposition = backDisposition;
1796 updateSystemUiLocked(token, vis, backDisposition);
1800 private void updateSystemUi(IBinder token, int vis, int backDisposition) {
1801 synchronized (mMethodMap) {
1802 updateSystemUiLocked(token, vis, backDisposition);
1806 // Caution! This method is called in this class. Handle multi-user carefully
1807 private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
1808 if (!calledWithValidToken(token)) {
1809 final int uid = Binder.getCallingUid();
1810 Slog.e(TAG, "Ignoring updateSystemUiLocked due to an invalid token. uid:" + uid
1811 + " token:" + token);
1815 // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
1816 // all updateSystemUi happens on system previlege.
1817 final long ident = Binder.clearCallingIdentity();
1819 // apply policy for binder calls
1820 if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
1823 // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
1824 final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
1825 if (mStatusBar != null) {
1826 mStatusBar.setImeWindowStatus(token, vis, backDisposition,
1827 needsToShowImeSwitcher);
1829 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1830 if (imi != null && needsToShowImeSwitcher) {
1831 // Used to load label
1832 final CharSequence title = mRes.getText(
1833 com.android.internal.R.string.select_input_method);
1834 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
1835 mContext, imi, mCurrentSubtype);
1836 mImeSwitcherNotification.setContentTitle(title)
1837 .setContentText(summary)
1838 .setContentIntent(mImeSwitchPendingIntent);
1840 if ((mNotificationManager != null)
1841 && !mIWindowManager.hasNavigationBar()) {
1843 Slog.d(TAG, "--- show notification: label = " + summary);
1845 mNotificationManager.notifyAsUser(null,
1846 com.android.internal.R.string.select_input_method,
1847 mImeSwitcherNotification.build(), UserHandle.ALL);
1848 mNotificationShown = true;
1850 } catch (RemoteException e) {
1853 if (mNotificationShown && mNotificationManager != null) {
1855 Slog.d(TAG, "--- hide notification");
1857 mNotificationManager.cancelAsUser(null,
1858 com.android.internal.R.string.select_input_method, UserHandle.ALL);
1859 mNotificationShown = false;
1863 Binder.restoreCallingIdentity(ident);
1868 public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1869 if (!calledFromValidUser()) {
1872 synchronized (mMethodMap) {
1873 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1874 for (int i = 0; i < spans.length; ++i) {
1875 SuggestionSpan ss = spans[i];
1876 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1877 mSecureSuggestionSpans.put(ss, currentImi);
1884 public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1885 if (!calledFromValidUser()) {
1888 synchronized (mMethodMap) {
1889 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1890 // TODO: Do not send the intent if the process of the targetImi is already dead.
1891 if (targetImi != null) {
1892 final String[] suggestions = span.getSuggestions();
1893 if (index < 0 || index >= suggestions.length) return false;
1894 final String className = span.getNotificationTargetClassName();
1895 final Intent intent = new Intent();
1896 // Ensures that only a class in the original IME package will receive the
1898 intent.setClassName(targetImi.getPackageName(), className);
1899 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1900 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1901 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1902 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1903 final long ident = Binder.clearCallingIdentity();
1905 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1907 Binder.restoreCallingIdentity(ident);
1915 void updateFromSettingsLocked(boolean enabledMayChange) {
1916 updateInputMethodsFromSettingsLocked(enabledMayChange);
1917 updateKeyboardFromSettingsLocked();
1920 void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
1921 if (enabledMayChange) {
1922 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1923 for (int i=0; i<enabled.size(); i++) {
1924 // We allow the user to select "disabled until used" apps, so if they
1925 // are enabling one of those here we now need to make it enabled.
1926 InputMethodInfo imm = enabled.get(i);
1928 ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
1929 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
1930 mSettings.getCurrentUserId());
1931 if (ai != null && ai.enabledSetting
1932 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
1934 Slog.d(TAG, "Update state(" + imm.getId()
1935 + "): DISABLED_UNTIL_USED -> DEFAULT");
1937 mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
1938 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
1939 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
1940 mContext.getBasePackageName());
1942 } catch (RemoteException e) {
1946 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1947 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1948 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1950 String id = mSettings.getSelectedInputMethod();
1951 // There is no input method selected, try to choose new applicable input method.
1952 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1953 id = mSettings.getSelectedInputMethod();
1955 if (!TextUtils.isEmpty(id)) {
1957 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
1958 } catch (IllegalArgumentException e) {
1959 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1960 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
1962 mShortcutInputMethodsAndSubtypes.clear();
1964 // There is no longer an input method set, so stop any current one.
1965 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
1967 // Here is not the perfect place to reset the switching controller. Ideally
1968 // mSwitchingController and mSettings should be able to share the same state.
1969 // TODO: Make sure that mSwitchingController and mSettings are sharing the
1970 // the same enabled IMEs list.
1971 mSwitchingController.resetCircularListLocked(mContext);
1975 public void updateKeyboardFromSettingsLocked() {
1976 mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
1977 if (mSwitchingDialog != null
1978 && mSwitchingDialogTitleView != null
1979 && mSwitchingDialog.isShowing()) {
1980 final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
1981 com.android.internal.R.id.hard_keyboard_switch);
1982 hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
1986 private void notifyInputMethodSubtypeChanged(final int userId,
1987 @Nullable final InputMethodInfo inputMethodInfo,
1988 @Nullable final InputMethodSubtype subtype) {
1989 final InputManagerInternal inputManagerInternal =
1990 LocalServices.getService(InputManagerInternal.class);
1991 if (inputManagerInternal != null) {
1992 inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
1996 /* package */ void setInputMethodLocked(String id, int subtypeId) {
1997 InputMethodInfo info = mMethodMap.get(id);
1999 throw new IllegalArgumentException("Unknown id: " + id);
2002 // See if we need to notify a subtype change within the same IME.
2003 if (id.equals(mCurMethodId)) {
2004 final int subtypeCount = info.getSubtypeCount();
2005 if (subtypeCount <= 0) {
2008 final InputMethodSubtype oldSubtype = mCurrentSubtype;
2009 final InputMethodSubtype newSubtype;
2010 if (subtypeId >= 0 && subtypeId < subtypeCount) {
2011 newSubtype = info.getSubtypeAt(subtypeId);
2013 // If subtype is null, try to find the most applicable one from
2014 // getCurrentInputMethodSubtype.
2015 newSubtype = getCurrentInputMethodSubtypeLocked();
2017 if (newSubtype == null || oldSubtype == null) {
2018 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
2019 + ", new subtype = " + newSubtype);
2022 if (newSubtype != oldSubtype) {
2023 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
2024 if (mCurMethod != null) {
2026 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
2027 mCurMethod.changeInputMethodSubtype(newSubtype);
2028 } catch (RemoteException e) {
2029 Slog.w(TAG, "Failed to call changeInputMethodSubtype");
2033 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
2038 // Changing to a different IME.
2039 final long ident = Binder.clearCallingIdentity();
2041 // Set a subtype to this input method.
2042 // subtypeId the name of a subtype which will be set.
2043 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
2044 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
2045 // because mCurMethodId is stored as a history in
2046 // setSelectedInputMethodAndSubtypeLocked().
2049 if (ActivityManagerNative.isSystemReady()) {
2050 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
2051 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2052 intent.putExtra("input_method_id", id);
2053 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2055 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
2057 Binder.restoreCallingIdentity(ident);
2060 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
2061 getCurrentInputMethodSubtypeLocked());
2065 public boolean showSoftInput(IInputMethodClient client, int flags,
2066 ResultReceiver resultReceiver) {
2067 if (!calledFromValidUser()) {
2070 int uid = Binder.getCallingUid();
2071 long ident = Binder.clearCallingIdentity();
2073 synchronized (mMethodMap) {
2074 if (mCurClient == null || client == null
2075 || mCurClient.client.asBinder() != client.asBinder()) {
2077 // We need to check if this is the current client with
2078 // focus in the window manager, to allow this call to
2079 // be made before input is started in it.
2080 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2081 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
2084 } catch (RemoteException e) {
2089 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
2090 return showCurrentInputLocked(flags, resultReceiver);
2093 Binder.restoreCallingIdentity(ident);
2097 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2098 mShowRequested = true;
2099 if (mAccessibilityRequestingNoSoftKeyboard) {
2103 if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
2104 mShowExplicitlyRequested = true;
2106 } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
2107 mShowExplicitlyRequested = true;
2110 if (!mSystemReady) {
2114 boolean res = false;
2115 if (mCurMethod != null) {
2116 if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
2117 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
2118 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
2121 if (mHaveConnection && !mVisibleBound) {
2122 bindCurrentInputMethodService(
2123 mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
2124 | Context.BIND_TREAT_LIKE_ACTIVITY
2125 | Context.BIND_FOREGROUND_SERVICE);
2126 mVisibleBound = true;
2129 } else if (mHaveConnection && SystemClock.uptimeMillis()
2130 >= (mLastBindTime+TIME_TO_RECONNECT)) {
2131 // The client has asked to have the input method shown, but
2132 // we have been sitting here too long with a connection to the
2133 // service and no interface received, so let's disconnect/connect
2134 // to try to prod things along.
2135 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
2136 SystemClock.uptimeMillis()-mLastBindTime,1);
2137 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
2138 mContext.unbindService(this);
2139 bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
2140 | Context.BIND_NOT_VISIBLE);
2143 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
2144 + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
2152 public boolean hideSoftInput(IInputMethodClient client, int flags,
2153 ResultReceiver resultReceiver) {
2154 if (!calledFromValidUser()) {
2157 int uid = Binder.getCallingUid();
2158 long ident = Binder.clearCallingIdentity();
2160 synchronized (mMethodMap) {
2161 if (mCurClient == null || client == null
2162 || mCurClient.client.asBinder() != client.asBinder()) {
2164 // We need to check if this is the current client with
2165 // focus in the window manager, to allow this call to
2166 // be made before input is started in it.
2167 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2168 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
2169 + uid + ": " + client);
2172 } catch (RemoteException e) {
2177 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
2178 return hideCurrentInputLocked(flags, resultReceiver);
2181 Binder.restoreCallingIdentity(ident);
2185 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2186 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
2187 && (mShowExplicitlyRequested || mShowForced)) {
2188 if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
2191 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
2192 if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
2196 // There is a chance that IMM#hideSoftInput() is called in a transient state where
2197 // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
2198 // to be updated with the new value sent from IME process. Even in such a transient state
2199 // historically we have accepted an incoming call of IMM#hideSoftInput() from the
2200 // application process as a valid request, and have even promised such a behavior with CTS
2201 // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
2202 // IMMS#InputShown indicates that the software keyboard is shown.
2203 // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
2204 final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
2205 (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
2207 if (shouldHideSoftInput) {
2208 // The IME will report its visible state again after the following message finally
2209 // delivered to the IME process as an IPC. Hence the inconsistency between
2210 // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
2212 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2213 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
2218 if (mHaveConnection && mVisibleBound) {
2219 mContext.unbindService(mVisibleConnection);
2220 mVisibleBound = false;
2222 mInputShown = false;
2223 mShowRequested = false;
2224 mShowExplicitlyRequested = false;
2225 mShowForced = false;
2230 public InputBindResult startInputOrWindowGainedFocus(
2231 /* @InputMethodClient.StartInputReason */ final int startInputReason,
2232 IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2233 int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
2234 /* @InputConnectionInspector.missingMethods */ final int missingMethods) {
2235 if (windowToken != null) {
2236 return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
2237 softInputMode, windowFlags, attribute, inputContext, missingMethods);
2239 return startInput(startInputReason, client, inputContext, missingMethods, attribute,
2244 private InputBindResult windowGainedFocus(
2245 /* @InputMethodClient.StartInputReason */ final int startInputReason,
2246 IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2247 int windowFlags, EditorInfo attribute, IInputContext inputContext,
2248 /* @InputConnectionInspector.missingMethods */ final int missingMethods) {
2249 // Needs to check the validity before clearing calling identity
2250 final boolean calledFromValidUser = calledFromValidUser();
2251 InputBindResult res = null;
2252 long ident = Binder.clearCallingIdentity();
2254 synchronized (mMethodMap) {
2255 if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
2256 + InputMethodClient.getStartInputReason(startInputReason)
2257 + " client=" + client.asBinder()
2258 + " inputContext=" + inputContext
2259 + " missingMethods="
2260 + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
2261 + " attribute=" + attribute
2262 + " controlFlags=#" + Integer.toHexString(controlFlags)
2263 + " softInputMode=#" + Integer.toHexString(softInputMode)
2264 + " windowFlags=#" + Integer.toHexString(windowFlags));
2266 ClientState cs = mClients.get(client.asBinder());
2268 throw new IllegalArgumentException("unknown client "
2269 + client.asBinder());
2273 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
2274 // Check with the window manager to make sure this client actually
2275 // has a window with focus. If not, reject. This is thread safe
2276 // because if the focus changes some time before or after, the
2277 // next client receiving focus that has any interest in input will
2278 // be calling through here after that change happens.
2279 Slog.w(TAG, "Focus gain on non-focused client " + cs.client
2280 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
2283 } catch (RemoteException e) {
2286 if (!calledFromValidUser) {
2287 Slog.w(TAG, "A background user is requesting window. Hiding IME.");
2288 Slog.w(TAG, "If you want to interect with IME, you need "
2289 + "android.permission.INTERACT_ACROSS_USERS_FULL");
2290 hideCurrentInputLocked(0, null);
2294 if (mCurFocusedWindow == windowToken) {
2295 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
2296 + " attribute=" + attribute + ", token = " + windowToken);
2297 if (attribute != null) {
2298 return startInputUncheckedLocked(cs, inputContext, missingMethods,
2299 attribute, controlFlags);
2303 mCurFocusedWindow = windowToken;
2304 mCurFocusedWindowClient = cs;
2306 // Should we auto-show the IME even if the caller has not
2307 // specified what should be done with it?
2308 // We only do this automatically if the window can resize
2309 // to accommodate the IME (so what the user sees will give
2310 // them good context without input information being obscured
2311 // by the IME) or if running on a large screen where there
2312 // is more room for the target window + IME.
2313 final boolean doAutoShow =
2314 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
2315 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
2316 || mRes.getConfiguration().isLayoutSizeAtLeast(
2317 Configuration.SCREENLAYOUT_SIZE_LARGE);
2318 final boolean isTextEditor =
2319 (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
2321 // We want to start input before showing the IME, but after closing
2322 // it. We want to do this after closing it to help the IME disappear
2323 // more quickly (not get stuck behind it initializing itself for the
2324 // new focused input, even if its window wants to hide the IME).
2325 boolean didStart = false;
2327 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
2328 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
2329 if (!isTextEditor || !doAutoShow) {
2330 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
2331 // There is no focus view, and this window will
2332 // be behind any soft input window, so hide the
2333 // soft input window if it is shown.
2334 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
2335 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
2337 } else if (isTextEditor && doAutoShow && (softInputMode &
2338 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2339 // There is a focus view, and we are navigating forward
2340 // into the window, so show the input window for the user.
2341 // We only do this automatically if the window can resize
2342 // to accommodate the IME (so what the user sees will give
2343 // them good context without input information being obscured
2344 // by the IME) or if running on a large screen where there
2345 // is more room for the target window + IME.
2346 if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
2347 if (attribute != null) {
2348 res = startInputUncheckedLocked(cs, inputContext,
2349 missingMethods, attribute, controlFlags);
2352 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2355 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2358 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
2359 if ((softInputMode &
2360 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2361 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
2362 hideCurrentInputLocked(0, null);
2365 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2366 if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2367 hideCurrentInputLocked(0, null);
2369 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
2370 if ((softInputMode &
2371 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2372 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2373 if (attribute != null) {
2374 res = startInputUncheckedLocked(cs, inputContext,
2375 missingMethods, attribute, controlFlags);
2378 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2381 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2382 if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2383 if (attribute != null) {
2384 res = startInputUncheckedLocked(cs, inputContext, missingMethods,
2385 attribute, controlFlags);
2388 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2392 if (!didStart && attribute != null) {
2393 res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
2398 Binder.restoreCallingIdentity(ident);
2405 public void showInputMethodPickerFromClient(
2406 IInputMethodClient client, int auxiliarySubtypeMode) {
2407 if (!calledFromValidUser()) {
2410 synchronized (mMethodMap) {
2411 if (mCurClient == null || client == null
2412 || mCurClient.client.asBinder() != client.asBinder()) {
2413 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2414 + Binder.getCallingUid() + ": " + client);
2417 // Always call subtype picker, because subtype picker is a superset of input method
2419 mHandler.sendMessage(mCaller.obtainMessageI(
2420 MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
2425 public void setInputMethod(IBinder token, String id) {
2426 if (!calledFromValidUser()) {
2429 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2433 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2434 if (!calledFromValidUser()) {
2437 synchronized (mMethodMap) {
2438 if (subtype != null) {
2439 setInputMethodWithSubtypeIdLocked(token, id,
2440 InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
2441 subtype.hashCode()));
2443 setInputMethod(token, id);
2449 public void showInputMethodAndSubtypeEnablerFromClient(
2450 IInputMethodClient client, String inputMethodId) {
2451 if (!calledFromValidUser()) {
2454 synchronized (mMethodMap) {
2455 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2456 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2461 public boolean switchToLastInputMethod(IBinder token) {
2462 if (!calledFromValidUser()) {
2465 synchronized (mMethodMap) {
2466 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2467 final InputMethodInfo lastImi;
2468 if (lastIme != null) {
2469 lastImi = mMethodMap.get(lastIme.first);
2473 String targetLastImiId = null;
2474 int subtypeId = NOT_A_SUBTYPE_ID;
2475 if (lastIme != null && lastImi != null) {
2476 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2477 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2478 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2479 : mCurrentSubtype.hashCode();
2480 // If the last IME is the same as the current IME and the last subtype is not
2481 // defined, there is no need to switch to the last IME.
2482 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2483 targetLastImiId = lastIme.first;
2484 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2488 if (TextUtils.isEmpty(targetLastImiId)
2489 && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
2490 // This is a safety net. If the currentSubtype can't be added to the history
2491 // and the framework couldn't find the last ime, we will make the last ime be
2492 // the most applicable enabled keyboard subtype of the system imes.
2493 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2494 if (enabled != null) {
2495 final int N = enabled.size();
2496 final String locale = mCurrentSubtype == null
2497 ? mRes.getConfiguration().locale.toString()
2498 : mCurrentSubtype.getLocale();
2499 for (int i = 0; i < N; ++i) {
2500 final InputMethodInfo imi = enabled.get(i);
2501 if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
2502 InputMethodSubtype keyboardSubtype =
2503 InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
2504 InputMethodUtils.getSubtypes(imi),
2505 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
2506 if (keyboardSubtype != null) {
2507 targetLastImiId = imi.getId();
2508 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2509 imi, keyboardSubtype.hashCode());
2510 if(keyboardSubtype.getLocale().equals(locale)) {
2519 if (!TextUtils.isEmpty(targetLastImiId)) {
2521 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2522 + ", from: " + mCurMethodId + ", " + subtypeId);
2524 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
2533 public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2534 if (!calledFromValidUser()) {
2537 synchronized (mMethodMap) {
2538 if (!calledWithValidToken(token)) {
2539 final int uid = Binder.getCallingUid();
2540 Slog.e(TAG, "Ignoring switchToNextInputMethod due to an invalid token. uid:" + uid
2541 + " token:" + token);
2544 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2545 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2546 true /* forward */);
2547 if (nextSubtype == null) {
2550 setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
2551 nextSubtype.mSubtypeId);
2557 public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2558 if (!calledFromValidUser()) {
2561 synchronized (mMethodMap) {
2562 if (!calledWithValidToken(token)) {
2563 final int uid = Binder.getCallingUid();
2564 Slog.e(TAG, "Ignoring shouldOfferSwitchingToNextInputMethod due to an invalid "
2565 + "token. uid:" + uid + " token:" + token);
2568 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2569 false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2570 true /* forward */);
2571 if (nextSubtype == null) {
2579 public InputMethodSubtype getLastInputMethodSubtype() {
2580 if (!calledFromValidUser()) {
2583 synchronized (mMethodMap) {
2584 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2585 // TODO: Handle the case of the last IME with no subtypes
2586 if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2587 || TextUtils.isEmpty(lastIme.second)) return null;
2588 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2589 if (lastImi == null) return null;
2591 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2592 final int lastSubtypeId =
2593 InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2594 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2597 return lastImi.getSubtypeAt(lastSubtypeId);
2598 } catch (NumberFormatException e) {
2605 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2606 if (!calledFromValidUser()) {
2609 // By this IPC call, only a process which shares the same uid with the IME can add
2610 // additional input method subtypes to the IME.
2611 if (TextUtils.isEmpty(imiId) || subtypes == null) return;
2612 synchronized (mMethodMap) {
2613 final InputMethodInfo imi = mMethodMap.get(imiId);
2614 if (imi == null) return;
2615 final String[] packageInfos;
2617 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2618 } catch (RemoteException e) {
2619 Slog.e(TAG, "Failed to get package infos");
2622 if (packageInfos != null) {
2623 final int packageNum = packageInfos.length;
2624 for (int i = 0; i < packageNum; ++i) {
2625 if (packageInfos[i].equals(imi.getPackageName())) {
2626 mFileManager.addInputMethodSubtypes(imi, subtypes);
2627 final long ident = Binder.clearCallingIdentity();
2629 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
2631 Binder.restoreCallingIdentity(ident);
2642 public int getInputMethodWindowVisibleHeight() {
2643 return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
2647 public void clearLastInputMethodWindowForTransition(IBinder token) {
2648 if (!calledFromValidUser()) {
2651 final long ident = Binder.clearCallingIdentity();
2653 synchronized (mMethodMap) {
2654 if (!calledWithValidToken(token)) {
2655 final int uid = Binder.getCallingUid();
2656 Slog.e(TAG, "Ignoring clearLastInputMethodWindowForTransition due to an "
2657 + "invalid token. uid:" + uid + " token:" + token);
2661 mWindowManagerInternal.clearLastInputMethodWindowForTransition();
2663 Binder.restoreCallingIdentity(ident);
2668 public void notifyUserAction(int sequenceNumber) {
2670 Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
2672 synchronized (mMethodMap) {
2673 if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
2675 Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
2676 + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
2677 + " actual: " + sequenceNumber);
2681 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2683 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
2688 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2689 synchronized (mMethodMap) {
2690 setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
2694 private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
2695 if (token == null) {
2696 if (mContext.checkCallingOrSelfPermission(
2697 android.Manifest.permission.WRITE_SECURE_SETTINGS)
2698 != PackageManager.PERMISSION_GRANTED) {
2699 throw new SecurityException(
2700 "Using null token requires permission "
2701 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2703 } else if (mCurToken != token) {
2704 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2705 + " token: " + token);
2709 final long ident = Binder.clearCallingIdentity();
2711 setInputMethodLocked(id, subtypeId);
2713 Binder.restoreCallingIdentity(ident);
2718 public void hideMySoftInput(IBinder token, int flags) {
2719 if (!calledFromValidUser()) {
2722 synchronized (mMethodMap) {
2723 if (!calledWithValidToken(token)) {
2724 final int uid = Binder.getCallingUid();
2725 Slog.e(TAG, "Ignoring hideInputMethod due to an invalid token. uid:"
2726 + uid + " token:" + token);
2729 long ident = Binder.clearCallingIdentity();
2731 hideCurrentInputLocked(flags, null);
2733 Binder.restoreCallingIdentity(ident);
2739 public void showMySoftInput(IBinder token, int flags) {
2740 if (!calledFromValidUser()) {
2743 synchronized (mMethodMap) {
2744 if (!calledWithValidToken(token)) {
2745 final int uid = Binder.getCallingUid();
2746 Slog.e(TAG, "Ignoring showMySoftInput due to an invalid token. uid:"
2747 + uid + " token:" + token);
2750 long ident = Binder.clearCallingIdentity();
2752 showCurrentInputLocked(flags, null);
2754 Binder.restoreCallingIdentity(ident);
2759 void setEnabledSessionInMainThread(SessionState session) {
2760 if (mEnabledSession != session) {
2761 if (mEnabledSession != null && mEnabledSession.session != null) {
2763 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2764 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
2765 } catch (RemoteException e) {
2768 mEnabledSession = session;
2769 if (mEnabledSession != null && mEnabledSession.session != null) {
2771 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2772 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
2773 } catch (RemoteException e) {
2780 public boolean handleMessage(Message msg) {
2783 case MSG_SHOW_IM_SUBTYPE_PICKER:
2784 final boolean showAuxSubtypes;
2786 case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
2787 // This is undocumented so far, but IMM#showInputMethodPicker() has been
2788 // implemented so that auxiliary subtypes will be excluded when the soft
2789 // keyboard is invisible.
2790 showAuxSubtypes = mInputShown;
2792 case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
2793 showAuxSubtypes = true;
2795 case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
2796 showAuxSubtypes = false;
2799 Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
2802 showInputMethodMenu(showAuxSubtypes);
2805 case MSG_SHOW_IM_SUBTYPE_ENABLER:
2806 showInputMethodAndSubtypeEnabler((String)msg.obj);
2809 case MSG_SHOW_IM_CONFIG:
2810 showConfigureInputMethods();
2813 // ---------------------------------------------------------
2815 case MSG_UNBIND_INPUT:
2817 ((IInputMethod)msg.obj).unbindInput();
2818 } catch (RemoteException e) {
2819 // There is nothing interesting about the method dying.
2822 case MSG_BIND_INPUT:
2823 args = (SomeArgs)msg.obj;
2825 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2826 } catch (RemoteException e) {
2830 case MSG_SHOW_SOFT_INPUT:
2831 args = (SomeArgs)msg.obj;
2833 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
2834 + msg.arg1 + ", " + args.arg2 + ")");
2835 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
2836 } catch (RemoteException e) {
2840 case MSG_HIDE_SOFT_INPUT:
2841 args = (SomeArgs)msg.obj;
2843 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
2845 ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
2846 } catch (RemoteException e) {
2850 case MSG_HIDE_CURRENT_INPUT_METHOD:
2851 synchronized (mMethodMap) {
2852 hideCurrentInputLocked(0, null);
2855 case MSG_ATTACH_TOKEN:
2856 args = (SomeArgs)msg.obj;
2858 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2859 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2860 } catch (RemoteException e) {
2864 case MSG_CREATE_SESSION: {
2865 args = (SomeArgs)msg.obj;
2866 IInputMethod method = (IInputMethod)args.arg1;
2867 InputChannel channel = (InputChannel)args.arg2;
2869 method.createSession(channel, (IInputSessionCallback)args.arg3);
2870 } catch (RemoteException e) {
2872 // Dispose the channel if the input method is not local to this process
2873 // because the remote proxy will get its own copy when unparceled.
2874 if (channel != null && Binder.isProxy(method)) {
2881 // ---------------------------------------------------------
2883 case MSG_START_INPUT: {
2884 int missingMethods = msg.arg1;
2885 args = (SomeArgs) msg.obj;
2887 SessionState session = (SessionState) args.arg1;
2888 setEnabledSessionInMainThread(session);
2889 session.method.startInput((IInputContext) args.arg2, missingMethods,
2890 (EditorInfo) args.arg3);
2891 } catch (RemoteException e) {
2896 case MSG_RESTART_INPUT: {
2897 int missingMethods = msg.arg1;
2898 args = (SomeArgs) msg.obj;
2900 SessionState session = (SessionState) args.arg1;
2901 setEnabledSessionInMainThread(session);
2902 session.method.restartInput((IInputContext) args.arg2, missingMethods,
2903 (EditorInfo) args.arg3);
2904 } catch (RemoteException e) {
2910 // ---------------------------------------------------------
2912 case MSG_UNBIND_CLIENT:
2914 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
2915 } catch (RemoteException e) {
2916 // There is nothing interesting about the last client dying.
2919 case MSG_BIND_CLIENT: {
2920 args = (SomeArgs)msg.obj;
2921 IInputMethodClient client = (IInputMethodClient)args.arg1;
2922 InputBindResult res = (InputBindResult)args.arg2;
2924 client.onBindMethod(res);
2925 } catch (RemoteException e) {
2926 Slog.w(TAG, "Client died receiving input method " + args.arg2);
2928 // Dispose the channel if the input method is not local to this process
2929 // because the remote proxy will get its own copy when unparceled.
2930 if (res.channel != null && Binder.isProxy(client)) {
2931 res.channel.dispose();
2937 case MSG_SET_ACTIVE:
2939 ((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
2940 } catch (RemoteException e) {
2941 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
2942 + ((ClientState)msg.obj).pid + " uid "
2943 + ((ClientState)msg.obj).uid);
2946 case MSG_SET_INTERACTIVE:
2947 handleSetInteractive(msg.arg1 != 0);
2949 case MSG_SWITCH_IME:
2950 handleSwitchInputMethod(msg.arg1 != 0);
2952 case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
2953 final int sequenceNumber = msg.arg1;
2954 final ClientState clientState = (ClientState)msg.obj;
2956 clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
2957 } catch (RemoteException e) {
2958 Slog.w(TAG, "Got RemoteException sending "
2959 + "setUserActionNotificationSequenceNumber("
2960 + sequenceNumber + ") notification to pid "
2961 + clientState.pid + " uid "
2967 // --------------------------------------------------------------
2968 case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
2969 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
2975 private void handleSetInteractive(final boolean interactive) {
2976 synchronized (mMethodMap) {
2977 mIsInteractive = interactive;
2978 updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
2980 // Inform the current client of the change in active status
2981 if (mCurClient != null && mCurClient.client != null) {
2982 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
2983 MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mCurClient));
2988 private void handleSwitchInputMethod(final boolean forwardDirection) {
2989 synchronized (mMethodMap) {
2990 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2991 false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
2992 if (nextSubtype == null) {
2995 setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
2996 final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
2997 if (newInputMethodInfo == null) {
3000 final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
3001 newInputMethodInfo, mCurrentSubtype);
3002 if (!TextUtils.isEmpty(toastText)) {
3003 if (mSubtypeSwitchedByShortCutToast == null) {
3004 mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText,
3005 Toast.LENGTH_SHORT);
3007 mSubtypeSwitchedByShortCutToast.setText(toastText);
3009 mSubtypeSwitchedByShortCutToast.show();
3014 private boolean chooseNewDefaultIMELocked() {
3015 final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
3016 mSettings.getEnabledInputMethodListLocked());
3019 Slog.d(TAG, "New default IME was selected: " + imi.getId());
3021 resetSelectedInputMethodAndSubtypeLocked(imi.getId());
3028 void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
3030 Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
3031 + " \n ------ caller=" + Debug.getCallers(10));
3033 mMethodList.clear();
3036 // Use for queryIntentServicesAsUser
3037 final PackageManager pm = mContext.getPackageManager();
3039 // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
3040 // behavior of PackageManager is exactly what we want. It by default picks up appropriate
3041 // services depending on the unlock state for the specified user.
3042 final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
3043 new Intent(InputMethod.SERVICE_INTERFACE),
3044 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
3045 mSettings.getCurrentUserId());
3047 final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
3048 mFileManager.getAllAdditionalInputMethodSubtypes();
3049 for (int i = 0; i < services.size(); ++i) {
3050 ResolveInfo ri = services.get(i);
3051 ServiceInfo si = ri.serviceInfo;
3052 ComponentName compName = new ComponentName(si.packageName, si.name);
3053 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
3055 Slog.w(TAG, "Skipping input method " + compName
3056 + ": it does not require the permission "
3057 + android.Manifest.permission.BIND_INPUT_METHOD);
3061 if (DEBUG) Slog.d(TAG, "Checking " + compName);
3064 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
3066 final String id = p.getId();
3067 mMethodMap.put(id, p);
3070 Slog.d(TAG, "Found an input method " + p);
3072 } catch (Exception e) {
3073 Slog.wtf(TAG, "Unable to load input method " + compName, e);
3077 // TODO: The following code should find better place to live.
3078 if (!resetDefaultEnabledIme) {
3079 boolean enabledImeFound = false;
3080 final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
3081 final int N = enabledImes.size();
3082 for (int i = 0; i < N; ++i) {
3083 final InputMethodInfo imi = enabledImes.get(i);
3084 if (mMethodList.contains(imi)) {
3085 enabledImeFound = true;
3089 if (!enabledImeFound) {
3090 Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
3091 resetDefaultEnabledIme = true;
3092 resetSelectedInputMethodAndSubtypeLocked("");
3096 if (resetDefaultEnabledIme) {
3097 final ArrayList<InputMethodInfo> defaultEnabledIme =
3098 InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, mMethodList);
3099 final int N = defaultEnabledIme.size();
3100 for (int i = 0; i < N; ++i) {
3101 final InputMethodInfo imi = defaultEnabledIme.get(i);
3103 Slog.d(TAG, "--- enable ime = " + imi);
3105 setInputMethodEnabledLocked(imi.getId(), true);
3109 final String defaultImiId = mSettings.getSelectedInputMethod();
3110 if (!TextUtils.isEmpty(defaultImiId)) {
3111 if (!mMethodMap.containsKey(defaultImiId)) {
3112 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
3113 if (chooseNewDefaultIMELocked()) {
3114 updateInputMethodsFromSettingsLocked(true);
3117 // Double check that the default IME is certainly enabled.
3118 setInputMethodEnabledLocked(defaultImiId, true);
3121 // Here is not the perfect place to reset the switching controller. Ideally
3122 // mSwitchingController and mSettings should be able to share the same state.
3123 // TODO: Make sure that mSwitchingController and mSettings are sharing the
3124 // the same enabled IMEs list.
3125 mSwitchingController.resetCircularListLocked(mContext);
3128 // ----------------------------------------------------------------------
3130 private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
3131 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
3132 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3133 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3134 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3135 if (!TextUtils.isEmpty(inputMethodId)) {
3136 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
3139 synchronized (mMethodMap) {
3140 userId = mSettings.getCurrentUserId();
3142 mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
3145 private void showConfigureInputMethods() {
3146 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
3147 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3148 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3149 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3150 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
3153 private boolean isScreenLocked() {
3154 return mKeyguardManager != null
3155 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
3158 private void showInputMethodMenu(boolean showAuxSubtypes) {
3159 if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
3161 final Context context = mContext;
3162 final boolean isScreenLocked = isScreenLocked();
3164 final String lastInputMethodId = mSettings.getSelectedInputMethod();
3165 int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
3166 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
3168 synchronized (mMethodMap) {
3169 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3170 mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
3172 if (immis == null || immis.size() == 0) {
3176 hideInputMethodMenuLocked();
3178 final List<ImeSubtypeListItem> imList =
3179 mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
3180 showAuxSubtypes, isScreenLocked);
3182 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
3183 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
3184 if (currentSubtype != null) {
3185 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
3186 lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3187 currentImi, currentSubtype.hashCode());
3191 final int N = imList.size();
3192 mIms = new InputMethodInfo[N];
3193 mSubtypeIds = new int[N];
3194 int checkedItem = 0;
3195 for (int i = 0; i < N; ++i) {
3196 final ImeSubtypeListItem item = imList.get(i);
3197 mIms[i] = item.mImi;
3198 mSubtypeIds[i] = item.mSubtypeId;
3199 if (mIms[i].getId().equals(lastInputMethodId)) {
3200 int subtypeId = mSubtypeIds[i];
3201 if ((subtypeId == NOT_A_SUBTYPE_ID)
3202 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
3203 || (subtypeId == lastInputMethodSubtypeId)) {
3209 final Context settingsContext = new ContextThemeWrapper(context,
3210 com.android.internal.R.style.Theme_DeviceDefault_Settings);
3212 mDialogBuilder = new AlertDialog.Builder(settingsContext);
3213 mDialogBuilder.setOnCancelListener(new OnCancelListener() {
3215 public void onCancel(DialogInterface dialog) {
3216 hideInputMethodMenu();
3220 final Context dialogContext = mDialogBuilder.getContext();
3221 final TypedArray a = dialogContext.obtainStyledAttributes(null,
3222 com.android.internal.R.styleable.DialogPreference,
3223 com.android.internal.R.attr.alertDialogStyle, 0);
3224 final Drawable dialogIcon = a.getDrawable(
3225 com.android.internal.R.styleable.DialogPreference_dialogIcon);
3228 mDialogBuilder.setIcon(dialogIcon);
3230 final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
3231 final View tv = inflater.inflate(
3232 com.android.internal.R.layout.input_method_switch_dialog_title, null);
3233 mDialogBuilder.setCustomTitle(tv);
3235 // Setup layout for a toggle switch of the hardware keyboard
3236 mSwitchingDialogTitleView = tv;
3237 mSwitchingDialogTitleView
3238 .findViewById(com.android.internal.R.id.hard_keyboard_section)
3239 .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
3240 ? View.VISIBLE : View.GONE);
3241 final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
3242 com.android.internal.R.id.hard_keyboard_switch);
3243 hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
3244 hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
3246 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
3247 mSettings.setShowImeWithHardKeyboard(isChecked);
3248 // Ensure that the input method dialog is dismissed when changing
3249 // the hardware keyboard state.
3250 hideInputMethodMenu();
3254 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
3255 com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
3256 final OnClickListener choiceListener = new OnClickListener() {
3258 public void onClick(final DialogInterface dialog, final int which) {
3259 synchronized (mMethodMap) {
3260 if (mIms == null || mIms.length <= which || mSubtypeIds == null
3261 || mSubtypeIds.length <= which) {
3264 final InputMethodInfo im = mIms[which];
3265 int subtypeId = mSubtypeIds[which];
3266 adapter.mCheckedItem = which;
3267 adapter.notifyDataSetChanged();
3268 hideInputMethodMenu();
3270 if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
3271 subtypeId = NOT_A_SUBTYPE_ID;
3273 setInputMethodLocked(im.getId(), subtypeId);
3278 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
3280 mSwitchingDialog = mDialogBuilder.create();
3281 mSwitchingDialog.setCanceledOnTouchOutside(true);
3282 mSwitchingDialog.getWindow().setType(
3283 WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
3284 mSwitchingDialog.getWindow().getAttributes().privateFlags |=
3285 WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
3286 mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
3287 updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
3288 mSwitchingDialog.show();
3292 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
3293 private final LayoutInflater mInflater;
3294 private final int mTextViewResourceId;
3295 private final List<ImeSubtypeListItem> mItemsList;
3296 public int mCheckedItem;
3297 public ImeSubtypeListAdapter(Context context, int textViewResourceId,
3298 List<ImeSubtypeListItem> itemsList, int checkedItem) {
3299 super(context, textViewResourceId, itemsList);
3301 mTextViewResourceId = textViewResourceId;
3302 mItemsList = itemsList;
3303 mCheckedItem = checkedItem;
3304 mInflater = context.getSystemService(LayoutInflater.class);
3308 public View getView(int position, View convertView, ViewGroup parent) {
3309 final View view = convertView != null ? convertView
3310 : mInflater.inflate(mTextViewResourceId, null);
3311 if (position < 0 || position >= mItemsList.size()) return view;
3312 final ImeSubtypeListItem item = mItemsList.get(position);
3313 final CharSequence imeName = item.mImeName;
3314 final CharSequence subtypeName = item.mSubtypeName;
3315 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
3316 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
3317 if (TextUtils.isEmpty(subtypeName)) {
3318 firstTextView.setText(imeName);
3319 secondTextView.setVisibility(View.GONE);
3321 firstTextView.setText(subtypeName);
3322 secondTextView.setText(imeName);
3323 secondTextView.setVisibility(View.VISIBLE);
3325 final RadioButton radioButton =
3326 (RadioButton)view.findViewById(com.android.internal.R.id.radio);
3327 radioButton.setChecked(position == mCheckedItem);
3332 void hideInputMethodMenu() {
3333 synchronized (mMethodMap) {
3334 hideInputMethodMenuLocked();
3338 void hideInputMethodMenuLocked() {
3339 if (DEBUG) Slog.v(TAG, "Hide switching menu");
3341 if (mSwitchingDialog != null) {
3342 mSwitchingDialog.dismiss();
3343 mSwitchingDialog = null;
3346 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
3347 mDialogBuilder = null;
3351 // ----------------------------------------------------------------------
3354 public boolean setInputMethodEnabled(String id, boolean enabled) {
3355 // TODO: Make this work even for non-current users?
3356 if (!calledFromValidUser()) {
3359 synchronized (mMethodMap) {
3360 if (mContext.checkCallingOrSelfPermission(
3361 android.Manifest.permission.WRITE_SECURE_SETTINGS)
3362 != PackageManager.PERMISSION_GRANTED) {
3363 throw new SecurityException(
3364 "Requires permission "
3365 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
3368 long ident = Binder.clearCallingIdentity();
3370 return setInputMethodEnabledLocked(id, enabled);
3372 Binder.restoreCallingIdentity(ident);
3377 boolean setInputMethodEnabledLocked(String id, boolean enabled) {
3378 // Make sure this is a valid input method.
3379 InputMethodInfo imm = mMethodMap.get(id);
3381 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
3384 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
3385 .getEnabledInputMethodsAndSubtypeListLocked();
3388 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
3389 if (pair.first.equals(id)) {
3390 // We are enabling this input method, but it is already enabled.
3391 // Nothing to do. The previous state was enabled.
3395 mSettings.appendAndPutEnabledInputMethodLocked(id, false);
3396 // Previous state was disabled.
3399 StringBuilder builder = new StringBuilder();
3400 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3401 builder, enabledInputMethodsList, id)) {
3402 // Disabled input method is currently selected, switch to another one.
3403 final String selId = mSettings.getSelectedInputMethod();
3404 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
3405 Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
3406 resetSelectedInputMethodAndSubtypeLocked("");
3408 // Previous state was enabled.
3411 // We are disabling the input method but it is already disabled.
3412 // Nothing to do. The previous state was disabled.
3418 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
3419 boolean setSubtypeOnly) {
3420 // Update the history of InputMethod and Subtype
3421 mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
3423 mCurUserActionNotificationSequenceNumber =
3424 Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
3426 Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
3427 + mCurUserActionNotificationSequenceNumber);
3430 if (mCurClient != null && mCurClient.client != null) {
3431 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
3432 MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
3433 mCurUserActionNotificationSequenceNumber, mCurClient));
3437 if (imi == null || subtypeId < 0) {
3438 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3439 mCurrentSubtype = null;
3441 if (subtypeId < imi.getSubtypeCount()) {
3442 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
3443 mSettings.putSelectedSubtype(subtype.hashCode());
3444 mCurrentSubtype = subtype;
3446 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3447 // If the subtype is not specified, choose the most applicable one
3448 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
3452 if (!setSubtypeOnly) {
3453 // Set InputMethod here
3454 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
3458 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
3459 InputMethodInfo imi = mMethodMap.get(newDefaultIme);
3460 int lastSubtypeId = NOT_A_SUBTYPE_ID;
3461 // newDefaultIme is empty when there is no candidate for the selected IME.
3462 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
3463 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
3464 if (subtypeHashCode != null) {
3466 lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3467 imi, Integer.parseInt(subtypeHashCode));
3468 } catch (NumberFormatException e) {
3469 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
3473 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
3476 // If there are no selected shortcuts, tries finding the most applicable ones.
3477 private Pair<InputMethodInfo, InputMethodSubtype>
3478 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
3479 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
3480 InputMethodInfo mostApplicableIMI = null;
3481 InputMethodSubtype mostApplicableSubtype = null;
3482 boolean foundInSystemIME = false;
3484 // Search applicable subtype for each InputMethodInfo
3485 for (InputMethodInfo imi: imis) {
3486 final String imiId = imi.getId();
3487 if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3490 InputMethodSubtype subtype = null;
3491 final List<InputMethodSubtype> enabledSubtypes =
3492 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3493 // 1. Search by the current subtype's locale from enabledSubtypes.
3494 if (mCurrentSubtype != null) {
3495 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3496 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
3498 // 2. Search by the system locale from enabledSubtypes.
3499 // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
3500 if (subtype == null) {
3501 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3502 mRes, enabledSubtypes, mode, null, true);
3504 final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
3505 InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
3506 final ArrayList<InputMethodSubtype> subtypesForSearch =
3507 overridingImplicitlyEnabledSubtypes.isEmpty()
3508 ? InputMethodUtils.getSubtypes(imi)
3509 : overridingImplicitlyEnabledSubtypes;
3510 // 4. Search by the current subtype's locale from all subtypes.
3511 if (subtype == null && mCurrentSubtype != null) {
3512 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3513 mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
3515 // 5. Search by the system locale from all subtypes.
3516 // 6. Search the first enabled subtype matched with mode from all subtypes.
3517 if (subtype == null) {
3518 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3519 mRes, subtypesForSearch, mode, null, true);
3521 if (subtype != null) {
3522 if (imiId.equals(mCurMethodId)) {
3523 // The current input method is the most applicable IME.
3524 mostApplicableIMI = imi;
3525 mostApplicableSubtype = subtype;
3527 } else if (!foundInSystemIME) {
3528 // The system input method is 2nd applicable IME.
3529 mostApplicableIMI = imi;
3530 mostApplicableSubtype = subtype;
3531 if ((imi.getServiceInfo().applicationInfo.flags
3532 & ApplicationInfo.FLAG_SYSTEM) != 0) {
3533 foundInSystemIME = true;
3539 if (mostApplicableIMI != null) {
3540 Slog.w(TAG, "Most applicable shortcut input method was:"
3541 + mostApplicableIMI.getId());
3542 if (mostApplicableSubtype != null) {
3543 Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3544 + "," + mostApplicableSubtype.getMode() + ","
3545 + mostApplicableSubtype.getLocale());
3549 if (mostApplicableIMI != null) {
3550 return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
3557 * @return Return the current subtype of this input method.
3560 public InputMethodSubtype getCurrentInputMethodSubtype() {
3561 // TODO: Make this work even for non-current users?
3562 if (!calledFromValidUser()) {
3565 synchronized (mMethodMap) {
3566 return getCurrentInputMethodSubtypeLocked();
3570 private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3571 if (mCurMethodId == null) {
3574 final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3575 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3576 if (imi == null || imi.getSubtypeCount() == 0) {
3579 if (!subtypeIsSelected || mCurrentSubtype == null
3580 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3581 int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
3582 if (subtypeId == NOT_A_SUBTYPE_ID) {
3583 // If there are no selected subtypes, the framework will try to find
3584 // the most applicable subtype from explicitly or implicitly enabled
3586 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3587 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3588 // If there is only one explicitly or implicitly enabled subtype,
3590 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3591 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3592 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3593 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3594 mRes, explicitlyOrImplicitlyEnabledSubtypes,
3595 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
3596 if (mCurrentSubtype == null) {
3597 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3598 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3603 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3606 return mCurrentSubtype;
3609 // TODO: We should change the return type from List to List<Parcelable>
3610 @SuppressWarnings("rawtypes")
3612 public List getShortcutInputMethodsAndSubtypes() {
3613 synchronized (mMethodMap) {
3614 ArrayList<Object> ret = new ArrayList<>();
3615 if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3616 // If there are no selected shortcut subtypes, the framework will try to find
3617 // the most applicable subtype from all subtypes whose mode is
3618 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3619 Pair<InputMethodInfo, InputMethodSubtype> info =
3620 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3621 InputMethodUtils.SUBTYPE_MODE_VOICE);
3623 ret.add(info.first);
3624 ret.add(info.second);
3628 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3630 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3639 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3640 // TODO: Make this work even for non-current users?
3641 if (!calledFromValidUser()) {
3644 synchronized (mMethodMap) {
3645 if (subtype != null && mCurMethodId != null) {
3646 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3647 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
3648 if (subtypeId != NOT_A_SUBTYPE_ID) {
3649 setInputMethodLocked(mCurMethodId, subtypeId);
3657 // TODO: Cache the state for each user and reset when the cached user is removed.
3658 private static class InputMethodFileManager {
3659 private static final String SYSTEM_PATH = "system";
3660 private static final String INPUT_METHOD_PATH = "inputmethod";
3661 private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3662 private static final String NODE_SUBTYPES = "subtypes";
3663 private static final String NODE_SUBTYPE = "subtype";
3664 private static final String NODE_IMI = "imi";
3665 private static final String ATTR_ID = "id";
3666 private static final String ATTR_LABEL = "label";
3667 private static final String ATTR_ICON = "icon";
3668 private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
3669 private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3670 private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
3671 private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3672 private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3673 private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3674 private static final String ATTR_IS_ASCII_CAPABLE = "isAsciiCapable";
3675 private final AtomicFile mAdditionalInputMethodSubtypeFile;
3676 private final HashMap<String, InputMethodInfo> mMethodMap;
3677 private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3679 public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3680 if (methodMap == null) {
3681 throw new NullPointerException("methodMap is null");
3683 mMethodMap = methodMap;
3684 final File systemDir = userId == UserHandle.USER_SYSTEM
3685 ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3686 : Environment.getUserSystemDirectory(userId);
3687 final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3688 if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
3689 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3691 final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3692 mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3693 if (!subtypeFile.exists()) {
3694 // If "subtypes.xml" doesn't exist, create a blank file.
3695 writeAdditionalInputMethodSubtypes(
3696 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3698 readAdditionalInputMethodSubtypes(
3699 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3703 private void deleteAllInputMethodSubtypes(String imiId) {
3704 synchronized (mMethodMap) {
3705 mAdditionalSubtypesMap.remove(imiId);
3706 writeAdditionalInputMethodSubtypes(
3707 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3711 public void addInputMethodSubtypes(
3712 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3713 synchronized (mMethodMap) {
3714 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
3715 final int N = additionalSubtypes.length;
3716 for (int i = 0; i < N; ++i) {
3717 final InputMethodSubtype subtype = additionalSubtypes[i];
3718 if (!subtypes.contains(subtype)) {
3719 subtypes.add(subtype);
3721 Slog.w(TAG, "Duplicated subtype definition found: "
3722 + subtype.getLocale() + ", " + subtype.getMode());
3725 mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3726 writeAdditionalInputMethodSubtypes(
3727 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3731 public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3732 synchronized (mMethodMap) {
3733 return mAdditionalSubtypesMap;
3737 private static void writeAdditionalInputMethodSubtypes(
3738 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3739 HashMap<String, InputMethodInfo> methodMap) {
3740 // Safety net for the case that this function is called before methodMap is set.
3741 final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3742 FileOutputStream fos = null;
3744 fos = subtypesFile.startWrite();
3745 final XmlSerializer out = new FastXmlSerializer();
3746 out.setOutput(fos, StandardCharsets.UTF_8.name());
3747 out.startDocument(null, true);
3748 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3749 out.startTag(null, NODE_SUBTYPES);
3750 for (String imiId : allSubtypes.keySet()) {
3751 if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3752 Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3755 out.startTag(null, NODE_IMI);
3756 out.attribute(null, ATTR_ID, imiId);
3757 final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3758 final int N = subtypesList.size();
3759 for (int i = 0; i < N; ++i) {
3760 final InputMethodSubtype subtype = subtypesList.get(i);
3761 out.startTag(null, NODE_SUBTYPE);
3762 if (subtype.hasSubtypeId()) {
3763 out.attribute(null, ATTR_IME_SUBTYPE_ID,
3764 String.valueOf(subtype.getSubtypeId()));
3766 out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3767 out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3768 out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3769 out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
3770 subtype.getLanguageTag());
3771 out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3772 out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3773 out.attribute(null, ATTR_IS_AUXILIARY,
3774 String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3775 out.attribute(null, ATTR_IS_ASCII_CAPABLE,
3776 String.valueOf(subtype.isAsciiCapable() ? 1 : 0));
3777 out.endTag(null, NODE_SUBTYPE);
3779 out.endTag(null, NODE_IMI);
3781 out.endTag(null, NODE_SUBTYPES);
3783 subtypesFile.finishWrite(fos);
3784 } catch (java.io.IOException e) {
3785 Slog.w(TAG, "Error writing subtypes", e);
3787 subtypesFile.failWrite(fos);
3792 private static void readAdditionalInputMethodSubtypes(
3793 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3794 if (allSubtypes == null || subtypesFile == null) return;
3795 allSubtypes.clear();
3796 try (final FileInputStream fis = subtypesFile.openRead()) {
3797 final XmlPullParser parser = Xml.newPullParser();
3798 parser.setInput(fis, StandardCharsets.UTF_8.name());
3799 int type = parser.getEventType();
3800 // Skip parsing until START_TAG
3801 while ((type = parser.next()) != XmlPullParser.START_TAG
3802 && type != XmlPullParser.END_DOCUMENT) {}
3803 String firstNodeName = parser.getName();
3804 if (!NODE_SUBTYPES.equals(firstNodeName)) {
3805 throw new XmlPullParserException("Xml doesn't start with subtypes");
3807 final int depth =parser.getDepth();
3808 String currentImiId = null;
3809 ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3810 while (((type = parser.next()) != XmlPullParser.END_TAG
3811 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3812 if (type != XmlPullParser.START_TAG)
3814 final String nodeName = parser.getName();
3815 if (NODE_IMI.equals(nodeName)) {
3816 currentImiId = parser.getAttributeValue(null, ATTR_ID);
3817 if (TextUtils.isEmpty(currentImiId)) {
3818 Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3821 tempSubtypesArray = new ArrayList<>();
3822 allSubtypes.put(currentImiId, tempSubtypesArray);
3823 } else if (NODE_SUBTYPE.equals(nodeName)) {
3824 if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3825 Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3828 final int icon = Integer.parseInt(
3829 parser.getAttributeValue(null, ATTR_ICON));
3830 final int label = Integer.parseInt(
3831 parser.getAttributeValue(null, ATTR_LABEL));
3832 final String imeSubtypeLocale =
3833 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3834 final String languageTag =
3835 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
3836 final String imeSubtypeMode =
3837 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3838 final String imeSubtypeExtraValue =
3839 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3840 final boolean isAuxiliary = "1".equals(String.valueOf(
3841 parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3842 final boolean isAsciiCapable = "1".equals(String.valueOf(
3843 parser.getAttributeValue(null, ATTR_IS_ASCII_CAPABLE)));
3844 final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder()
3845 .setSubtypeNameResId(label)
3846 .setSubtypeIconResId(icon)
3847 .setSubtypeLocale(imeSubtypeLocale)
3848 .setLanguageTag(languageTag)
3849 .setSubtypeMode(imeSubtypeMode)
3850 .setSubtypeExtraValue(imeSubtypeExtraValue)
3851 .setIsAuxiliary(isAuxiliary)
3852 .setIsAsciiCapable(isAsciiCapable);
3853 final String subtypeIdString =
3854 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_ID);
3855 if (subtypeIdString != null) {
3856 builder.setSubtypeId(Integer.parseInt(subtypeIdString));
3858 tempSubtypesArray.add(builder.build());
3861 } catch (XmlPullParserException | IOException | NumberFormatException e) {
3862 Slog.w(TAG, "Error reading subtypes", e);
3868 private static final class LocalServiceImpl implements InputMethodManagerInternal {
3870 private final Handler mHandler;
3872 LocalServiceImpl(@NonNull final Handler handler) {
3877 public void setInteractive(boolean interactive) {
3878 // Do everything in handler so as not to block the caller.
3879 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INTERACTIVE,
3880 interactive ? 1 : 0, 0));
3884 public void switchInputMethod(boolean forwardDirection) {
3885 // Do everything in handler so as not to block the caller.
3886 mHandler.sendMessage(mHandler.obtainMessage(MSG_SWITCH_IME,
3887 forwardDirection ? 1 : 0, 0));
3891 public void hideCurrentInputMethod() {
3892 mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
3893 mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
3897 private static String imeWindowStatusToString(final int imeWindowVis) {
3898 final StringBuilder sb = new StringBuilder();
3899 boolean first = true;
3900 if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
3901 sb.append("Active");
3904 if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
3908 sb.append("Visible");
3910 return sb.toString();
3914 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3915 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3916 != PackageManager.PERMISSION_GRANTED) {
3918 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3919 + Binder.getCallingPid()
3920 + ", uid=" + Binder.getCallingUid());
3924 IInputMethod method;
3926 ClientState focusedWindowClient;
3928 final Printer p = new PrintWriterPrinter(pw);
3930 synchronized (mMethodMap) {
3931 p.println("Current Input Method Manager state:");
3932 int N = mMethodList.size();
3933 p.println(" Input Methods:");
3934 for (int i=0; i<N; i++) {
3935 InputMethodInfo info = mMethodList.get(i);
3936 p.println(" InputMethod #" + i + ":");
3939 p.println(" Clients:");
3940 for (ClientState ci : mClients.values()) {
3941 p.println(" Client " + ci + ":");
3942 p.println(" client=" + ci.client);
3943 p.println(" inputContext=" + ci.inputContext);
3944 p.println(" sessionRequested=" + ci.sessionRequested);
3945 p.println(" curSession=" + ci.curSession);
3947 p.println(" mCurMethodId=" + mCurMethodId);
3948 client = mCurClient;
3949 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
3950 p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
3951 focusedWindowClient = mCurFocusedWindowClient;
3952 p.println(" mCurFocusedWindowClient=" + focusedWindowClient);
3953 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3954 + " mBoundToMethod=" + mBoundToMethod);
3955 p.println(" mCurToken=" + mCurToken);
3956 p.println(" mCurIntent=" + mCurIntent);
3957 method = mCurMethod;
3958 p.println(" mCurMethod=" + mCurMethod);
3959 p.println(" mEnabledSession=" + mEnabledSession);
3960 p.println(" mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
3961 p.println(" mShowRequested=" + mShowRequested
3962 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3963 + " mShowForced=" + mShowForced
3964 + " mInputShown=" + mInputShown);
3965 p.println(" mCurUserActionNotificationSequenceNumber="
3966 + mCurUserActionNotificationSequenceNumber);
3967 p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
3968 p.println(" mSettingsObserver=" + mSettingsObserver);
3969 p.println(" mSwitchingController:");
3970 mSwitchingController.dump(p);
3971 p.println(" mSettings:");
3972 mSettings.dumpLocked(p, " ");
3976 if (client != null) {
3979 client.client.asBinder().dump(fd, args);
3980 } catch (RemoteException e) {
3981 p.println("Input method client dead: " + e);
3984 p.println("No input method client.");
3987 if (focusedWindowClient != null && client != focusedWindowClient) {
3989 p.println("Warning: Current input method client doesn't match the last focused. "
3991 p.println("Dumping input method client in the last focused window just in case.");
3995 focusedWindowClient.client.asBinder().dump(fd, args);
3996 } catch (RemoteException e) {
3997 p.println("Input method client in focused window dead: " + e);
4002 if (method != null) {
4005 method.asBinder().dump(fd, args);
4006 } catch (RemoteException e) {
4007 p.println("Input method service dead: " + e);
4010 p.println("No input method service.");