OSDN Git Service

DO NOT MERGE. Grant MMS Uri permissions as the calling UID.
[android-x86/frameworks-base.git] / services / core / java / com / android / server / InputMethodManagerService.java
1 /*
2  *
3  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
4  * use this file except in compliance with the License. You may obtain a copy of
5  * the License at
6  *
7  * http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11  * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12  * License for the specific language governing permissions and limitations under
13  * the License.
14  */
15
16 package com.android.server;
17
18 import static java.lang.annotation.RetentionPolicy.SOURCE;
19
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;
37
38 import org.xmlpull.v1.XmlPullParser;
39 import org.xmlpull.v1.XmlPullParserException;
40 import org.xmlpull.v1.XmlSerializer;
41
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;
131
132 import java.io.File;
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;
144
145 /**
146  * This class provides a system service that manages input methods.
147  */
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";
153
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;
157
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;
165
166     static final int MSG_START_INPUT = 2000;
167     static final int MSG_RESTART_INPUT = 2010;
168
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;
175
176     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
177
178     static final long TIME_TO_RECONNECT = 3 * 1000;
179
180     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
181
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";
184
185     @Retention(SOURCE)
186     @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
187     private @interface  HardKeyboardBehavior {
188         int WIRELESS_AFFORDANCE = 0;
189         int WIRED_AFFORDANCE = 1;
190     }
191
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;
205
206     final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
207
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;
215
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) {
219         }
220
221         @Override public void onServiceDisconnected(ComponentName name) {
222         }
223     };
224     boolean mVisibleBound = false;
225
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;
235
236     static class SessionState {
237         final ClientState client;
238         final IInputMethod method;
239
240         IInputMethodSession session;
241         InputChannel channel;
242
243         @Override
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
251                     + "}";
252         }
253
254         SessionState(ClientState _client, IInputMethod _method,
255                 IInputMethodSession _session, InputChannel _channel) {
256             client = _client;
257             method = _method;
258             session = _session;
259             channel = _channel;
260         }
261     }
262
263     static final class ClientState {
264         final IInputMethodClient client;
265         final IInputContext inputContext;
266         final int uid;
267         final int pid;
268         final InputBinding binding;
269
270         boolean sessionRequested;
271         SessionState curSession;
272
273         @Override
274         public String toString() {
275             return "ClientState{" + Integer.toHexString(
276                     System.identityHashCode(this)) + " uid " + uid
277                     + " pid " + pid + "}";
278         }
279
280         ClientState(IInputMethodClient _client, IInputContext _inputContext,
281                 int _uid, int _pid) {
282             client = _client;
283             inputContext = _inputContext;
284             uid = _uid;
285             pid = _pid;
286             binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
287         }
288     }
289
290     final HashMap<IBinder, ClientState> mClients = new HashMap<>();
291
292     /**
293      * Set once the system is ready to run third party code.
294      */
295     boolean mSystemReady;
296
297     /**
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}.
301      *
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>
304      *
305      * <p>Note that {@link #mCurId} is used to track which IME is being connected to
306      * {@link InputMethodManagerService}.</p>
307      *
308      * @see #mCurId
309      */
310     @Nullable
311     String mCurMethodId;
312
313     /**
314      * The current binding sequence number, incremented every time there is
315      * a new bind performed.
316      */
317     int mCurSeq;
318
319     /**
320      * The client that is currently bound to an input method.
321      */
322     ClientState mCurClient;
323
324     /**
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.
328      */
329     IBinder mCurFocusedWindow;
330
331     /**
332      * The client by which {@link #mCurFocusedWindow} was reported.  Used only for debugging.
333      */
334     ClientState mCurFocusedWindowClient;
335
336     /**
337      * The input context last provided by the current client.
338      */
339     IInputContext mCurInputContext;
340
341     /**
342      * The missing method flags for the input context last provided by the current client.
343      *
344      * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
345      */
346     int mCurInputContextMissingMethods;
347
348     /**
349      * The attributes last provided by the current client.
350      */
351     EditorInfo mCurAttribute;
352
353     /**
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.
356      *
357      * <p>This can be {@code null} when no input method is connected.</p>
358      *
359      * @see #mCurMethodId
360      */
361     @Nullable
362     String mCurId;
363
364     /**
365      * The current subtype of the current input method.
366      */
367     private InputMethodSubtype mCurrentSubtype;
368
369     // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
370     private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
371             mShortcutInputMethodsAndSubtypes = new HashMap<>();
372
373     // Was the keyguard locked when this client became current?
374     private boolean mCurClientInKeyguard;
375
376     /**
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).
379      */
380     boolean mHaveConnection;
381
382     /**
383      * Set if the client has asked for the input method to be shown.
384      */
385     boolean mShowRequested;
386
387     /**
388      * Set if we were explicitly told to show the input method.
389      */
390     boolean mShowExplicitlyRequested;
391
392     /**
393      * Set if we were forced to be shown.
394      */
395     boolean mShowForced;
396
397     /**
398      * Set if we last told the input method to show itself.
399      */
400     boolean mInputShown;
401
402     /**
403      * The Intent used to connect to the current input method.
404      */
405     Intent mCurIntent;
406
407     /**
408      * The token we have made for the currently active input method, to
409      * identify it in the future.
410      */
411     IBinder mCurToken;
412
413     /**
414      * If non-null, this is the input method service we are currently connected
415      * to.
416      */
417     IInputMethod mCurMethod;
418
419     /**
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.
422      */
423     long mLastBindTime;
424
425     /**
426      * Have we called mCurMethod.bindInput()?
427      */
428     boolean mBoundToMethod;
429
430     /**
431      * Currently enabled session.  Only touched by service thread, not
432      * protected by a lock.
433      */
434     SessionState mEnabledSession;
435
436     /**
437      * True if the device is currently interactive with user.  The value is true initially.
438      */
439     boolean mIsInteractive = true;
440
441     int mCurUserActionNotificationSequenceNumber = 0;
442
443     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
444
445     /**
446      * A set of status bits regarding the active IME.
447      *
448      * <p>This value is a combination of following two bits:</p>
449      * <dl>
450      * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
451      * <dd>
452      *   If this bit is ON, connected IME is ready to accept touch/key events.
453      * </dd>
454      * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
455      * <dd>
456      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
457      * </dd>
458      * </dl>
459      * <em>Do not update this value outside of setImeWindowStatus.</em>
460      */
461     int mImeWindowVis;
462
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;
477
478     class SettingsObserver extends ContentObserver {
479         int mUserId;
480         boolean mRegistered = false;
481         @NonNull
482         String mLastEnabled = "";
483
484         /**
485          * <em>This constructor must be called within the lock.</em>
486          */
487         SettingsObserver(Handler handler) {
488             super(handler);
489         }
490
491         public void registerContentObserverLocked(@UserIdInt int userId) {
492             if (mRegistered && mUserId == userId) {
493                 return;
494             }
495             ContentResolver resolver = mContext.getContentResolver();
496             if (mRegistered) {
497                 mContext.getContentResolver().unregisterContentObserver(this);
498                 mRegistered = false;
499             }
500             if (mUserId != userId) {
501                 mLastEnabled = "";
502                 mUserId = userId;
503             }
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);
514             mRegistered = true;
515         }
516
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,
529                             0, mUserId) == 1;
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);
536                     }
537                 } else {
538                     boolean enabledChanged = false;
539                     String newEnabled = mSettings.getEnabledInputMethodsStr();
540                     if (!mLastEnabled.equals(newEnabled)) {
541                         mLastEnabled = newEnabled;
542                         enabledChanged = true;
543                     }
544                     updateInputMethodsFromSettingsLocked(enabledChanged);
545                 }
546             }
547         }
548
549         @Override
550         public String toString() {
551             return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
552                     + " mLastEnabled=" + mLastEnabled + "}";
553         }
554     }
555
556     class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
557         @Override
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
563                 return;
564             } else if (Intent.ACTION_USER_ADDED.equals(action)
565                     || Intent.ACTION_USER_REMOVED.equals(action)) {
566                 updateCurrentProfileIds();
567                 return;
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);
576                 }
577             } else {
578                 Slog.w(TAG, "Unexpected intent " + intent);
579             }
580         }
581     }
582
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.
586     //
587     // TODO: Move this method to InputMethodUtils with adding unit tests.
588     static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
589         if (DEBUG_RESTORE) {
590             Slog.i(TAG, "Restoring enabled input methods:");
591             Slog.i(TAG, "prev=" + prevValue);
592             Slog.i(TAG, " new=" + newValue);
593         }
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);
599
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);
607             }
608             prevSubtypes.addAll(entry.getValue());
609         }
610
611         final String mergedImesAndSubtypesString =
612                 InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap);
613         if (DEBUG_RESTORE) {
614             Slog.i(TAG, "Merged IME string:");
615             Slog.i(TAG, "     " + mergedImesAndSubtypesString);
616         }
617         Settings.Secure.putString(context.getContentResolver(),
618                 Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
619     }
620
621     class MyPackageMonitor extends PackageMonitor {
622         private boolean isChangingPackagesOfCurrentUser() {
623             final int userId = getChangingUserId();
624             final boolean retval = userId == mSettings.getCurrentUserId();
625             if (DEBUG) {
626                 if (!retval) {
627                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
628                 }
629             }
630             return retval;
631         }
632
633         @Override
634         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
635             if (!isChangingPackagesOfCurrentUser()) {
636                 return false;
637             }
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)) {
647                                     if (!doit) {
648                                         return true;
649                                     }
650                                     resetSelectedInputMethodAndSubtypeLocked("");
651                                     chooseNewDefaultIMELocked();
652                                     return true;
653                                 }
654                             }
655                         }
656                     }
657                 }
658             }
659             return false;
660         }
661
662         @Override
663         public void onSomePackagesChanged() {
664             if (!isChangingPackagesOfCurrentUser()) {
665                 return;
666             }
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)) {
676                             curIm = imi;
677                         }
678
679                         int change = isPackageDisappearing(imi.getPackageName());
680                         if (isPackageModified(imi.getPackageName())) {
681                             mFileManager.deleteAllInputMethodSubtypes(imiId);
682                         }
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);
688                         }
689                     }
690                 }
691
692                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
693
694                 boolean changed = false;
695
696                 if (curIm != null) {
697                     int change = isPackageDisappearing(curIm.getPackageName());
698                     if (change == PACKAGE_TEMPORARY_CHANGE
699                             || change == PACKAGE_PERMANENT_CHANGE) {
700                         ServiceInfo si = null;
701                         try {
702                             si = mIPackageManager.getServiceInfo(
703                                     curIm.getComponent(), 0, mSettings.getCurrentUserId());
704                         } catch (RemoteException ex) {
705                         }
706                         if (si == null) {
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()) {
712                                 changed = true;
713                                 curIm = null;
714                                 Slog.i(TAG, "Unsetting current input method");
715                                 resetSelectedInputMethodAndSubtypeLocked("");
716                             }
717                         }
718                     }
719                 }
720
721                 if (curIm == null) {
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.
728                     changed = true;
729                 }
730
731                 if (changed) {
732                     updateFromSettingsLocked(false);
733                 }
734             }
735         }
736     }
737
738     private static final class MethodCallback extends IInputSessionCallback.Stub {
739         private final InputMethodManagerService mParentIMMS;
740         private final IInputMethod mMethod;
741         private final InputChannel mChannel;
742
743         MethodCallback(InputMethodManagerService imms, IInputMethod method,
744                 InputChannel channel) {
745             mParentIMMS = imms;
746             mMethod = method;
747             mChannel = channel;
748         }
749
750         @Override
751         public void sessionCreated(IInputMethodSession session) {
752             long ident = Binder.clearCallingIdentity();
753             try {
754                 mParentIMMS.onSessionCreated(mMethod, session, mChannel);
755             } finally {
756                 Binder.restoreCallingIdentity(ident);
757             }
758         }
759     }
760
761     private class HardKeyboardListener
762             implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
763         @Override
764         public void onHardKeyboardStatusChange(boolean available) {
765             mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
766                         available ? 1 : 0));
767         }
768
769         public void handleHardKeyboardStatusChange(boolean available) {
770             if (DEBUG) {
771                 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
772             }
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);
779                 }
780             }
781         }
782     }
783
784     public static final class Lifecycle extends SystemService {
785         private InputMethodManagerService mService;
786
787         public Lifecycle(Context context) {
788             super(context);
789             mService = new InputMethodManagerService(context);
790         }
791
792         @Override
793         public void onStart() {
794             LocalServices.addService(InputMethodManagerInternal.class,
795                     new LocalServiceImpl(mService.mHandler));
796             publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
797         }
798
799         @Override
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);
804         }
805
806         @Override
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);
814             }
815         }
816
817         @Override
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);
822         }
823     }
824
825     void onUnlockUser(@UserIdInt int userId) {
826         synchronized(mMethodMap) {
827             final int currentUserId = mSettings.getCurrentUserId();
828             if (DEBUG) {
829                 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
830             }
831             if (userId != currentUserId) {
832                 return;
833             }
834             mSettings.switchCurrentUser(currentUserId, !mSystemReady);
835             // We need to rebuild IMEs.
836             buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
837             updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
838         }
839     }
840
841     void onSwitchUser(@UserIdInt int userId) {
842         synchronized (mMethodMap) {
843             switchUserLocked(userId);
844         }
845     }
846
847     public InputMethodManagerService(Context context) {
848         mIPackageManager = AppGlobals.getPackageManager();
849         mContext = context;
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() {
858             @Override
859             public void executeMessage(Message msg) {
860                 handleMessage(msg);
861             }
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);
871
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)
876             .setWhen(0)
877             .setOngoing(true)
878             .addExtras(extras)
879             .setCategory(Notification.CATEGORY_SYSTEM)
880             .setColor(com.android.internal.R.color.system_notification_accent_color);
881
882         Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
883         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
884
885         mShowOngoingImeSwitcherForPhones = false;
886
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);
893
894         mNotificationShown = false;
895         int userId = 0;
896         try {
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);
900         }
901         mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
902
903         // mSettings should be created before buildInputMethodListLocked
904         mSettings = new InputMethodSettings(
905                 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
906
907         updateCurrentProfileIds();
908         mFileManager = new InputMethodFileManager(mMethodMap, userId);
909         synchronized (mMethodMap) {
910             mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
911                     mSettings, context);
912         }
913
914         // Just checking if defaultImiId is empty or not
915         final String defaultImiId = mSettings.getSelectedInputMethod();
916         if (DEBUG) {
917             Slog.d(TAG, "Initial default ime = " + defaultImiId);
918         }
919         mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
920
921         synchronized (mMethodMap) {
922             buildInputMethodListLocked(!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
923         }
924         mSettings.enableAllIMEsIfThereIsNoEnabledIME();
925
926         if (!mImeSelectedOnBoot) {
927             Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
928             synchronized (mMethodMap) {
929                 resetDefaultImeLocked(context);
930             }
931         }
932
933         synchronized (mMethodMap) {
934             mSettingsObserver.registerContentObserverLocked(userId);
935             updateFromSettingsLocked(true);
936         }
937
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() {
944                     @Override
945                     public void onReceive(Context context, Intent intent) {
946                         synchronized(mMethodMap) {
947                             resetStateIfCurrentLocaleChangedLocked();
948                         }
949                     }
950                 }, filter);
951     }
952
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))) {
956             return;
957         }
958         final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
959                 context, mSystemReady, mSettings.getEnabledInputMethodListLocked());
960         if (suitableImes.isEmpty()) {
961             Slog.i(TAG, "No default found");
962             return;
963         }
964         final InputMethodInfo defIm = suitableImes.get(0);
965         Slog.i(TAG, "Default found, using " + defIm.getId());
966         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
967     }
968
969     private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
970             final boolean resetDefaultEnabledIme) {
971         if (!mSystemReady) {
972             // not system ready
973             return;
974         }
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);
981             }
982             if (DEBUG) {
983                 Slog.i(TAG, "LocaleList has been changed to " + newLocales);
984             }
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);
992                 }
993             } else {
994                 // If the locale is changed, needs to reset the default ime
995                 resetDefaultImeLocked(mContext);
996             }
997             updateFromSettingsLocked(true);
998             mLastSystemLocales = newLocales;
999             if (!updateOnlyWhenLocaleChanged) {
1000                 try {
1001                     startInputInnerLocked();
1002                 } catch (RuntimeException e) {
1003                     Slog.w(TAG, "Unexpected exception", e);
1004                 }
1005             }
1006         }
1007     }
1008
1009     private void resetStateIfCurrentLocaleChangedLocked() {
1010         resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */,
1011                 true /* resetDefaultImeLocked */);
1012     }
1013
1014     private void switchUserLocked(int newUserId) {
1015         if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
1016                 + " currentUserId=" + mSettings.getCurrentUserId());
1017
1018         // ContentObserver should be registered again when the user is changed
1019         mSettingsObserver.registerContentObserverLocked(newUserId);
1020
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();
1030
1031         if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
1032                 + " defaultImiId=" + defaultImiId);
1033
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());
1046         }
1047
1048         if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
1049                 + " selectedIme=" + mSettings.getSelectedInputMethod());
1050     }
1051
1052     void updateCurrentProfileIds() {
1053         mSettings.setCurrentProfileIds(
1054                 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
1055     }
1056
1057     @Override
1058     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
1059             throws RemoteException {
1060         try {
1061             return super.onTransact(code, data, reply, flags);
1062         } catch (RuntimeException e) {
1063             // The input method manager only throws security exceptions, so let's
1064             // log all others.
1065             if (!(e instanceof SecurityException)) {
1066                 Slog.wtf(TAG, "Input Method Manager Crash", e);
1067             }
1068             throw e;
1069         }
1070     }
1071
1072     public void systemRunning(StatusBarManagerService statusBar) {
1073         synchronized (mMethodMap) {
1074             if (DEBUG) {
1075                 Slog.d(TAG, "--- systemReady");
1076             }
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);
1087                 }
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);
1094                 }
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());
1102                 }
1103                 mLastSystemLocales = mRes.getConfiguration().getLocales();
1104                 try {
1105                     startInputInnerLocked();
1106                 } catch (RuntimeException e) {
1107                     Slog.w(TAG, "Unexpected exception", e);
1108                 }
1109             }
1110         }
1111     }
1112
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);
1120         if (DEBUG) {
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());
1126         }
1127         if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
1128             return true;
1129         }
1130
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
1135         // by a token.
1136         if (mContext.checkCallingOrSelfPermission(
1137                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
1138                         == PackageManager.PERMISSION_GRANTED) {
1139             if (DEBUG) {
1140                 Slog.d(TAG, "--- Access granted because the calling process has "
1141                         + "the INTERACT_ACROSS_USERS_FULL permission");
1142             }
1143             return true;
1144         }
1145         Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
1146                 + Debug.getCallers(10));
1147         return false;
1148     }
1149
1150
1151     /**
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.
1155      */
1156     private boolean calledWithValidToken(IBinder token) {
1157         if (token == null || mCurToken != token) {
1158             return false;
1159         }
1160         return true;
1161     }
1162
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);
1167             return false;
1168         }
1169         return mContext.bindServiceAsUser(service, conn, flags,
1170                 new UserHandle(mSettings.getCurrentUserId()));
1171     }
1172
1173     @Override
1174     public List<InputMethodInfo> getInputMethodList() {
1175         // TODO: Make this work even for non-current users?
1176         if (!calledFromValidUser()) {
1177             return Collections.emptyList();
1178         }
1179         synchronized (mMethodMap) {
1180             return new ArrayList<>(mMethodList);
1181         }
1182     }
1183
1184     @Override
1185     public List<InputMethodInfo> getEnabledInputMethodList() {
1186         // TODO: Make this work even for non-current users?
1187         if (!calledFromValidUser()) {
1188             return Collections.emptyList();
1189         }
1190         synchronized (mMethodMap) {
1191             return mSettings.getEnabledInputMethodListLocked();
1192         }
1193     }
1194
1195     /**
1196      * @param imiId if null, returns enabled subtypes for the current imi
1197      * @return enabled subtypes of the specified imi
1198      */
1199     @Override
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();
1205         }
1206         synchronized (mMethodMap) {
1207             final InputMethodInfo imi;
1208             if (imiId == null && mCurMethodId != null) {
1209                 imi = mMethodMap.get(mCurMethodId);
1210             } else {
1211                 imi = mMethodMap.get(imiId);
1212             }
1213             if (imi == null) {
1214                 return Collections.emptyList();
1215             }
1216             return mSettings.getEnabledInputMethodSubtypeListLocked(
1217                     mContext, imi, allowsImplicitlySelectedSubtypes);
1218         }
1219     }
1220
1221     @Override
1222     public void addClient(IInputMethodClient client,
1223             IInputContext inputContext, int uid, int pid) {
1224         if (!calledFromValidUser()) {
1225             return;
1226         }
1227         synchronized (mMethodMap) {
1228             mClients.put(client.asBinder(), new ClientState(client,
1229                     inputContext, uid, pid));
1230         }
1231     }
1232
1233     @Override
1234     public void removeClient(IInputMethodClient client) {
1235         if (!calledFromValidUser()) {
1236             return;
1237         }
1238         synchronized (mMethodMap) {
1239             ClientState cs = mClients.remove(client.asBinder());
1240             if (cs != null) {
1241                 clearClientSessionLocked(cs);
1242                 if (mCurClient == cs) {
1243                     mCurClient = null;
1244                 }
1245                 if (mCurFocusedWindowClient == cs) {
1246                     mCurFocusedWindowClient = null;
1247                 }
1248             }
1249         }
1250     }
1251
1252     void executeOrSendMessage(IInterface target, Message msg) {
1253          if (target.asBinder() instanceof Binder) {
1254              mCaller.sendMessage(msg);
1255          } else {
1256              handleMessage(msg);
1257              msg.recycle();
1258          }
1259     }
1260
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));
1271                 }
1272             }
1273
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;
1279             mCurClient = null;
1280
1281             hideInputMethodMenuLocked();
1282         }
1283     }
1284
1285     private int getImeShowFlags() {
1286         int flags = 0;
1287         if (mShowForced) {
1288             flags |= InputMethod.SHOW_FORCED
1289                     | InputMethod.SHOW_EXPLICIT;
1290         } else if (mShowExplicitlyRequested) {
1291             flags |= InputMethod.SHOW_EXPLICIT;
1292         }
1293         return flags;
1294     }
1295
1296     private int getAppShowFlags() {
1297         int flags = 0;
1298         if (mShowForced) {
1299             flags |= InputMethodManager.SHOW_FORCED;
1300         } else if (!mShowExplicitlyRequested) {
1301             flags |= InputMethodManager.SHOW_IMPLICIT;
1302         }
1303         return flags;
1304     }
1305
1306     InputBindResult attachNewInputLocked(boolean initial) {
1307         if (!mBoundToMethod) {
1308             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1309                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1310             mBoundToMethod = true;
1311         }
1312         final SessionState session = mCurClient.curSession;
1313         if (initial) {
1314             executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
1315                     MSG_START_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
1316                     mCurAttribute));
1317         } else {
1318             executeOrSendMessage(session.method, mCaller.obtainMessageIOOO(
1319                     MSG_RESTART_INPUT, mCurInputContextMissingMethods, session, mCurInputContext,
1320                     mCurAttribute));
1321         }
1322         if (mShowRequested) {
1323             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1324             showCurrentInputLocked(getAppShowFlags(), null);
1325         }
1326         return new InputBindResult(session.session,
1327                 (session.channel != null ? session.channel.dup() : null),
1328                 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
1329     }
1330
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) {
1338             return mNoBinding;
1339         }
1340
1341         ClientState cs = mClients.get(client.asBinder());
1342         if (cs == null) {
1343             throw new IllegalArgumentException("unknown client "
1344                     + client.asBinder());
1345         }
1346
1347         if (attribute == null) {
1348             Slog.w(TAG, "Ignoring startInput with null EditorInfo."
1349                     + " uid=" + cs.uid + " pid=" + cs.pid);
1350             return null;
1351         }
1352
1353         try {
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 + ")");
1362                 return null;
1363             }
1364         } catch (RemoteException e) {
1365         }
1366
1367         return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
1368                 controlFlags);
1369     }
1370
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) {
1376             return mNoBinding;
1377         }
1378
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);
1383             return mNoBinding;
1384         }
1385
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
1390             // one.
1391             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
1392             if (DEBUG) Slog.v(TAG, "switching to client: client="
1393                     + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
1394
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));
1399             }
1400         }
1401
1402         // Bump up the sequence for this client and attach it.
1403         mCurSeq++;
1404         if (mCurSeq <= 0) mCurSeq = 1;
1405         mCurClient = cs;
1406         mCurInputContext = inputContext;
1407         mCurInputContextMissingMethods = missingMethods;
1408         mCurAttribute = attribute;
1409
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);
1417             }
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);
1436                 } else {
1437                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1438                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1439                 }
1440             }
1441         }
1442
1443         return startInputInnerLocked();
1444     }
1445
1446     InputBindResult startInputInnerLocked() {
1447         if (mCurMethodId == null) {
1448             return mNoBinding;
1449         }
1450
1451         if (!mSystemReady) {
1452             // If the system is not yet ready, we shouldn't be running third
1453             // party code.
1454             return new InputBindResult(null, null, mCurMethodId, mCurSeq,
1455                     mCurUserActionNotificationSequenceNumber);
1456         }
1457
1458         InputMethodInfo info = mMethodMap.get(mCurMethodId);
1459         if (info == null) {
1460             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1461         }
1462
1463         unbindCurrentMethodLocked(true);
1464
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();
1478             try {
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) {
1483             }
1484             return new InputBindResult(null, null, mCurId, mCurSeq,
1485                     mCurUserActionNotificationSequenceNumber);
1486         } else {
1487             mCurIntent = null;
1488             Slog.w(TAG, "Failure connecting to input method service: "
1489                     + mCurIntent);
1490         }
1491         return null;
1492     }
1493
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()) {
1500             return null;
1501         }
1502         synchronized (mMethodMap) {
1503             if (DEBUG) {
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));
1512             }
1513             final long ident = Binder.clearCallingIdentity();
1514             try {
1515                 return startInputLocked(startInputReason, client, inputContext, missingMethods,
1516                         attribute, controlFlags);
1517             } finally {
1518                 Binder.restoreCallingIdentity(ident);
1519             }
1520         }
1521     }
1522
1523     @Override
1524     public void finishInput(IInputMethodClient client) {
1525     }
1526
1527     @Override
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);
1535                     return;
1536                 }
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);
1543                 }
1544             }
1545         }
1546     }
1547
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));
1561                     }
1562                     return;
1563                 }
1564             }
1565         }
1566
1567         // Session abandoned.  Close its associated input channel.
1568         channel.dispose();
1569     }
1570
1571     void unbindCurrentMethodLocked(boolean savePosition) {
1572         if (mVisibleBound) {
1573             mContext.unbindService(mVisibleConnection);
1574             mVisibleBound = false;
1575         }
1576
1577         if (mHaveConnection) {
1578             mContext.unbindService(this);
1579             mHaveConnection = false;
1580         }
1581
1582         if (mCurToken != null) {
1583             try {
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();
1588                 }
1589                 mIWindowManager.removeWindowToken(mCurToken);
1590             } catch (RemoteException e) {
1591             }
1592             mCurToken = null;
1593         }
1594
1595         mCurId = null;
1596         clearCurMethodLocked();
1597     }
1598
1599     void resetCurrentMethodAndClient(
1600             /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1601         mCurMethodId = null;
1602         unbindCurrentMethodLocked(false);
1603         unbindCurrentClientLocked(unbindClientReason);
1604     }
1605
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])));
1614         }
1615     }
1616
1617     void clearClientSessionLocked(ClientState cs) {
1618         finishSessionLocked(cs.curSession);
1619         cs.curSession = null;
1620         cs.sessionRequested = false;
1621     }
1622
1623     private void finishSessionLocked(SessionState sessionState) {
1624         if (sessionState != null) {
1625             if (sessionState.session != null) {
1626                 try {
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);
1631                 }
1632                 sessionState.session = null;
1633             }
1634             if (sessionState.channel != null) {
1635                 sessionState.channel.dispose();
1636                 sessionState.channel = null;
1637             }
1638         }
1639     }
1640
1641     void clearCurMethodLocked() {
1642         if (mCurMethod != null) {
1643             for (ClientState cs : mClients.values()) {
1644                 clearClientSessionLocked(cs);
1645             }
1646
1647             finishSessionLocked(mEnabledSession);
1648             mEnabledSession = null;
1649             mCurMethod = null;
1650         }
1651         if (mStatusBar != null) {
1652             mStatusBar.setIconVisibility(mSlotIme, false);
1653         }
1654     }
1655
1656     @Override
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));
1673                 }
1674             }
1675         }
1676     }
1677
1678     @Override
1679     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1680         long ident = Binder.clearCallingIdentity();
1681         try {
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);
1687                     return;
1688                 }
1689                 if (iconId == 0) {
1690                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1691                     if (mStatusBar != null) {
1692                         mStatusBar.setIconVisibility(mSlotIme, false);
1693                     }
1694                 } else if (packageName != null) {
1695                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1696                     CharSequence contentDescription = null;
1697                     try {
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) {
1704                         /* ignore */
1705                     }
1706                     if (mStatusBar != null) {
1707                         mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
1708                                 contentDescription  != null
1709                                         ? contentDescription.toString() : null);
1710                         mStatusBar.setIconVisibility(mSlotIme, true);
1711                     }
1712                 }
1713             }
1714         } finally {
1715             Binder.restoreCallingIdentity(ident);
1716         }
1717     }
1718
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.
1730                 return true;
1731             }
1732         } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
1733             return false;
1734         }
1735
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;
1741         int auxCount = 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) {
1750                 ++nonAuxCount;
1751             } else {
1752                 for (int j = 0; j < subtypeCount; ++j) {
1753                     final InputMethodSubtype subtype = subtypes.get(j);
1754                     if (!subtype.isAuxiliary()) {
1755                         ++nonAuxCount;
1756                         nonAuxSubtype = subtype;
1757                     } else {
1758                         ++auxCount;
1759                         auxSubtype = subtype;
1760                     }
1761                 }
1762             }
1763         }
1764         if (nonAuxCount > 1 || auxCount > 1) {
1765             return true;
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)) {
1772                 return false;
1773             }
1774             return true;
1775         }
1776         return false;
1777     }
1778
1779     private boolean isKeyguardLocked() {
1780         return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1781     }
1782
1783     @SuppressWarnings("deprecation")
1784     @Override
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);
1790             return;
1791         }
1792
1793         synchronized (mMethodMap) {
1794             mImeWindowVis = vis;
1795             mBackDisposition = backDisposition;
1796             updateSystemUiLocked(token, vis, backDisposition);
1797         }
1798     }
1799
1800     private void updateSystemUi(IBinder token, int vis, int backDisposition) {
1801         synchronized (mMethodMap) {
1802             updateSystemUiLocked(token, vis, backDisposition);
1803         }
1804     }
1805
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);
1812             return;
1813         }
1814
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();
1818         try {
1819             // apply policy for binder calls
1820             if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
1821                 vis = 0;
1822             }
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);
1828             }
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);
1839                 try {
1840                     if ((mNotificationManager != null)
1841                             && !mIWindowManager.hasNavigationBar()) {
1842                         if (DEBUG) {
1843                             Slog.d(TAG, "--- show notification: label =  " + summary);
1844                         }
1845                         mNotificationManager.notifyAsUser(null,
1846                                 com.android.internal.R.string.select_input_method,
1847                                 mImeSwitcherNotification.build(), UserHandle.ALL);
1848                         mNotificationShown = true;
1849                     }
1850                 } catch (RemoteException e) {
1851                 }
1852             } else {
1853                 if (mNotificationShown && mNotificationManager != null) {
1854                     if (DEBUG) {
1855                         Slog.d(TAG, "--- hide notification");
1856                     }
1857                     mNotificationManager.cancelAsUser(null,
1858                             com.android.internal.R.string.select_input_method, UserHandle.ALL);
1859                     mNotificationShown = false;
1860                 }
1861             }
1862         } finally {
1863             Binder.restoreCallingIdentity(ident);
1864         }
1865     }
1866
1867     @Override
1868     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1869         if (!calledFromValidUser()) {
1870             return;
1871         }
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);
1878                 }
1879             }
1880         }
1881     }
1882
1883     @Override
1884     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1885         if (!calledFromValidUser()) {
1886             return false;
1887         }
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
1897                 // notification.
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();
1904                 try {
1905                     mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1906                 } finally {
1907                     Binder.restoreCallingIdentity(ident);
1908                 }
1909                 return true;
1910             }
1911         }
1912         return false;
1913     }
1914
1915     void updateFromSettingsLocked(boolean enabledMayChange) {
1916         updateInputMethodsFromSettingsLocked(enabledMayChange);
1917         updateKeyboardFromSettingsLocked();
1918     }
1919
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);
1927                 try {
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) {
1933                         if (DEBUG) {
1934                             Slog.d(TAG, "Update state(" + imm.getId()
1935                                     + "): DISABLED_UNTIL_USED -> DEFAULT");
1936                         }
1937                         mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
1938                                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
1939                                 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
1940                                 mContext.getBasePackageName());
1941                     }
1942                 } catch (RemoteException e) {
1943                 }
1944             }
1945         }
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
1949         // enabled.
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();
1954         }
1955         if (!TextUtils.isEmpty(id)) {
1956             try {
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);
1961             }
1962             mShortcutInputMethodsAndSubtypes.clear();
1963         } else {
1964             // There is no longer an input method set, so stop any current one.
1965             resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
1966         }
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);
1972
1973     }
1974
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);
1983         }
1984     }
1985
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);
1993         }
1994     }
1995
1996     /* package */ void setInputMethodLocked(String id, int subtypeId) {
1997         InputMethodInfo info = mMethodMap.get(id);
1998         if (info == null) {
1999             throw new IllegalArgumentException("Unknown id: " + id);
2000         }
2001
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) {
2006                 return;
2007             }
2008             final InputMethodSubtype oldSubtype = mCurrentSubtype;
2009             final InputMethodSubtype newSubtype;
2010             if (subtypeId >= 0 && subtypeId < subtypeCount) {
2011                 newSubtype = info.getSubtypeAt(subtypeId);
2012             } else {
2013                 // If subtype is null, try to find the most applicable one from
2014                 // getCurrentInputMethodSubtype.
2015                 newSubtype = getCurrentInputMethodSubtypeLocked();
2016             }
2017             if (newSubtype == null || oldSubtype == null) {
2018                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
2019                         + ", new subtype = " + newSubtype);
2020                 return;
2021             }
2022             if (newSubtype != oldSubtype) {
2023                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
2024                 if (mCurMethod != null) {
2025                     try {
2026                         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
2027                         mCurMethod.changeInputMethodSubtype(newSubtype);
2028                     } catch (RemoteException e) {
2029                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
2030                         return;
2031                     }
2032                 }
2033                 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
2034             }
2035             return;
2036         }
2037
2038         // Changing to a different IME.
2039         final long ident = Binder.clearCallingIdentity();
2040         try {
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().
2047             mCurMethodId = id;
2048
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);
2054             }
2055             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
2056         } finally {
2057             Binder.restoreCallingIdentity(ident);
2058         }
2059
2060         notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
2061                 getCurrentInputMethodSubtypeLocked());
2062     }
2063
2064     @Override
2065     public boolean showSoftInput(IInputMethodClient client, int flags,
2066             ResultReceiver resultReceiver) {
2067         if (!calledFromValidUser()) {
2068             return false;
2069         }
2070         int uid = Binder.getCallingUid();
2071         long ident = Binder.clearCallingIdentity();
2072         try {
2073             synchronized (mMethodMap) {
2074                 if (mCurClient == null || client == null
2075                         || mCurClient.client.asBinder() != client.asBinder()) {
2076                     try {
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);
2082                             return false;
2083                         }
2084                     } catch (RemoteException e) {
2085                         return false;
2086                     }
2087                 }
2088
2089                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
2090                 return showCurrentInputLocked(flags, resultReceiver);
2091             }
2092         } finally {
2093             Binder.restoreCallingIdentity(ident);
2094         }
2095     }
2096
2097     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2098         mShowRequested = true;
2099         if (mAccessibilityRequestingNoSoftKeyboard) {
2100             return false;
2101         }
2102
2103         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
2104             mShowExplicitlyRequested = true;
2105             mShowForced = true;
2106         } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
2107             mShowExplicitlyRequested = true;
2108         }
2109
2110         if (!mSystemReady) {
2111             return false;
2112         }
2113
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,
2119                     resultReceiver));
2120             mInputShown = true;
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;
2127             }
2128             res = 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);
2141         } else {
2142             if (DEBUG) {
2143                 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
2144                         + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
2145             }
2146         }
2147
2148         return res;
2149     }
2150
2151     @Override
2152     public boolean hideSoftInput(IInputMethodClient client, int flags,
2153             ResultReceiver resultReceiver) {
2154         if (!calledFromValidUser()) {
2155             return false;
2156         }
2157         int uid = Binder.getCallingUid();
2158         long ident = Binder.clearCallingIdentity();
2159         try {
2160             synchronized (mMethodMap) {
2161                 if (mCurClient == null || client == null
2162                         || mCurClient.client.asBinder() != client.asBinder()) {
2163                     try {
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);
2170                             return false;
2171                         }
2172                     } catch (RemoteException e) {
2173                         return false;
2174                     }
2175                 }
2176
2177                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
2178                 return hideCurrentInputLocked(flags, resultReceiver);
2179             }
2180         } finally {
2181             Binder.restoreCallingIdentity(ident);
2182         }
2183     }
2184
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");
2189             return false;
2190         }
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");
2193             return false;
2194         }
2195
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);
2206         boolean res;
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
2211             // the final state.
2212             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2213                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
2214             res = true;
2215         } else {
2216             res = false;
2217         }
2218         if (mHaveConnection && mVisibleBound) {
2219             mContext.unbindService(mVisibleConnection);
2220             mVisibleBound = false;
2221         }
2222         mInputShown = false;
2223         mShowRequested = false;
2224         mShowExplicitlyRequested = false;
2225         mShowForced = false;
2226         return res;
2227     }
2228
2229     @Override
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);
2238         } else {
2239             return startInput(startInputReason, client, inputContext, missingMethods, attribute,
2240                     controlFlags);
2241         }
2242     }
2243
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();
2253         try {
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));
2265
2266                 ClientState cs = mClients.get(client.asBinder());
2267                 if (cs == null) {
2268                     throw new IllegalArgumentException("unknown client "
2269                             + client.asBinder());
2270                 }
2271
2272                 try {
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 + ")");
2281                         return null;
2282                     }
2283                 } catch (RemoteException e) {
2284                 }
2285
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);
2291                     return null;
2292                 }
2293
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);
2300                     }
2301                     return null;
2302                 }
2303                 mCurFocusedWindow = windowToken;
2304                 mCurFocusedWindowClient = cs;
2305
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;
2320
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;
2326
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);
2336                             }
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);
2350                                 didStart = true;
2351                             }
2352                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2353                         }
2354                         break;
2355                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2356                         // Do nothing.
2357                         break;
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);
2363                         }
2364                         break;
2365                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2366                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2367                         hideCurrentInputLocked(0, null);
2368                         break;
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);
2376                                 didStart = true;
2377                             }
2378                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2379                         }
2380                         break;
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);
2386                             didStart = true;
2387                         }
2388                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2389                         break;
2390                 }
2391
2392                 if (!didStart && attribute != null) {
2393                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
2394                             controlFlags);
2395                 }
2396             }
2397         } finally {
2398             Binder.restoreCallingIdentity(ident);
2399         }
2400
2401         return res;
2402     }
2403
2404     @Override
2405     public void showInputMethodPickerFromClient(
2406             IInputMethodClient client, int auxiliarySubtypeMode) {
2407         if (!calledFromValidUser()) {
2408             return;
2409         }
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);
2415             }
2416
2417             // Always call subtype picker, because subtype picker is a superset of input method
2418             // picker.
2419             mHandler.sendMessage(mCaller.obtainMessageI(
2420                     MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
2421         }
2422     }
2423
2424     @Override
2425     public void setInputMethod(IBinder token, String id) {
2426         if (!calledFromValidUser()) {
2427             return;
2428         }
2429         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2430     }
2431
2432     @Override
2433     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2434         if (!calledFromValidUser()) {
2435             return;
2436         }
2437         synchronized (mMethodMap) {
2438             if (subtype != null) {
2439                 setInputMethodWithSubtypeIdLocked(token, id,
2440                         InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
2441                                 subtype.hashCode()));
2442             } else {
2443                 setInputMethod(token, id);
2444             }
2445         }
2446     }
2447
2448     @Override
2449     public void showInputMethodAndSubtypeEnablerFromClient(
2450             IInputMethodClient client, String inputMethodId) {
2451         if (!calledFromValidUser()) {
2452             return;
2453         }
2454         synchronized (mMethodMap) {
2455             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2456                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2457         }
2458     }
2459
2460     @Override
2461     public boolean switchToLastInputMethod(IBinder token) {
2462         if (!calledFromValidUser()) {
2463             return false;
2464         }
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);
2470             } else {
2471                 lastImi = null;
2472             }
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);
2485                 }
2486             }
2487
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)) {
2511                                     break;
2512                                 }
2513                             }
2514                         }
2515                     }
2516                 }
2517             }
2518
2519             if (!TextUtils.isEmpty(targetLastImiId)) {
2520                 if (DEBUG) {
2521                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2522                             + ", from: " + mCurMethodId + ", " + subtypeId);
2523                 }
2524                 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
2525                 return true;
2526             } else {
2527                 return false;
2528             }
2529         }
2530     }
2531
2532     @Override
2533     public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2534         if (!calledFromValidUser()) {
2535             return false;
2536         }
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);
2542                 return false;
2543             }
2544             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2545                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2546                     true /* forward */);
2547             if (nextSubtype == null) {
2548                 return false;
2549             }
2550             setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
2551                     nextSubtype.mSubtypeId);
2552             return true;
2553         }
2554     }
2555
2556     @Override
2557     public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2558         if (!calledFromValidUser()) {
2559             return false;
2560         }
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);
2566                 return false;
2567             }
2568             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2569                     false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2570                     true /* forward */);
2571             if (nextSubtype == null) {
2572                 return false;
2573             }
2574             return true;
2575         }
2576     }
2577
2578     @Override
2579     public InputMethodSubtype getLastInputMethodSubtype() {
2580         if (!calledFromValidUser()) {
2581             return null;
2582         }
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;
2590             try {
2591                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2592                 final int lastSubtypeId =
2593                         InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2594                 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2595                     return null;
2596                 }
2597                 return lastImi.getSubtypeAt(lastSubtypeId);
2598             } catch (NumberFormatException e) {
2599                 return null;
2600             }
2601         }
2602     }
2603
2604     @Override
2605     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2606         if (!calledFromValidUser()) {
2607             return;
2608         }
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;
2616             try {
2617                 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2618             } catch (RemoteException e) {
2619                 Slog.e(TAG, "Failed to get package infos");
2620                 return;
2621             }
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();
2628                         try {
2629                             buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
2630                         } finally {
2631                             Binder.restoreCallingIdentity(ident);
2632                         }
2633                         return;
2634                     }
2635                 }
2636             }
2637         }
2638         return;
2639     }
2640
2641     @Override
2642     public int getInputMethodWindowVisibleHeight() {
2643         return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
2644     }
2645
2646     @Override
2647     public void clearLastInputMethodWindowForTransition(IBinder token) {
2648         if (!calledFromValidUser()) {
2649             return;
2650         }
2651         final long ident = Binder.clearCallingIdentity();
2652         try {
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);
2658                     return;
2659                 }
2660             }
2661             mWindowManagerInternal.clearLastInputMethodWindowForTransition();
2662         } finally {
2663             Binder.restoreCallingIdentity(ident);
2664         }
2665     }
2666
2667     @Override
2668     public void notifyUserAction(int sequenceNumber) {
2669         if (DEBUG) {
2670             Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
2671         }
2672         synchronized (mMethodMap) {
2673             if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
2674                 if (DEBUG) {
2675                     Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
2676                             + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
2677                             + " actual: " + sequenceNumber);
2678                 }
2679                 return;
2680             }
2681             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2682             if (imi != null) {
2683                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
2684             }
2685         }
2686     }
2687
2688     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2689         synchronized (mMethodMap) {
2690             setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
2691         }
2692     }
2693
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);
2702             }
2703         } else if (mCurToken != token) {
2704             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2705                     + " token: " + token);
2706             return;
2707         }
2708
2709         final long ident = Binder.clearCallingIdentity();
2710         try {
2711             setInputMethodLocked(id, subtypeId);
2712         } finally {
2713             Binder.restoreCallingIdentity(ident);
2714         }
2715     }
2716
2717     @Override
2718     public void hideMySoftInput(IBinder token, int flags) {
2719         if (!calledFromValidUser()) {
2720             return;
2721         }
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);
2727                 return;
2728             }
2729             long ident = Binder.clearCallingIdentity();
2730             try {
2731                 hideCurrentInputLocked(flags, null);
2732             } finally {
2733                 Binder.restoreCallingIdentity(ident);
2734             }
2735         }
2736     }
2737
2738     @Override
2739     public void showMySoftInput(IBinder token, int flags) {
2740         if (!calledFromValidUser()) {
2741             return;
2742         }
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);
2748                 return;
2749             }
2750             long ident = Binder.clearCallingIdentity();
2751             try {
2752                 showCurrentInputLocked(flags, null);
2753             } finally {
2754                 Binder.restoreCallingIdentity(ident);
2755             }
2756         }
2757     }
2758
2759     void setEnabledSessionInMainThread(SessionState session) {
2760         if (mEnabledSession != session) {
2761             if (mEnabledSession != null && mEnabledSession.session != null) {
2762                 try {
2763                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2764                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
2765                 } catch (RemoteException e) {
2766                 }
2767             }
2768             mEnabledSession = session;
2769             if (mEnabledSession != null && mEnabledSession.session != null) {
2770                 try {
2771                     if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2772                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
2773                 } catch (RemoteException e) {
2774                 }
2775             }
2776         }
2777     }
2778
2779     @Override
2780     public boolean handleMessage(Message msg) {
2781         SomeArgs args;
2782         switch (msg.what) {
2783             case MSG_SHOW_IM_SUBTYPE_PICKER:
2784                 final boolean showAuxSubtypes;
2785                 switch (msg.arg1) {
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;
2791                         break;
2792                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
2793                         showAuxSubtypes = true;
2794                         break;
2795                     case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
2796                         showAuxSubtypes = false;
2797                         break;
2798                     default:
2799                         Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
2800                         return false;
2801                 }
2802                 showInputMethodMenu(showAuxSubtypes);
2803                 return true;
2804
2805             case MSG_SHOW_IM_SUBTYPE_ENABLER:
2806                 showInputMethodAndSubtypeEnabler((String)msg.obj);
2807                 return true;
2808
2809             case MSG_SHOW_IM_CONFIG:
2810                 showConfigureInputMethods();
2811                 return true;
2812
2813             // ---------------------------------------------------------
2814
2815             case MSG_UNBIND_INPUT:
2816                 try {
2817                     ((IInputMethod)msg.obj).unbindInput();
2818                 } catch (RemoteException e) {
2819                     // There is nothing interesting about the method dying.
2820                 }
2821                 return true;
2822             case MSG_BIND_INPUT:
2823                 args = (SomeArgs)msg.obj;
2824                 try {
2825                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2826                 } catch (RemoteException e) {
2827                 }
2828                 args.recycle();
2829                 return true;
2830             case MSG_SHOW_SOFT_INPUT:
2831                 args = (SomeArgs)msg.obj;
2832                 try {
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) {
2837                 }
2838                 args.recycle();
2839                 return true;
2840             case MSG_HIDE_SOFT_INPUT:
2841                 args = (SomeArgs)msg.obj;
2842                 try {
2843                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
2844                             + args.arg2 + ")");
2845                     ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
2846                 } catch (RemoteException e) {
2847                 }
2848                 args.recycle();
2849                 return true;
2850             case MSG_HIDE_CURRENT_INPUT_METHOD:
2851                 synchronized (mMethodMap) {
2852                     hideCurrentInputLocked(0, null);
2853                 }
2854                 return true;
2855             case MSG_ATTACH_TOKEN:
2856                 args = (SomeArgs)msg.obj;
2857                 try {
2858                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2859                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2860                 } catch (RemoteException e) {
2861                 }
2862                 args.recycle();
2863                 return true;
2864             case MSG_CREATE_SESSION: {
2865                 args = (SomeArgs)msg.obj;
2866                 IInputMethod method = (IInputMethod)args.arg1;
2867                 InputChannel channel = (InputChannel)args.arg2;
2868                 try {
2869                     method.createSession(channel, (IInputSessionCallback)args.arg3);
2870                 } catch (RemoteException e) {
2871                 } finally {
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)) {
2875                         channel.dispose();
2876                     }
2877                 }
2878                 args.recycle();
2879                 return true;
2880             }
2881             // ---------------------------------------------------------
2882
2883             case MSG_START_INPUT: {
2884                 int missingMethods = msg.arg1;
2885                 args = (SomeArgs) msg.obj;
2886                 try {
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) {
2892                 }
2893                 args.recycle();
2894                 return true;
2895             }
2896             case MSG_RESTART_INPUT: {
2897                 int missingMethods = msg.arg1;
2898                 args = (SomeArgs) msg.obj;
2899                 try {
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) {
2905                 }
2906                 args.recycle();
2907                 return true;
2908             }
2909
2910             // ---------------------------------------------------------
2911
2912             case MSG_UNBIND_CLIENT:
2913                 try {
2914                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
2915                 } catch (RemoteException e) {
2916                     // There is nothing interesting about the last client dying.
2917                 }
2918                 return true;
2919             case MSG_BIND_CLIENT: {
2920                 args = (SomeArgs)msg.obj;
2921                 IInputMethodClient client = (IInputMethodClient)args.arg1;
2922                 InputBindResult res = (InputBindResult)args.arg2;
2923                 try {
2924                     client.onBindMethod(res);
2925                 } catch (RemoteException e) {
2926                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
2927                 } finally {
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();
2932                     }
2933                 }
2934                 args.recycle();
2935                 return true;
2936             }
2937             case MSG_SET_ACTIVE:
2938                 try {
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);
2944                 }
2945                 return true;
2946             case MSG_SET_INTERACTIVE:
2947                 handleSetInteractive(msg.arg1 != 0);
2948                 return true;
2949             case MSG_SWITCH_IME:
2950                 handleSwitchInputMethod(msg.arg1 != 0);
2951                 return true;
2952             case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
2953                 final int sequenceNumber = msg.arg1;
2954                 final ClientState clientState = (ClientState)msg.obj;
2955                 try {
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 "
2962                             + clientState.uid);
2963                 }
2964                 return true;
2965             }
2966
2967             // --------------------------------------------------------------
2968             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
2969                 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
2970                 return true;
2971         }
2972         return false;
2973     }
2974
2975     private void handleSetInteractive(final boolean interactive) {
2976         synchronized (mMethodMap) {
2977             mIsInteractive = interactive;
2978             updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
2979
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));
2984             }
2985         }
2986     }
2987
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) {
2993                 return;
2994             }
2995             setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
2996             final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
2997             if (newInputMethodInfo == null) {
2998                 return;
2999             }
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);
3006                 } else {
3007                     mSubtypeSwitchedByShortCutToast.setText(toastText);
3008                 }
3009                 mSubtypeSwitchedByShortCutToast.show();
3010             }
3011         }
3012     }
3013
3014     private boolean chooseNewDefaultIMELocked() {
3015         final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
3016                 mSettings.getEnabledInputMethodListLocked());
3017         if (imi != null) {
3018             if (DEBUG) {
3019                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
3020             }
3021             resetSelectedInputMethodAndSubtypeLocked(imi.getId());
3022             return true;
3023         }
3024
3025         return false;
3026     }
3027
3028     void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
3029         if (DEBUG) {
3030             Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
3031                     + " \n ------ caller=" + Debug.getCallers(10));
3032         }
3033         mMethodList.clear();
3034         mMethodMap.clear();
3035
3036         // Use for queryIntentServicesAsUser
3037         final PackageManager pm = mContext.getPackageManager();
3038
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());
3046
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(
3054                     si.permission)) {
3055                 Slog.w(TAG, "Skipping input method " + compName
3056                         + ": it does not require the permission "
3057                         + android.Manifest.permission.BIND_INPUT_METHOD);
3058                 continue;
3059             }
3060
3061             if (DEBUG) Slog.d(TAG, "Checking " + compName);
3062
3063             try {
3064                 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
3065                 mMethodList.add(p);
3066                 final String id = p.getId();
3067                 mMethodMap.put(id, p);
3068
3069                 if (DEBUG) {
3070                     Slog.d(TAG, "Found an input method " + p);
3071                 }
3072             } catch (Exception e) {
3073                 Slog.wtf(TAG, "Unable to load input method " + compName, e);
3074             }
3075         }
3076
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;
3086                     break;
3087                 }
3088             }
3089             if (!enabledImeFound) {
3090                 Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
3091                 resetDefaultEnabledIme = true;
3092                 resetSelectedInputMethodAndSubtypeLocked("");
3093             }
3094         }
3095
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);
3102                 if (DEBUG) {
3103                     Slog.d(TAG, "--- enable ime = " + imi);
3104                 }
3105                 setInputMethodEnabledLocked(imi.getId(), true);
3106             }
3107         }
3108
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);
3115                 }
3116             } else {
3117                 // Double check that the default IME is certainly enabled.
3118                 setInputMethodEnabledLocked(defaultImiId, true);
3119             }
3120         }
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);
3126     }
3127
3128     // ----------------------------------------------------------------------
3129
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);
3137         }
3138         final int userId;
3139         synchronized (mMethodMap) {
3140             userId = mSettings.getCurrentUserId();
3141         }
3142         mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
3143     }
3144
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);
3151     }
3152
3153     private boolean isScreenLocked() {
3154         return mKeyguardManager != null
3155                 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
3156     }
3157
3158     private void showInputMethodMenu(boolean showAuxSubtypes) {
3159         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
3160
3161         final Context context = mContext;
3162         final boolean isScreenLocked = isScreenLocked();
3163
3164         final String lastInputMethodId = mSettings.getSelectedInputMethod();
3165         int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
3166         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
3167
3168         synchronized (mMethodMap) {
3169             final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3170                     mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
3171                             mContext);
3172             if (immis == null || immis.size() == 0) {
3173                 return;
3174             }
3175
3176             hideInputMethodMenuLocked();
3177
3178             final List<ImeSubtypeListItem> imList =
3179                     mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
3180                             showAuxSubtypes, isScreenLocked);
3181
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());
3188                 }
3189             }
3190
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)) {
3204                         checkedItem = i;
3205                     }
3206                 }
3207             }
3208
3209             final Context settingsContext = new ContextThemeWrapper(context,
3210                     com.android.internal.R.style.Theme_DeviceDefault_Settings);
3211
3212             mDialogBuilder = new AlertDialog.Builder(settingsContext);
3213             mDialogBuilder.setOnCancelListener(new OnCancelListener() {
3214                 @Override
3215                 public void onCancel(DialogInterface dialog) {
3216                     hideInputMethodMenu();
3217                 }
3218             });
3219
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);
3226             a.recycle();
3227
3228             mDialogBuilder.setIcon(dialogIcon);
3229
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);
3234
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() {
3245                 @Override
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();
3251                 }
3252             });
3253
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() {
3257                 @Override
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) {
3262                             return;
3263                         }
3264                         final InputMethodInfo im = mIms[which];
3265                         int subtypeId = mSubtypeIds[which];
3266                         adapter.mCheckedItem = which;
3267                         adapter.notifyDataSetChanged();
3268                         hideInputMethodMenu();
3269                         if (im != null) {
3270                             if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
3271                                 subtypeId = NOT_A_SUBTYPE_ID;
3272                             }
3273                             setInputMethodLocked(im.getId(), subtypeId);
3274                         }
3275                     }
3276                 }
3277             };
3278             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
3279
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();
3289         }
3290     }
3291
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);
3300
3301             mTextViewResourceId = textViewResourceId;
3302             mItemsList = itemsList;
3303             mCheckedItem = checkedItem;
3304             mInflater = context.getSystemService(LayoutInflater.class);
3305         }
3306
3307         @Override
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);
3320             } else {
3321                 firstTextView.setText(subtypeName);
3322                 secondTextView.setText(imeName);
3323                 secondTextView.setVisibility(View.VISIBLE);
3324             }
3325             final RadioButton radioButton =
3326                     (RadioButton)view.findViewById(com.android.internal.R.id.radio);
3327             radioButton.setChecked(position == mCheckedItem);
3328             return view;
3329         }
3330     }
3331
3332     void hideInputMethodMenu() {
3333         synchronized (mMethodMap) {
3334             hideInputMethodMenuLocked();
3335         }
3336     }
3337
3338     void hideInputMethodMenuLocked() {
3339         if (DEBUG) Slog.v(TAG, "Hide switching menu");
3340
3341         if (mSwitchingDialog != null) {
3342             mSwitchingDialog.dismiss();
3343             mSwitchingDialog = null;
3344         }
3345
3346         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
3347         mDialogBuilder = null;
3348         mIms = null;
3349     }
3350
3351     // ----------------------------------------------------------------------
3352
3353     @Override
3354     public boolean setInputMethodEnabled(String id, boolean enabled) {
3355         // TODO: Make this work even for non-current users?
3356         if (!calledFromValidUser()) {
3357             return false;
3358         }
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);
3366             }
3367
3368             long ident = Binder.clearCallingIdentity();
3369             try {
3370                 return setInputMethodEnabledLocked(id, enabled);
3371             } finally {
3372                 Binder.restoreCallingIdentity(ident);
3373             }
3374         }
3375     }
3376
3377     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
3378         // Make sure this is a valid input method.
3379         InputMethodInfo imm = mMethodMap.get(id);
3380         if (imm == null) {
3381             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
3382         }
3383
3384         List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
3385                 .getEnabledInputMethodsAndSubtypeListLocked();
3386
3387         if (enabled) {
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.
3392                     return true;
3393                 }
3394             }
3395             mSettings.appendAndPutEnabledInputMethodLocked(id, false);
3396             // Previous state was disabled.
3397             return false;
3398         } else {
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("");
3407                 }
3408                 // Previous state was enabled.
3409                 return true;
3410             } else {
3411                 // We are disabling the input method but it is already disabled.
3412                 // Nothing to do.  The previous state was disabled.
3413                 return false;
3414             }
3415         }
3416     }
3417
3418     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
3419             boolean setSubtypeOnly) {
3420         // Update the history of InputMethod and Subtype
3421         mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
3422
3423         mCurUserActionNotificationSequenceNumber =
3424                 Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
3425         if (DEBUG) {
3426             Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
3427                     + mCurUserActionNotificationSequenceNumber);
3428         }
3429
3430         if (mCurClient != null && mCurClient.client != null) {
3431             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
3432                     MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
3433                     mCurUserActionNotificationSequenceNumber, mCurClient));
3434         }
3435
3436         // Set Subtype here
3437         if (imi == null || subtypeId < 0) {
3438             mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3439             mCurrentSubtype = null;
3440         } else {
3441             if (subtypeId < imi.getSubtypeCount()) {
3442                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
3443                 mSettings.putSelectedSubtype(subtype.hashCode());
3444                 mCurrentSubtype = subtype;
3445             } else {
3446                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3447                 // If the subtype is not specified, choose the most applicable one
3448                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
3449             }
3450         }
3451
3452         if (!setSubtypeOnly) {
3453             // Set InputMethod here
3454             mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
3455         }
3456     }
3457
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) {
3465                 try {
3466                     lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3467                             imi, Integer.parseInt(subtypeHashCode));
3468                 } catch (NumberFormatException e) {
3469                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
3470                 }
3471             }
3472         }
3473         setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
3474     }
3475
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;
3483
3484         // Search applicable subtype for each InputMethodInfo
3485         for (InputMethodInfo imi: imis) {
3486             final String imiId = imi.getId();
3487             if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3488                 continue;
3489             }
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);
3497             }
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);
3503             }
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);
3514             }
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);
3520             }
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;
3526                     break;
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;
3534                     }
3535                 }
3536             }
3537         }
3538         if (DEBUG) {
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());
3546                 }
3547             }
3548         }
3549         if (mostApplicableIMI != null) {
3550             return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
3551         } else {
3552             return null;
3553         }
3554     }
3555
3556     /**
3557      * @return Return the current subtype of this input method.
3558      */
3559     @Override
3560     public InputMethodSubtype getCurrentInputMethodSubtype() {
3561         // TODO: Make this work even for non-current users?
3562         if (!calledFromValidUser()) {
3563             return null;
3564         }
3565         synchronized (mMethodMap) {
3566             return getCurrentInputMethodSubtypeLocked();
3567         }
3568     }
3569
3570     private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3571         if (mCurMethodId == null) {
3572             return null;
3573         }
3574         final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3575         final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3576         if (imi == null || imi.getSubtypeCount() == 0) {
3577             return null;
3578         }
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
3585                 // subtypes.
3586                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3587                         mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3588                 // If there is only one explicitly or implicitly enabled subtype,
3589                 // just returns it.
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,
3599                                 true);
3600                     }
3601                 }
3602             } else {
3603                 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3604             }
3605         }
3606         return mCurrentSubtype;
3607     }
3608
3609     // TODO: We should change the return type from List to List<Parcelable>
3610     @SuppressWarnings("rawtypes")
3611     @Override
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);
3622                 if (info != null) {
3623                     ret.add(info.first);
3624                     ret.add(info.second);
3625                 }
3626                 return ret;
3627             }
3628             for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3629                 ret.add(imi);
3630                 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3631                     ret.add(subtype);
3632                 }
3633             }
3634             return ret;
3635         }
3636     }
3637
3638     @Override
3639     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3640         // TODO: Make this work even for non-current users?
3641         if (!calledFromValidUser()) {
3642             return false;
3643         }
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);
3650                     return true;
3651                 }
3652             }
3653             return false;
3654         }
3655     }
3656
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 =
3678                 new HashMap<>();
3679         public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3680             if (methodMap == null) {
3681                 throw new NullPointerException("methodMap is null");
3682             }
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());
3690             }
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);
3697             } else {
3698                 readAdditionalInputMethodSubtypes(
3699                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3700             }
3701         }
3702
3703         private void deleteAllInputMethodSubtypes(String imiId) {
3704             synchronized (mMethodMap) {
3705                 mAdditionalSubtypesMap.remove(imiId);
3706                 writeAdditionalInputMethodSubtypes(
3707                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3708             }
3709         }
3710
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);
3720                     } else {
3721                         Slog.w(TAG, "Duplicated subtype definition found: "
3722                                 + subtype.getLocale() + ", " + subtype.getMode());
3723                     }
3724                 }
3725                 mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3726                 writeAdditionalInputMethodSubtypes(
3727                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3728             }
3729         }
3730
3731         public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3732             synchronized (mMethodMap) {
3733                 return mAdditionalSubtypesMap;
3734             }
3735         }
3736
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;
3743             try {
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);
3753                         continue;
3754                     }
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()));
3765                         }
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);
3778                     }
3779                     out.endTag(null, NODE_IMI);
3780                 }
3781                 out.endTag(null, NODE_SUBTYPES);
3782                 out.endDocument();
3783                 subtypesFile.finishWrite(fos);
3784             } catch (java.io.IOException e) {
3785                 Slog.w(TAG, "Error writing subtypes", e);
3786                 if (fos != null) {
3787                     subtypesFile.failWrite(fos);
3788                 }
3789             }
3790         }
3791
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");
3806                 }
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)
3813                         continue;
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");
3819                             continue;
3820                         }
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);
3826                             continue;
3827                         }
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));
3857                         }
3858                         tempSubtypesArray.add(builder.build());
3859                     }
3860                 }
3861             } catch (XmlPullParserException | IOException | NumberFormatException e) {
3862                 Slog.w(TAG, "Error reading subtypes", e);
3863                 return;
3864             }
3865         }
3866     }
3867
3868     private static final class LocalServiceImpl implements InputMethodManagerInternal {
3869         @NonNull
3870         private final Handler mHandler;
3871
3872         LocalServiceImpl(@NonNull final Handler handler) {
3873             mHandler = handler;
3874         }
3875
3876         @Override
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));
3881         }
3882
3883         @Override
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));
3888         }
3889
3890         @Override
3891         public void hideCurrentInputMethod() {
3892             mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
3893             mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
3894         }
3895     }
3896
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");
3902             first = false;
3903         }
3904         if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
3905             if (!first) {
3906                 sb.append("|");
3907             }
3908             sb.append("Visible");
3909         }
3910         return sb.toString();
3911     }
3912
3913     @Override
3914     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3915         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3916                 != PackageManager.PERMISSION_GRANTED) {
3917
3918             pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3919                     + Binder.getCallingPid()
3920                     + ", uid=" + Binder.getCallingUid());
3921             return;
3922         }
3923
3924         IInputMethod method;
3925         ClientState client;
3926         ClientState focusedWindowClient;
3927
3928         final Printer p = new PrintWriterPrinter(pw);
3929
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 + ":");
3937                 info.dump(p, "    ");
3938             }
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);
3946             }
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, "    ");
3973         }
3974
3975         p.println(" ");
3976         if (client != null) {
3977             pw.flush();
3978             try {
3979                 client.client.asBinder().dump(fd, args);
3980             } catch (RemoteException e) {
3981                 p.println("Input method client dead: " + e);
3982             }
3983         } else {
3984             p.println("No input method client.");
3985         }
3986
3987         if (focusedWindowClient != null && client != focusedWindowClient) {
3988             p.println(" ");
3989             p.println("Warning: Current input method client doesn't match the last focused. "
3990                     + "window.");
3991             p.println("Dumping input method client in the last focused window just in case.");
3992             p.println(" ");
3993             pw.flush();
3994             try {
3995                 focusedWindowClient.client.asBinder().dump(fd, args);
3996             } catch (RemoteException e) {
3997                 p.println("Input method client in focused window dead: " + e);
3998             }
3999         }
4000
4001         p.println(" ");
4002         if (method != null) {
4003             pw.flush();
4004             try {
4005                 method.asBinder().dump(fd, args);
4006             } catch (RemoteException e) {
4007                 p.println("Input method service dead: " + e);
4008             }
4009         } else {
4010             p.println("No input method service.");
4011         }
4012     }
4013 }