OSDN Git Service

b6a0e5cc9957fb81d55afa874ff7f42059c087dc
[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 android.view.Display.DEFAULT_DISPLAY;
19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
20 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
21 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
22 import static java.lang.annotation.RetentionPolicy.SOURCE;
23
24 import com.android.internal.annotations.GuardedBy;
25 import com.android.internal.content.PackageMonitor;
26 import com.android.internal.inputmethod.IInputContentUriToken;
27 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
28 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
29 import com.android.internal.inputmethod.InputMethodUtils;
30 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
31 import com.android.internal.os.HandlerCaller;
32 import com.android.internal.os.SomeArgs;
33 import com.android.internal.os.TransferPipe;
34 import com.android.internal.util.FastXmlSerializer;
35 import com.android.internal.view.IInputContext;
36 import com.android.internal.view.IInputMethod;
37 import com.android.internal.view.IInputMethodClient;
38 import com.android.internal.view.IInputMethodManager;
39 import com.android.internal.view.IInputMethodSession;
40 import com.android.internal.view.IInputSessionCallback;
41 import com.android.internal.view.InputBindResult;
42 import com.android.internal.view.InputMethodClient;
43 import com.android.server.statusbar.StatusBarManagerService;
44
45 import org.xmlpull.v1.XmlPullParser;
46 import org.xmlpull.v1.XmlPullParserException;
47 import org.xmlpull.v1.XmlSerializer;
48
49 import android.annotation.IntDef;
50 import android.annotation.NonNull;
51 import android.annotation.Nullable;
52 import android.annotation.UserIdInt;
53 import android.app.ActivityManager;
54 import android.app.ActivityManagerInternal;
55 import android.app.AlertDialog;
56 import android.app.AppGlobals;
57 import android.app.AppOpsManager;
58 import android.app.KeyguardManager;
59 import android.app.Notification;
60 import android.app.NotificationManager;
61 import android.app.PendingIntent;
62 import android.content.BroadcastReceiver;
63 import android.content.ComponentName;
64 import android.content.ContentProvider;
65 import android.content.ContentResolver;
66 import android.content.Context;
67 import android.content.DialogInterface;
68 import android.content.DialogInterface.OnCancelListener;
69 import android.content.DialogInterface.OnClickListener;
70 import android.content.Intent;
71 import android.content.IntentFilter;
72 import android.content.ServiceConnection;
73 import android.content.pm.ApplicationInfo;
74 import android.content.pm.IPackageManager;
75 import android.content.pm.PackageManager;
76 import android.content.pm.ResolveInfo;
77 import android.content.pm.ServiceInfo;
78 import android.content.res.Configuration;
79 import android.content.res.Resources;
80 import android.content.res.TypedArray;
81 import android.database.ContentObserver;
82 import android.graphics.drawable.Drawable;
83 import android.hardware.input.InputManagerInternal;
84 import android.inputmethodservice.InputMethodService;
85 import android.net.Uri;
86 import android.os.Binder;
87 import android.os.Bundle;
88 import android.os.Debug;
89 import android.os.Environment;
90 import android.os.Handler;
91 import android.os.IBinder;
92 import android.os.IInterface;
93 import android.os.Message;
94 import android.os.LocaleList;
95 import android.os.Parcel;
96 import android.os.Process;
97 import android.os.RemoteException;
98 import android.os.ResultReceiver;
99 import android.os.ServiceManager;
100 import android.os.SystemClock;
101 import android.os.UserHandle;
102 import android.os.UserManager;
103 import android.provider.Settings;
104 import android.text.TextUtils;
105 import android.text.style.SuggestionSpan;
106 import android.util.ArrayMap;
107 import android.util.ArraySet;
108 import android.util.AtomicFile;
109 import android.util.EventLog;
110 import android.util.LruCache;
111 import android.util.Pair;
112 import android.util.PrintWriterPrinter;
113 import android.util.Printer;
114 import android.util.Slog;
115 import android.util.Xml;
116 import android.view.ContextThemeWrapper;
117 import android.view.IWindowManager;
118 import android.view.InputChannel;
119 import android.view.LayoutInflater;
120 import android.view.View;
121 import android.view.ViewGroup;
122 import android.view.Window;
123 import android.view.WindowManager;
124 import android.view.WindowManagerInternal;
125 import android.view.inputmethod.EditorInfo;
126 import android.view.inputmethod.InputBinding;
127 import android.view.inputmethod.InputConnection;
128 import android.view.inputmethod.InputConnectionInspector;
129 import android.view.inputmethod.InputMethod;
130 import android.view.inputmethod.InputMethodInfo;
131 import android.view.inputmethod.InputMethodManager;
132 import android.view.inputmethod.InputMethodManagerInternal;
133 import android.view.inputmethod.InputMethodSubtype;
134 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
135 import android.widget.ArrayAdapter;
136 import android.widget.CompoundButton;
137 import android.widget.CompoundButton.OnCheckedChangeListener;
138 import android.widget.RadioButton;
139 import android.widget.Switch;
140 import android.widget.TextView;
141 import android.widget.Toast;
142
143 import java.io.File;
144 import java.io.FileDescriptor;
145 import java.io.FileInputStream;
146 import java.io.FileOutputStream;
147 import java.io.IOException;
148 import java.io.PrintWriter;
149 import java.lang.annotation.Retention;
150 import java.nio.charset.StandardCharsets;
151 import java.security.InvalidParameterException;
152 import java.util.ArrayList;
153 import java.util.Collections;
154 import java.util.HashMap;
155 import java.util.List;
156 import java.util.WeakHashMap;
157
158 /**
159  * This class provides a system service that manages input methods.
160  */
161 public class InputMethodManagerService extends IInputMethodManager.Stub
162         implements ServiceConnection, Handler.Callback {
163     static final boolean DEBUG = false;
164     static final boolean DEBUG_RESTORE = DEBUG || false;
165     static final String TAG = "InputMethodManagerService";
166
167     static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
168     static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
169     static final int MSG_SHOW_IM_CONFIG = 3;
170
171     static final int MSG_UNBIND_INPUT = 1000;
172     static final int MSG_BIND_INPUT = 1010;
173     static final int MSG_SHOW_SOFT_INPUT = 1020;
174     static final int MSG_HIDE_SOFT_INPUT = 1030;
175     static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
176     static final int MSG_ATTACH_TOKEN = 1040;
177     static final int MSG_CREATE_SESSION = 1050;
178
179     static final int MSG_START_INPUT = 2000;
180
181     static final int MSG_UNBIND_CLIENT = 3000;
182     static final int MSG_BIND_CLIENT = 3010;
183     static final int MSG_SET_ACTIVE = 3020;
184     static final int MSG_SET_INTERACTIVE = 3030;
185     static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
186     static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
187     static final int MSG_SWITCH_IME = 3050;
188
189     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
190
191     static final int MSG_SYSTEM_UNLOCK_USER = 5000;
192
193     static final long TIME_TO_RECONNECT = 3 * 1000;
194
195     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
196
197     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
198     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
199
200     @Retention(SOURCE)
201     @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
202     private @interface  HardKeyboardBehavior {
203         int WIRELESS_AFFORDANCE = 0;
204         int WIRED_AFFORDANCE = 1;
205     }
206
207     final Context mContext;
208     final Resources mRes;
209     final Handler mHandler;
210     final InputMethodSettings mSettings;
211     final SettingsObserver mSettingsObserver;
212     final IWindowManager mIWindowManager;
213     final WindowManagerInternal mWindowManagerInternal;
214     final HandlerCaller mCaller;
215     final boolean mHasFeature;
216     private InputMethodFileManager mFileManager;
217     private final HardKeyboardListener mHardKeyboardListener;
218     private final AppOpsManager mAppOpsManager;
219     private final UserManager mUserManager;
220
221     final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
222
223     // All known input methods.  mMethodMap also serves as the global
224     // lock for this class.
225     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
226     final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
227     private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
228             new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
229     private final InputMethodSubtypeSwitchingController mSwitchingController;
230
231     /**
232      * Tracks how many times {@link #mMethodMap} was updated.
233      */
234     @GuardedBy("mMethodMap")
235     private int mMethodMapUpdateCount = 0;
236
237     // Used to bring IME service up to visible adjustment while it is being shown.
238     final ServiceConnection mVisibleConnection = new ServiceConnection() {
239         @Override public void onServiceConnected(ComponentName name, IBinder service) {
240         }
241
242         @Override public void onServiceDisconnected(ComponentName name) {
243         }
244     };
245     boolean mVisibleBound = false;
246
247     // Ongoing notification
248     private NotificationManager mNotificationManager;
249     private KeyguardManager mKeyguardManager;
250     private @Nullable StatusBarManagerService mStatusBar;
251     private Notification.Builder mImeSwitcherNotification;
252     private PendingIntent mImeSwitchPendingIntent;
253     private boolean mShowOngoingImeSwitcherForPhones;
254     private boolean mNotificationShown;
255
256     static class SessionState {
257         final ClientState client;
258         final IInputMethod method;
259
260         IInputMethodSession session;
261         InputChannel channel;
262
263         @Override
264         public String toString() {
265             return "SessionState{uid " + client.uid + " pid " + client.pid
266                     + " method " + Integer.toHexString(
267                             System.identityHashCode(method))
268                     + " session " + Integer.toHexString(
269                             System.identityHashCode(session))
270                     + " channel " + channel
271                     + "}";
272         }
273
274         SessionState(ClientState _client, IInputMethod _method,
275                 IInputMethodSession _session, InputChannel _channel) {
276             client = _client;
277             method = _method;
278             session = _session;
279             channel = _channel;
280         }
281     }
282
283     static final class ClientState {
284         final IInputMethodClient client;
285         final IInputContext inputContext;
286         final int uid;
287         final int pid;
288         final InputBinding binding;
289
290         boolean sessionRequested;
291         SessionState curSession;
292
293         @Override
294         public String toString() {
295             return "ClientState{" + Integer.toHexString(
296                     System.identityHashCode(this)) + " uid " + uid
297                     + " pid " + pid + "}";
298         }
299
300         ClientState(IInputMethodClient _client, IInputContext _inputContext,
301                 int _uid, int _pid) {
302             client = _client;
303             inputContext = _inputContext;
304             uid = _uid;
305             pid = _pid;
306             binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
307         }
308     }
309
310     final HashMap<IBinder, ClientState> mClients = new HashMap<>();
311
312     /**
313      * Set once the system is ready to run third party code.
314      */
315     boolean mSystemReady;
316
317     /**
318      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
319      * method.  This is to be synchronized with the secure settings keyed with
320      * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
321      *
322      * <p>This can be transiently {@code null} when the system is re-initializing input method
323      * settings, e.g., the system locale is just changed.</p>
324      *
325      * <p>Note that {@link #mCurId} is used to track which IME is being connected to
326      * {@link InputMethodManagerService}.</p>
327      *
328      * @see #mCurId
329      */
330     @Nullable
331     String mCurMethodId;
332
333     /**
334      * The current binding sequence number, incremented every time there is
335      * a new bind performed.
336      */
337     int mCurSeq;
338
339     /**
340      * The client that is currently bound to an input method.
341      */
342     ClientState mCurClient;
343
344     /**
345      * The last window token that we confirmed to be focused.  This is always updated upon reports
346      * from the input method client.  If the window state is already changed before the report is
347      * handled, this field just keeps the last value.
348      */
349     IBinder mCurFocusedWindow;
350
351     /**
352      * The client by which {@link #mCurFocusedWindow} was reported.  Used only for debugging.
353      */
354     ClientState mCurFocusedWindowClient;
355
356     /**
357      * The input context last provided by the current client.
358      */
359     IInputContext mCurInputContext;
360
361     /**
362      * The missing method flags for the input context last provided by the current client.
363      *
364      * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
365      */
366     int mCurInputContextMissingMethods;
367
368     /**
369      * The attributes last provided by the current client.
370      */
371     EditorInfo mCurAttribute;
372
373     /**
374      * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
375      * connected to or in the process of connecting to.
376      *
377      * <p>This can be {@code null} when no input method is connected.</p>
378      *
379      * @see #mCurMethodId
380      */
381     @Nullable
382     String mCurId;
383
384     /**
385      * The current subtype of the current input method.
386      */
387     private InputMethodSubtype mCurrentSubtype;
388
389     // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
390     private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
391             mShortcutInputMethodsAndSubtypes = new HashMap<>();
392
393     // Was the keyguard locked when this client became current?
394     private boolean mCurClientInKeyguard;
395
396     /**
397      * Set to true if our ServiceConnection is currently actively bound to
398      * a service (whether or not we have gotten its IBinder back yet).
399      */
400     boolean mHaveConnection;
401
402     /**
403      * Set if the client has asked for the input method to be shown.
404      */
405     boolean mShowRequested;
406
407     /**
408      * Set if we were explicitly told to show the input method.
409      */
410     boolean mShowExplicitlyRequested;
411
412     /**
413      * Set if we were forced to be shown.
414      */
415     boolean mShowForced;
416
417     /**
418      * Set if we last told the input method to show itself.
419      */
420     boolean mInputShown;
421
422     /**
423      * {@code true} if the current input method is in fullscreen mode.
424      */
425     boolean mInFullscreenMode;
426
427     /**
428      * The Intent used to connect to the current input method.
429      */
430     Intent mCurIntent;
431
432     /**
433      * The token we have made for the currently active input method, to
434      * identify it in the future.
435      */
436     IBinder mCurToken;
437
438     /**
439      * If non-null, this is the input method service we are currently connected
440      * to.
441      */
442     IInputMethod mCurMethod;
443
444     /**
445      * Time that we last initiated a bind to the input method, to determine
446      * if we should try to disconnect and reconnect to it.
447      */
448     long mLastBindTime;
449
450     /**
451      * Have we called mCurMethod.bindInput()?
452      */
453     boolean mBoundToMethod;
454
455     /**
456      * Currently enabled session.  Only touched by service thread, not
457      * protected by a lock.
458      */
459     SessionState mEnabledSession;
460
461     /**
462      * True if the device is currently interactive with user.  The value is true initially.
463      */
464     boolean mIsInteractive = true;
465
466     int mCurUserActionNotificationSequenceNumber = 0;
467
468     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
469
470     /**
471      * A set of status bits regarding the active IME.
472      *
473      * <p>This value is a combination of following two bits:</p>
474      * <dl>
475      * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
476      * <dd>
477      *   If this bit is ON, connected IME is ready to accept touch/key events.
478      * </dd>
479      * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
480      * <dd>
481      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
482      * </dd>
483      * </dl>
484      * <em>Do not update this value outside of setImeWindowStatus.</em>
485      */
486     int mImeWindowVis;
487
488     private AlertDialog.Builder mDialogBuilder;
489     private AlertDialog mSwitchingDialog;
490     private IBinder mSwitchingDialogToken = new Binder();
491     private View mSwitchingDialogTitleView;
492     private Toast mSubtypeSwitchedByShortCutToast;
493     private InputMethodInfo[] mIms;
494     private int[] mSubtypeIds;
495     private LocaleList mLastSystemLocales;
496     private boolean mShowImeWithHardKeyboard;
497     private boolean mAccessibilityRequestingNoSoftKeyboard;
498     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
499     private final IPackageManager mIPackageManager;
500     private final String mSlotIme;
501     @HardKeyboardBehavior
502     private final int mHardKeyboardBehavior;
503
504     /**
505      * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the
506      * internal message queue. Any subsequent state change inside {@link InputMethodManagerService}
507      * will not affect those tasks that are already posted.
508      *
509      * <p>Posting {@link #MSG_START_INPUT} message basically means that
510      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
511      * back in the current IME process shortly, which will also affect what the current IME starts
512      * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
513      * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
514      * logical input session between the client application and the current IME.</p>
515      *
516      * <p>Be careful to not keep strong references to this object forever, which can prevent
517      * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
518      * </p>
519      */
520     private static class StartInputInfo {
521         @NonNull
522         final IBinder mImeToken;
523         @Nullable
524         final IBinder mTargetWindow;
525
526         StartInputInfo(@NonNull IBinder imeToken, @Nullable IBinder targetWindow) {
527             mImeToken = imeToken;
528             mTargetWindow = targetWindow;
529         }
530     }
531
532     private WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
533
534     class SettingsObserver extends ContentObserver {
535         int mUserId;
536         boolean mRegistered = false;
537         @NonNull
538         String mLastEnabled = "";
539
540         /**
541          * <em>This constructor must be called within the lock.</em>
542          */
543         SettingsObserver(Handler handler) {
544             super(handler);
545         }
546
547         public void registerContentObserverLocked(@UserIdInt int userId) {
548             if (mRegistered && mUserId == userId) {
549                 return;
550             }
551             ContentResolver resolver = mContext.getContentResolver();
552             if (mRegistered) {
553                 mContext.getContentResolver().unregisterContentObserver(this);
554                 mRegistered = false;
555             }
556             if (mUserId != userId) {
557                 mLastEnabled = "";
558                 mUserId = userId;
559             }
560             resolver.registerContentObserver(Settings.Secure.getUriFor(
561                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
562             resolver.registerContentObserver(Settings.Secure.getUriFor(
563                     Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
564             resolver.registerContentObserver(Settings.Secure.getUriFor(
565                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
566             resolver.registerContentObserver(Settings.Secure.getUriFor(
567                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
568             resolver.registerContentObserver(Settings.Secure.getUriFor(
569                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
570             mRegistered = true;
571         }
572
573         @Override public void onChange(boolean selfChange, Uri uri) {
574             final Uri showImeUri = Settings.Secure.getUriFor(
575                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
576             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
577                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
578             synchronized (mMethodMap) {
579                 if (showImeUri.equals(uri)) {
580                     updateKeyboardFromSettingsLocked();
581                 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
582                     mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
583                             mContext.getContentResolver(),
584                             Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
585                             0, mUserId) == 1;
586                     if (mAccessibilityRequestingNoSoftKeyboard) {
587                         final boolean showRequested = mShowRequested;
588                         hideCurrentInputLocked(0, null);
589                         mShowRequested = showRequested;
590                     } else if (mShowRequested) {
591                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
592                     }
593                 } else {
594                     boolean enabledChanged = false;
595                     String newEnabled = mSettings.getEnabledInputMethodsStr();
596                     if (!mLastEnabled.equals(newEnabled)) {
597                         mLastEnabled = newEnabled;
598                         enabledChanged = true;
599                     }
600                     updateInputMethodsFromSettingsLocked(enabledChanged);
601                 }
602             }
603         }
604
605         @Override
606         public String toString() {
607             return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
608                     + " mLastEnabled=" + mLastEnabled + "}";
609         }
610     }
611
612     class ImmsBroadcastReceiver extends BroadcastReceiver {
613         @Override
614         public void onReceive(Context context, Intent intent) {
615             final String action = intent.getAction();
616             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
617                 hideInputMethodMenu();
618                 // No need to update mIsInteractive
619                 return;
620             } else if (Intent.ACTION_USER_ADDED.equals(action)
621                     || Intent.ACTION_USER_REMOVED.equals(action)) {
622                 updateCurrentProfileIds();
623                 return;
624             } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
625                 final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
626                 if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) {
627                     final String prevValue = intent.getStringExtra(
628                             Intent.EXTRA_SETTING_PREVIOUS_VALUE);
629                     final String newValue = intent.getStringExtra(
630                             Intent.EXTRA_SETTING_NEW_VALUE);
631                     restoreEnabledInputMethods(mContext, prevValue, newValue);
632                 }
633             } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
634                 onActionLocaleChanged();
635             } else {
636                 Slog.w(TAG, "Unexpected intent " + intent);
637             }
638         }
639     }
640
641     /**
642      * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
643      *
644      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
645      * the users. We should ignore this event if this is about any background user's locale.</p>
646      *
647      * <p>Caution: This method must not be called when system is not ready.</p>
648      */
649     void onActionLocaleChanged() {
650         synchronized (mMethodMap) {
651             final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
652             if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
653                 return;
654             }
655             buildInputMethodListLocked(true);
656             // If the locale is changed, needs to reset the default ime
657             resetDefaultImeLocked(mContext);
658             updateFromSettingsLocked(true);
659             mLastSystemLocales = possibleNewLocale;
660         }
661     }
662
663     // Apply the results of a restore operation to the set of enabled IMEs.  Note that this
664     // does not attempt to validate on the fly with any installed device policy, so must only
665     // be run in the context of initial device setup.
666     //
667     // TODO: Move this method to InputMethodUtils with adding unit tests.
668     static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
669         if (DEBUG_RESTORE) {
670             Slog.i(TAG, "Restoring enabled input methods:");
671             Slog.i(TAG, "prev=" + prevValue);
672             Slog.i(TAG, " new=" + newValue);
673         }
674         // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore
675         ArrayMap<String, ArraySet<String>> prevMap =
676                 InputMethodUtils.parseInputMethodsAndSubtypesString(prevValue);
677         ArrayMap<String, ArraySet<String>> newMap =
678                 InputMethodUtils.parseInputMethodsAndSubtypesString(newValue);
679
680         // Merge the restored ime+subtype enabled states into the live state
681         for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) {
682             final String imeId = entry.getKey();
683             ArraySet<String> prevSubtypes = prevMap.get(imeId);
684             if (prevSubtypes == null) {
685                 prevSubtypes = new ArraySet<>(2);
686                 prevMap.put(imeId, prevSubtypes);
687             }
688             prevSubtypes.addAll(entry.getValue());
689         }
690
691         final String mergedImesAndSubtypesString =
692                 InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap);
693         if (DEBUG_RESTORE) {
694             Slog.i(TAG, "Merged IME string:");
695             Slog.i(TAG, "     " + mergedImesAndSubtypesString);
696         }
697         Settings.Secure.putString(context.getContentResolver(),
698                 Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
699     }
700
701     final class MyPackageMonitor extends PackageMonitor {
702         /**
703          * Set of packages to be monitored.
704          *
705          * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
706          * all the packages when the user is unlocked, and direct-boot awareness will not be changed
707          * dynamically unless the entire package is updated, which also always triggers package
708          * rescanning.</p>
709          */
710         @GuardedBy("mMethodMap")
711         private ArraySet<String> mPackagesToMonitorComponentChange = new ArraySet<>();
712
713         @GuardedBy("mMethodMap")
714         void clearPackagesToMonitorComponentChangeLocked() {
715             mPackagesToMonitorComponentChange.clear();
716         }
717
718         @GuardedBy("mMethodMap")
719         final void addPackageToMonitorComponentChangeLocked(@NonNull String packageName) {
720             mPackagesToMonitorComponentChange.add(packageName);
721         }
722
723         private boolean isChangingPackagesOfCurrentUser() {
724             final int userId = getChangingUserId();
725             final boolean retval = userId == mSettings.getCurrentUserId();
726             if (DEBUG) {
727                 if (!retval) {
728                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
729                 }
730             }
731             return retval;
732         }
733
734         @Override
735         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
736             if (!isChangingPackagesOfCurrentUser()) {
737                 return false;
738             }
739             synchronized (mMethodMap) {
740                 String curInputMethodId = mSettings.getSelectedInputMethod();
741                 final int N = mMethodList.size();
742                 if (curInputMethodId != null) {
743                     for (int i=0; i<N; i++) {
744                         InputMethodInfo imi = mMethodList.get(i);
745                         if (imi.getId().equals(curInputMethodId)) {
746                             for (String pkg : packages) {
747                                 if (imi.getPackageName().equals(pkg)) {
748                                     if (!doit) {
749                                         return true;
750                                     }
751                                     resetSelectedInputMethodAndSubtypeLocked("");
752                                     chooseNewDefaultIMELocked();
753                                     return true;
754                                 }
755                             }
756                         }
757                     }
758                 }
759             }
760             return false;
761         }
762
763         @Override
764         public boolean onPackageChanged(String packageName, int uid, String[] components) {
765             // If this package is in the watch list, we want to check it.
766             synchronized (mMethodMap) {
767                 return mPackagesToMonitorComponentChange.contains(packageName);
768             }
769         }
770
771         @Override
772         public void onSomePackagesChanged() {
773             if (!isChangingPackagesOfCurrentUser()) {
774                 return;
775             }
776             synchronized (mMethodMap) {
777                 InputMethodInfo curIm = null;
778                 String curInputMethodId = mSettings.getSelectedInputMethod();
779                 final int N = mMethodList.size();
780                 if (curInputMethodId != null) {
781                     for (int i=0; i<N; i++) {
782                         InputMethodInfo imi = mMethodList.get(i);
783                         final String imiId = imi.getId();
784                         if (imiId.equals(curInputMethodId)) {
785                             curIm = imi;
786                         }
787
788                         int change = isPackageDisappearing(imi.getPackageName());
789                         if (isPackageModified(imi.getPackageName())) {
790                             mFileManager.deleteAllInputMethodSubtypes(imiId);
791                         }
792                         if (change == PACKAGE_TEMPORARY_CHANGE
793                                 || change == PACKAGE_PERMANENT_CHANGE) {
794                             Slog.i(TAG, "Input method uninstalled, disabling: "
795                                     + imi.getComponent());
796                             setInputMethodEnabledLocked(imi.getId(), false);
797                         }
798                     }
799                 }
800
801                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
802
803                 boolean changed = false;
804
805                 if (curIm != null) {
806                     int change = isPackageDisappearing(curIm.getPackageName());
807                     if (change == PACKAGE_TEMPORARY_CHANGE
808                             || change == PACKAGE_PERMANENT_CHANGE) {
809                         ServiceInfo si = null;
810                         try {
811                             si = mIPackageManager.getServiceInfo(
812                                     curIm.getComponent(), 0, mSettings.getCurrentUserId());
813                         } catch (RemoteException ex) {
814                         }
815                         if (si == null) {
816                             // Uh oh, current input method is no longer around!
817                             // Pick another one...
818                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
819                             updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
820                             if (!chooseNewDefaultIMELocked()) {
821                                 changed = true;
822                                 curIm = null;
823                                 Slog.i(TAG, "Unsetting current input method");
824                                 resetSelectedInputMethodAndSubtypeLocked("");
825                             }
826                         }
827                     }
828                 }
829
830                 if (curIm == null) {
831                     // We currently don't have a default input method... is
832                     // one now available?
833                     changed = chooseNewDefaultIMELocked();
834                 } else if (!changed && isPackageModified(curIm.getPackageName())) {
835                     // Even if the current input method is still available, mCurrentSubtype could
836                     // be obsolete when the package is modified in practice.
837                     changed = true;
838                 }
839
840                 if (changed) {
841                     updateFromSettingsLocked(false);
842                 }
843             }
844         }
845     }
846
847     private static final class MethodCallback extends IInputSessionCallback.Stub {
848         private final InputMethodManagerService mParentIMMS;
849         private final IInputMethod mMethod;
850         private final InputChannel mChannel;
851
852         MethodCallback(InputMethodManagerService imms, IInputMethod method,
853                 InputChannel channel) {
854             mParentIMMS = imms;
855             mMethod = method;
856             mChannel = channel;
857         }
858
859         @Override
860         public void sessionCreated(IInputMethodSession session) {
861             long ident = Binder.clearCallingIdentity();
862             try {
863                 mParentIMMS.onSessionCreated(mMethod, session, mChannel);
864             } finally {
865                 Binder.restoreCallingIdentity(ident);
866             }
867         }
868     }
869
870     private class HardKeyboardListener
871             implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
872         @Override
873         public void onHardKeyboardStatusChange(boolean available) {
874             mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
875                         available ? 1 : 0));
876         }
877
878         public void handleHardKeyboardStatusChange(boolean available) {
879             if (DEBUG) {
880                 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
881             }
882             synchronized(mMethodMap) {
883                 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
884                         && mSwitchingDialog.isShowing()) {
885                     mSwitchingDialogTitleView.findViewById(
886                             com.android.internal.R.id.hard_keyboard_section).setVisibility(
887                                     available ? View.VISIBLE : View.GONE);
888                 }
889             }
890         }
891     }
892
893     public static final class Lifecycle extends SystemService {
894         private InputMethodManagerService mService;
895
896         public Lifecycle(Context context) {
897             super(context);
898             mService = new InputMethodManagerService(context);
899         }
900
901         @Override
902         public void onStart() {
903             LocalServices.addService(InputMethodManagerInternal.class,
904                     new LocalServiceImpl(mService.mHandler));
905             publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
906         }
907
908         @Override
909         public void onSwitchUser(@UserIdInt int userHandle) {
910             // Called on ActivityManager thread.
911             // TODO: Dispatch this to a worker thread as needed.
912             mService.onSwitchUser(userHandle);
913         }
914
915         @Override
916         public void onBootPhase(int phase) {
917             // Called on ActivityManager thread.
918             // TODO: Dispatch this to a worker thread as needed.
919             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
920                 StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
921                         .getService(Context.STATUS_BAR_SERVICE);
922                 mService.systemRunning(statusBarService);
923             }
924         }
925
926         @Override
927         public void onUnlockUser(final @UserIdInt int userHandle) {
928             // Called on ActivityManager thread.
929             mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
930                     userHandle /* arg1 */, 0 /* arg2 */));
931         }
932     }
933
934     void onUnlockUser(@UserIdInt int userId) {
935         synchronized(mMethodMap) {
936             final int currentUserId = mSettings.getCurrentUserId();
937             if (DEBUG) {
938                 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
939             }
940             if (userId != currentUserId) {
941                 return;
942             }
943             mSettings.switchCurrentUser(currentUserId, !mSystemReady);
944             if (mSystemReady) {
945                 // We need to rebuild IMEs.
946                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
947                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
948             }
949         }
950     }
951
952     void onSwitchUser(@UserIdInt int userId) {
953         synchronized (mMethodMap) {
954             switchUserLocked(userId);
955         }
956     }
957
958     public InputMethodManagerService(Context context) {
959         mIPackageManager = AppGlobals.getPackageManager();
960         mContext = context;
961         mRes = context.getResources();
962         mHandler = new Handler(this);
963         // Note: SettingsObserver doesn't register observers in its constructor.
964         mSettingsObserver = new SettingsObserver(mHandler);
965         mIWindowManager = IWindowManager.Stub.asInterface(
966                 ServiceManager.getService(Context.WINDOW_SERVICE));
967         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
968         mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
969             @Override
970             public void executeMessage(Message msg) {
971                 handleMessage(msg);
972             }
973         }, true /*asyncHandler*/);
974         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
975         mUserManager = mContext.getSystemService(UserManager.class);
976         mHardKeyboardListener = new HardKeyboardListener();
977         mHasFeature = context.getPackageManager().hasSystemFeature(
978                 PackageManager.FEATURE_INPUT_METHODS);
979         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
980         mHardKeyboardBehavior = mContext.getResources().getInteger(
981                 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
982
983         Bundle extras = new Bundle();
984         extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
985         mImeSwitcherNotification = new Notification.Builder(mContext)
986             .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
987             .setWhen(0)
988             .setOngoing(true)
989             .addExtras(extras)
990             .setCategory(Notification.CATEGORY_SYSTEM)
991             .setColor(com.android.internal.R.color.system_notification_accent_color);
992
993         Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
994         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
995
996         mShowOngoingImeSwitcherForPhones = false;
997
998         mNotificationShown = false;
999         int userId = 0;
1000         try {
1001             userId = ActivityManager.getService().getCurrentUser().id;
1002         } catch (RemoteException e) {
1003             Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
1004         }
1005
1006         // mSettings should be created before buildInputMethodListLocked
1007         mSettings = new InputMethodSettings(
1008                 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
1009
1010         updateCurrentProfileIds();
1011         mFileManager = new InputMethodFileManager(mMethodMap, userId);
1012         mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
1013                 mSettings, context);
1014     }
1015
1016     private void resetDefaultImeLocked(Context context) {
1017         // Do not reset the default (current) IME when it is a 3rd-party IME
1018         if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
1019             return;
1020         }
1021         final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
1022                 context, mSettings.getEnabledInputMethodListLocked());
1023         if (suitableImes.isEmpty()) {
1024             Slog.i(TAG, "No default found");
1025             return;
1026         }
1027         final InputMethodInfo defIm = suitableImes.get(0);
1028         if (DEBUG) {
1029             Slog.i(TAG, "Default found, using " + defIm.getId());
1030         }
1031         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
1032     }
1033
1034     private void switchUserLocked(int newUserId) {
1035         if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
1036                 + " currentUserId=" + mSettings.getCurrentUserId());
1037
1038         // ContentObserver should be registered again when the user is changed
1039         mSettingsObserver.registerContentObserverLocked(newUserId);
1040
1041         // If the system is not ready or the device is not yed unlocked by the user, then we use
1042         // copy-on-write settings.
1043         final boolean useCopyOnWriteSettings =
1044                 !mSystemReady || !mUserManager.isUserUnlockingOrUnlocked(newUserId);
1045         mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
1046         updateCurrentProfileIds();
1047         // InputMethodFileManager should be reset when the user is changed
1048         mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
1049         final String defaultImiId = mSettings.getSelectedInputMethod();
1050
1051         if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
1052                 + " defaultImiId=" + defaultImiId);
1053
1054         // For secondary users, the list of enabled IMEs may not have been updated since the
1055         // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
1056         // not be empty even if the IME has been uninstalled by the primary user.
1057         // Even in such cases, IMMS works fine because it will find the most applicable
1058         // IME for that user.
1059         final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
1060         mLastSystemLocales = mRes.getConfiguration().getLocales();
1061
1062         // TODO: Is it really possible that switchUserLocked() happens before system ready?
1063         if (mSystemReady) {
1064             hideCurrentInputLocked(0, null);
1065             resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_USER);
1066             buildInputMethodListLocked(initialUserSwitch);
1067             if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
1068                 // This is the first time of the user switch and
1069                 // set the current ime to the proper one.
1070                 resetDefaultImeLocked(mContext);
1071             }
1072             updateFromSettingsLocked(true);
1073             try {
1074                 startInputInnerLocked();
1075             } catch (RuntimeException e) {
1076                 Slog.w(TAG, "Unexpected exception", e);
1077             }
1078         }
1079
1080         if (initialUserSwitch) {
1081             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1082                     mSettings.getEnabledInputMethodListLocked(), newUserId,
1083                     mContext.getBasePackageName());
1084         }
1085
1086         if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
1087                 + " selectedIme=" + mSettings.getSelectedInputMethod());
1088     }
1089
1090     void updateCurrentProfileIds() {
1091         mSettings.setCurrentProfileIds(
1092                 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
1093     }
1094
1095     @Override
1096     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
1097             throws RemoteException {
1098         try {
1099             return super.onTransact(code, data, reply, flags);
1100         } catch (RuntimeException e) {
1101             // The input method manager only throws security exceptions, so let's
1102             // log all others.
1103             if (!(e instanceof SecurityException)) {
1104                 Slog.wtf(TAG, "Input Method Manager Crash", e);
1105             }
1106             throw e;
1107         }
1108     }
1109
1110     public void systemRunning(StatusBarManagerService statusBar) {
1111         synchronized (mMethodMap) {
1112             if (DEBUG) {
1113                 Slog.d(TAG, "--- systemReady");
1114             }
1115             if (!mSystemReady) {
1116                 mSystemReady = true;
1117                 mLastSystemLocales = mRes.getConfiguration().getLocales();
1118                 final int currentUserId = mSettings.getCurrentUserId();
1119                 mSettings.switchCurrentUser(currentUserId,
1120                         !mUserManager.isUserUnlockingOrUnlocked(currentUserId));
1121                 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
1122                 mNotificationManager = mContext.getSystemService(NotificationManager.class);
1123                 mStatusBar = statusBar;
1124                 if (mStatusBar != null) {
1125                     mStatusBar.setIconVisibility(mSlotIme, false);
1126                 }
1127                 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
1128                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
1129                         com.android.internal.R.bool.show_ongoing_ime_switcher);
1130                 if (mShowOngoingImeSwitcherForPhones) {
1131                     mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
1132                             mHardKeyboardListener);
1133                 }
1134
1135                 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
1136                 mSettingsObserver.registerContentObserverLocked(currentUserId);
1137
1138                 final IntentFilter broadcastFilter = new IntentFilter();
1139                 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1140                 broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
1141                 broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
1142                 broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
1143                 broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
1144                 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
1145
1146                 buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
1147                 resetDefaultImeLocked(mContext);
1148                 updateFromSettingsLocked(true);
1149                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1150                         mSettings.getEnabledInputMethodListLocked(), currentUserId,
1151                         mContext.getBasePackageName());
1152
1153                 try {
1154                     startInputInnerLocked();
1155                 } catch (RuntimeException e) {
1156                     Slog.w(TAG, "Unexpected exception", e);
1157                 }
1158             }
1159         }
1160     }
1161
1162     // ---------------------------------------------------------------------------------------
1163     // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
1164     // 1) it comes from the system process
1165     // 2) the calling process' user id is identical to the current user id IMMS thinks.
1166     private boolean calledFromValidUser() {
1167         final int uid = Binder.getCallingUid();
1168         final int userId = UserHandle.getUserId(uid);
1169         if (DEBUG) {
1170             Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
1171                     + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
1172                     + " calling userId = " + userId + ", foreground user id = "
1173                     + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
1174                     + InputMethodUtils.getApiCallStack());
1175         }
1176         if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
1177             return true;
1178         }
1179
1180         // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
1181         // foreground user, not for the user of that process. Accordingly InputMethodManagerService
1182         // must not manage background users' states in any functions.
1183         // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
1184         // by a token.
1185         if (mContext.checkCallingOrSelfPermission(
1186                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
1187                         == PackageManager.PERMISSION_GRANTED) {
1188             if (DEBUG) {
1189                 Slog.d(TAG, "--- Access granted because the calling process has "
1190                         + "the INTERACT_ACROSS_USERS_FULL permission");
1191             }
1192             return true;
1193         }
1194         // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1195         Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
1196                 + Debug.getCallers(10));
1197         return false;
1198     }
1199
1200
1201     /**
1202      * Returns true iff the caller is identified to be the current input method with the token.
1203      * @param token The window token given to the input method when it was started.
1204      * @return true if and only if non-null valid token is specified.
1205      */
1206     private boolean calledWithValidToken(@Nullable IBinder token) {
1207         if (token == null && Binder.getCallingPid() == Process.myPid()) {
1208             if (DEBUG) {
1209                 // TODO(b/34851776): Basically it's the caller's fault if we reach here.
1210                 Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
1211             }
1212             return false;
1213         }
1214         if (token == null || token != mCurToken) {
1215             // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1216             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
1217                     + " uid:" + Binder.getCallingUid() + " token:" + token);
1218             return false;
1219         }
1220         return true;
1221     }
1222
1223     private boolean bindCurrentInputMethodService(
1224             Intent service, ServiceConnection conn, int flags) {
1225         if (service == null || conn == null) {
1226             Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
1227             return false;
1228         }
1229         return mContext.bindServiceAsUser(service, conn, flags,
1230                 new UserHandle(mSettings.getCurrentUserId()));
1231     }
1232
1233     @Override
1234     public List<InputMethodInfo> getInputMethodList() {
1235         // TODO: Make this work even for non-current users?
1236         if (!calledFromValidUser()) {
1237             return Collections.emptyList();
1238         }
1239         synchronized (mMethodMap) {
1240             return new ArrayList<>(mMethodList);
1241         }
1242     }
1243
1244     @Override
1245     public List<InputMethodInfo> getEnabledInputMethodList() {
1246         // TODO: Make this work even for non-current users?
1247         if (!calledFromValidUser()) {
1248             return Collections.emptyList();
1249         }
1250         synchronized (mMethodMap) {
1251             return mSettings.getEnabledInputMethodListLocked();
1252         }
1253     }
1254
1255     /**
1256      * @param imiId if null, returns enabled subtypes for the current imi
1257      * @return enabled subtypes of the specified imi
1258      */
1259     @Override
1260     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1261             boolean allowsImplicitlySelectedSubtypes) {
1262         // TODO: Make this work even for non-current users?
1263         if (!calledFromValidUser()) {
1264             return Collections.emptyList();
1265         }
1266         synchronized (mMethodMap) {
1267             final InputMethodInfo imi;
1268             if (imiId == null && mCurMethodId != null) {
1269                 imi = mMethodMap.get(mCurMethodId);
1270             } else {
1271                 imi = mMethodMap.get(imiId);
1272             }
1273             if (imi == null) {
1274                 return Collections.emptyList();
1275             }
1276             return mSettings.getEnabledInputMethodSubtypeListLocked(
1277                     mContext, imi, allowsImplicitlySelectedSubtypes);
1278         }
1279     }
1280
1281     @Override
1282     public void addClient(IInputMethodClient client,
1283             IInputContext inputContext, int uid, int pid) {
1284         if (!calledFromValidUser()) {
1285             return;
1286         }
1287         synchronized (mMethodMap) {
1288             mClients.put(client.asBinder(), new ClientState(client,
1289                     inputContext, uid, pid));
1290         }
1291     }
1292
1293     @Override
1294     public void removeClient(IInputMethodClient client) {
1295         if (!calledFromValidUser()) {
1296             return;
1297         }
1298         synchronized (mMethodMap) {
1299             ClientState cs = mClients.remove(client.asBinder());
1300             if (cs != null) {
1301                 clearClientSessionLocked(cs);
1302                 if (mCurClient == cs) {
1303                     mCurClient = null;
1304                 }
1305                 if (mCurFocusedWindowClient == cs) {
1306                     mCurFocusedWindowClient = null;
1307                 }
1308             }
1309         }
1310     }
1311
1312     void executeOrSendMessage(IInterface target, Message msg) {
1313          if (target.asBinder() instanceof Binder) {
1314              mCaller.sendMessage(msg);
1315          } else {
1316              handleMessage(msg);
1317              msg.recycle();
1318          }
1319     }
1320
1321     void unbindCurrentClientLocked(
1322             /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1323         if (mCurClient != null) {
1324             if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
1325                     + mCurClient.client.asBinder());
1326             if (mBoundToMethod) {
1327                 mBoundToMethod = false;
1328                 if (mCurMethod != null) {
1329                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1330                             MSG_UNBIND_INPUT, mCurMethod));
1331                 }
1332             }
1333
1334             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1335                     MSG_SET_ACTIVE, 0, 0, mCurClient));
1336             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1337                     MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
1338             mCurClient.sessionRequested = false;
1339             mCurClient = null;
1340
1341             hideInputMethodMenuLocked();
1342         }
1343     }
1344
1345     private int getImeShowFlags() {
1346         int flags = 0;
1347         if (mShowForced) {
1348             flags |= InputMethod.SHOW_FORCED
1349                     | InputMethod.SHOW_EXPLICIT;
1350         } else if (mShowExplicitlyRequested) {
1351             flags |= InputMethod.SHOW_EXPLICIT;
1352         }
1353         return flags;
1354     }
1355
1356     private int getAppShowFlags() {
1357         int flags = 0;
1358         if (mShowForced) {
1359             flags |= InputMethodManager.SHOW_FORCED;
1360         } else if (!mShowExplicitlyRequested) {
1361             flags |= InputMethodManager.SHOW_IMPLICIT;
1362         }
1363         return flags;
1364     }
1365
1366     InputBindResult attachNewInputLocked(
1367             /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
1368         if (!mBoundToMethod) {
1369             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1370                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1371             mBoundToMethod = true;
1372         }
1373
1374         final Binder startInputToken = new Binder();
1375         final StartInputInfo info = new StartInputInfo(mCurToken, mCurFocusedWindow);
1376         mStartInputMap.put(startInputToken, info);
1377
1378         final SessionState session = mCurClient.curSession;
1379         executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
1380                 MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
1381                 startInputToken, session, mCurInputContext, mCurAttribute));
1382         if (mShowRequested) {
1383             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1384             showCurrentInputLocked(getAppShowFlags(), null);
1385         }
1386         return new InputBindResult(session.session,
1387                 (session.channel != null ? session.channel.dup() : null),
1388                 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
1389     }
1390
1391     InputBindResult startInputLocked(
1392             /* @InputMethodClient.StartInputReason */ final int startInputReason,
1393             IInputMethodClient client, IInputContext inputContext,
1394             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1395             @Nullable EditorInfo attribute, int controlFlags) {
1396         // If no method is currently selected, do nothing.
1397         if (mCurMethodId == null) {
1398             return mNoBinding;
1399         }
1400
1401         ClientState cs = mClients.get(client.asBinder());
1402         if (cs == null) {
1403             throw new IllegalArgumentException("unknown client "
1404                     + client.asBinder());
1405         }
1406
1407         if (attribute == null) {
1408             Slog.w(TAG, "Ignoring startInput with null EditorInfo."
1409                     + " uid=" + cs.uid + " pid=" + cs.pid);
1410             return null;
1411         }
1412
1413         try {
1414             if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1415                 // Check with the window manager to make sure this client actually
1416                 // has a window with focus.  If not, reject.  This is thread safe
1417                 // because if the focus changes some time before or after, the
1418                 // next client receiving focus that has any interest in input will
1419                 // be calling through here after that change happens.
1420                 if (DEBUG) {
1421                     Slog.w(TAG, "Starting input on non-focused client " + cs.client
1422                             + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1423                 }
1424                 return null;
1425             }
1426         } catch (RemoteException e) {
1427         }
1428
1429         return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
1430                 controlFlags, startInputReason);
1431     }
1432
1433     InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
1434             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1435             @NonNull EditorInfo attribute, int controlFlags,
1436             /* @InputMethodClient.StartInputReason */ final int startInputReason) {
1437         // If no method is currently selected, do nothing.
1438         if (mCurMethodId == null) {
1439             return mNoBinding;
1440         }
1441
1442         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
1443                 attribute.packageName)) {
1444             Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
1445                     + " uid=" + cs.uid + " package=" + attribute.packageName);
1446             return mNoBinding;
1447         }
1448
1449         if (mCurClient != cs) {
1450             // Was the keyguard locked when switching over to the new client?
1451             mCurClientInKeyguard = isKeyguardLocked();
1452             // If the client is changing, we need to switch over to the new
1453             // one.
1454             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
1455             if (DEBUG) Slog.v(TAG, "switching to client: client="
1456                     + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
1457
1458             // If the screen is on, inform the new client it is active
1459             if (mIsInteractive) {
1460                 executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
1461                         MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
1462             }
1463         }
1464
1465         // Bump up the sequence for this client and attach it.
1466         mCurSeq++;
1467         if (mCurSeq <= 0) mCurSeq = 1;
1468         mCurClient = cs;
1469         mCurInputContext = inputContext;
1470         mCurInputContextMissingMethods = missingMethods;
1471         mCurAttribute = attribute;
1472
1473         // Check if the input method is changing.
1474         if (mCurId != null && mCurId.equals(mCurMethodId)) {
1475             if (cs.curSession != null) {
1476                 // Fast case: if we are already connected to the input method,
1477                 // then just return it.
1478                 return attachNewInputLocked(startInputReason,
1479                         (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
1480             }
1481             if (mHaveConnection) {
1482                 if (mCurMethod != null) {
1483                     // Return to client, and we will get back with it when
1484                     // we have had a session made for it.
1485                     requestClientSessionLocked(cs);
1486                     return new InputBindResult(null, null, mCurId, mCurSeq,
1487                             mCurUserActionNotificationSequenceNumber);
1488                 } else if (SystemClock.uptimeMillis()
1489                         < (mLastBindTime+TIME_TO_RECONNECT)) {
1490                     // In this case we have connected to the service, but
1491                     // don't yet have its interface.  If it hasn't been too
1492                     // long since we did the connection, we'll return to
1493                     // the client and wait to get the service interface so
1494                     // we can report back.  If it has been too long, we want
1495                     // to fall through so we can try a disconnect/reconnect
1496                     // to see if we can get back in touch with the service.
1497                     return new InputBindResult(null, null, mCurId, mCurSeq,
1498                             mCurUserActionNotificationSequenceNumber);
1499                 } else {
1500                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1501                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1502                 }
1503             }
1504         }
1505
1506         return startInputInnerLocked();
1507     }
1508
1509     InputBindResult startInputInnerLocked() {
1510         if (mCurMethodId == null) {
1511             return mNoBinding;
1512         }
1513
1514         if (!mSystemReady) {
1515             // If the system is not yet ready, we shouldn't be running third
1516             // party code.
1517             return new InputBindResult(null, null, mCurMethodId, mCurSeq,
1518                     mCurUserActionNotificationSequenceNumber);
1519         }
1520
1521         InputMethodInfo info = mMethodMap.get(mCurMethodId);
1522         if (info == null) {
1523             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1524         }
1525
1526         unbindCurrentMethodLocked(true);
1527
1528         mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1529         mCurIntent.setComponent(info.getComponent());
1530         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1531                 com.android.internal.R.string.input_method_binding_label);
1532         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1533                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1534         if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1535                 | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND
1536                 | Context.BIND_SHOWING_UI)) {
1537             mLastBindTime = SystemClock.uptimeMillis();
1538             mHaveConnection = true;
1539             mCurId = info.getId();
1540             mCurToken = new Binder();
1541             try {
1542                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1543                 mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
1544             } catch (RemoteException e) {
1545             }
1546             return new InputBindResult(null, null, mCurId, mCurSeq,
1547                     mCurUserActionNotificationSequenceNumber);
1548         } else {
1549             mCurIntent = null;
1550             Slog.w(TAG, "Failure connecting to input method service: "
1551                     + mCurIntent);
1552         }
1553         return null;
1554     }
1555
1556     private InputBindResult startInput(
1557             /* @InputMethodClient.StartInputReason */ final int startInputReason,
1558             IInputMethodClient client, IInputContext inputContext,
1559             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1560             @Nullable EditorInfo attribute, int controlFlags) {
1561         if (!calledFromValidUser()) {
1562             return null;
1563         }
1564         synchronized (mMethodMap) {
1565             if (DEBUG) {
1566                 Slog.v(TAG, "startInput: reason="
1567                         + InputMethodClient.getStartInputReason(startInputReason)
1568                         + " client = " + client.asBinder()
1569                         + " inputContext=" + inputContext
1570                         + " missingMethods="
1571                         + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
1572                         + " attribute=" + attribute
1573                         + " controlFlags=#" + Integer.toHexString(controlFlags));
1574             }
1575             final long ident = Binder.clearCallingIdentity();
1576             try {
1577                 return startInputLocked(startInputReason, client, inputContext, missingMethods,
1578                         attribute, controlFlags);
1579             } finally {
1580                 Binder.restoreCallingIdentity(ident);
1581             }
1582         }
1583     }
1584
1585     @Override
1586     public void finishInput(IInputMethodClient client) {
1587     }
1588
1589     @Override
1590     public void onServiceConnected(ComponentName name, IBinder service) {
1591         synchronized (mMethodMap) {
1592             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
1593                 mCurMethod = IInputMethod.Stub.asInterface(service);
1594                 if (mCurToken == null) {
1595                     Slog.w(TAG, "Service connected without a token!");
1596                     unbindCurrentMethodLocked(false);
1597                     return;
1598                 }
1599                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
1600                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1601                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
1602                 if (mCurClient != null) {
1603                     clearClientSessionLocked(mCurClient);
1604                     requestClientSessionLocked(mCurClient);
1605                 }
1606             }
1607         }
1608     }
1609
1610     void onSessionCreated(IInputMethod method, IInputMethodSession session,
1611             InputChannel channel) {
1612         synchronized (mMethodMap) {
1613             if (mCurMethod != null && method != null
1614                     && mCurMethod.asBinder() == method.asBinder()) {
1615                 if (mCurClient != null) {
1616                     clearClientSessionLocked(mCurClient);
1617                     mCurClient.curSession = new SessionState(mCurClient,
1618                             method, session, channel);
1619                     InputBindResult res = attachNewInputLocked(
1620                             InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
1621                     if (res.method != null) {
1622                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
1623                                 MSG_BIND_CLIENT, mCurClient.client, res));
1624                     }
1625                     return;
1626                 }
1627             }
1628         }
1629
1630         // Session abandoned.  Close its associated input channel.
1631         channel.dispose();
1632     }
1633
1634     void unbindCurrentMethodLocked(boolean savePosition) {
1635         if (mVisibleBound) {
1636             mContext.unbindService(mVisibleConnection);
1637             mVisibleBound = false;
1638         }
1639
1640         if (mHaveConnection) {
1641             mContext.unbindService(this);
1642             mHaveConnection = false;
1643         }
1644
1645         if (mCurToken != null) {
1646             try {
1647                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
1648                 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
1649                     // The current IME is shown. Hence an IME switch (transition) is happening.
1650                     mWindowManagerInternal.saveLastInputMethodWindowForTransition();
1651                 }
1652                 mIWindowManager.removeWindowToken(mCurToken, DEFAULT_DISPLAY);
1653             } catch (RemoteException e) {
1654             }
1655             mCurToken = null;
1656         }
1657
1658         mCurId = null;
1659         clearCurMethodLocked();
1660     }
1661
1662     void resetCurrentMethodAndClient(
1663             /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1664         mCurMethodId = null;
1665         unbindCurrentMethodLocked(false);
1666         unbindCurrentClientLocked(unbindClientReason);
1667     }
1668
1669     void requestClientSessionLocked(ClientState cs) {
1670         if (!cs.sessionRequested) {
1671             if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
1672             InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
1673             cs.sessionRequested = true;
1674             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
1675                     MSG_CREATE_SESSION, mCurMethod, channels[1],
1676                     new MethodCallback(this, mCurMethod, channels[0])));
1677         }
1678     }
1679
1680     void clearClientSessionLocked(ClientState cs) {
1681         finishSessionLocked(cs.curSession);
1682         cs.curSession = null;
1683         cs.sessionRequested = false;
1684     }
1685
1686     private void finishSessionLocked(SessionState sessionState) {
1687         if (sessionState != null) {
1688             if (sessionState.session != null) {
1689                 try {
1690                     sessionState.session.finishSession();
1691                 } catch (RemoteException e) {
1692                     Slog.w(TAG, "Session failed to close due to remote exception", e);
1693                     updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
1694                 }
1695                 sessionState.session = null;
1696             }
1697             if (sessionState.channel != null) {
1698                 sessionState.channel.dispose();
1699                 sessionState.channel = null;
1700             }
1701         }
1702     }
1703
1704     void clearCurMethodLocked() {
1705         if (mCurMethod != null) {
1706             for (ClientState cs : mClients.values()) {
1707                 clearClientSessionLocked(cs);
1708             }
1709
1710             finishSessionLocked(mEnabledSession);
1711             mEnabledSession = null;
1712             mCurMethod = null;
1713         }
1714         if (mStatusBar != null) {
1715             mStatusBar.setIconVisibility(mSlotIme, false);
1716         }
1717         mInFullscreenMode = false;
1718     }
1719
1720     @Override
1721     public void onServiceDisconnected(ComponentName name) {
1722         // Note that mContext.unbindService(this) does not trigger this.  Hence if we are here the
1723         // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed),
1724         // which is irregular but can eventually happen for everyone just by continuing using the
1725         // device.  Thus it is important to make sure that all the internal states are properly
1726         // refreshed when this method is called back.  Running
1727         //    adb install -r <APK that implements the current IME>
1728         // would be a good way to trigger such a situation.
1729         synchronized (mMethodMap) {
1730             if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1731                     + " mCurIntent=" + mCurIntent);
1732             if (mCurMethod != null && mCurIntent != null
1733                     && name.equals(mCurIntent.getComponent())) {
1734                 clearCurMethodLocked();
1735                 // We consider this to be a new bind attempt, since the system
1736                 // should now try to restart the service for us.
1737                 mLastBindTime = SystemClock.uptimeMillis();
1738                 mShowRequested = mInputShown;
1739                 mInputShown = false;
1740                 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_DISCONNECT_IME);
1741             }
1742         }
1743     }
1744
1745     @Override
1746     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1747         synchronized (mMethodMap) {
1748             if (!calledWithValidToken(token)) {
1749                 return;
1750             }
1751             final long ident = Binder.clearCallingIdentity();
1752             try {
1753                 if (iconId == 0) {
1754                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1755                     if (mStatusBar != null) {
1756                         mStatusBar.setIconVisibility(mSlotIme, false);
1757                     }
1758                 } else if (packageName != null) {
1759                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1760                     CharSequence contentDescription = null;
1761                     try {
1762                         // Use PackageManager to load label
1763                         final PackageManager packageManager = mContext.getPackageManager();
1764                         contentDescription = packageManager.getApplicationLabel(
1765                                 mIPackageManager.getApplicationInfo(packageName, 0,
1766                                         mSettings.getCurrentUserId()));
1767                     } catch (RemoteException e) {
1768                         /* ignore */
1769                     }
1770                     if (mStatusBar != null) {
1771                         mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
1772                                 contentDescription  != null
1773                                         ? contentDescription.toString() : null);
1774                         mStatusBar.setIconVisibility(mSlotIme, true);
1775                     }
1776                 }
1777             } finally {
1778                 Binder.restoreCallingIdentity(ident);
1779             }
1780         }
1781     }
1782
1783     private boolean shouldShowImeSwitcherLocked(int visibility) {
1784         if (!mShowOngoingImeSwitcherForPhones) return false;
1785         if (mSwitchingDialog != null) return false;
1786         if (isScreenLocked()) return false;
1787         if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
1788         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
1789             if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
1790                 // When physical keyboard is attached, we show the ime switcher (or notification if
1791                 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
1792                 // exists in the IME switcher dialog.  Might be OK to remove this condition once
1793                 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
1794                 return true;
1795             }
1796         } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
1797             return false;
1798         }
1799
1800         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1801         final int N = imis.size();
1802         if (N > 2) return true;
1803         if (N < 1) return false;
1804         int nonAuxCount = 0;
1805         int auxCount = 0;
1806         InputMethodSubtype nonAuxSubtype = null;
1807         InputMethodSubtype auxSubtype = null;
1808         for(int i = 0; i < N; ++i) {
1809             final InputMethodInfo imi = imis.get(i);
1810             final List<InputMethodSubtype> subtypes =
1811                     mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
1812             final int subtypeCount = subtypes.size();
1813             if (subtypeCount == 0) {
1814                 ++nonAuxCount;
1815             } else {
1816                 for (int j = 0; j < subtypeCount; ++j) {
1817                     final InputMethodSubtype subtype = subtypes.get(j);
1818                     if (!subtype.isAuxiliary()) {
1819                         ++nonAuxCount;
1820                         nonAuxSubtype = subtype;
1821                     } else {
1822                         ++auxCount;
1823                         auxSubtype = subtype;
1824                     }
1825                 }
1826             }
1827         }
1828         if (nonAuxCount > 1 || auxCount > 1) {
1829             return true;
1830         } else if (nonAuxCount == 1 && auxCount == 1) {
1831             if (nonAuxSubtype != null && auxSubtype != null
1832                     && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1833                             || auxSubtype.overridesImplicitlyEnabledSubtype()
1834                             || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1835                     && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1836                 return false;
1837             }
1838             return true;
1839         }
1840         return false;
1841     }
1842
1843     private boolean isKeyguardLocked() {
1844         return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1845     }
1846
1847     @SuppressWarnings("deprecation")
1848     @Override
1849     public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
1850             int backDisposition) {
1851         if (startInputToken == null) {
1852             throw new InvalidParameterException("startInputToken cannot be null");
1853         }
1854
1855         if (!calledWithValidToken(token)) {
1856             return;
1857         }
1858
1859         synchronized (mMethodMap) {
1860             final StartInputInfo info = mStartInputMap.get(startInputToken);
1861             if (info == null) {
1862                 throw new InvalidParameterException("Unknown startInputToken=" + startInputToken);
1863             }
1864             mImeWindowVis = vis;
1865             mBackDisposition = backDisposition;
1866             updateSystemUiLocked(token, vis, backDisposition);
1867         }
1868     }
1869
1870     private void updateSystemUi(IBinder token, int vis, int backDisposition) {
1871         synchronized (mMethodMap) {
1872             updateSystemUiLocked(token, vis, backDisposition);
1873         }
1874     }
1875
1876     // Caution! This method is called in this class. Handle multi-user carefully
1877     private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
1878         if (!calledWithValidToken(token)) {
1879             return;
1880         }
1881
1882         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
1883         // all updateSystemUi happens on system previlege.
1884         final long ident = Binder.clearCallingIdentity();
1885         try {
1886             // apply policy for binder calls
1887             if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
1888                 vis = 0;
1889             }
1890             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
1891             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
1892             if (mStatusBar != null) {
1893                 mStatusBar.setImeWindowStatus(token, vis, backDisposition,
1894                         needsToShowImeSwitcher);
1895             }
1896             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1897             if (imi != null && needsToShowImeSwitcher) {
1898                 // Used to load label
1899                 final CharSequence title = mRes.getText(
1900                         com.android.internal.R.string.select_input_method);
1901                 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
1902                         mContext, imi, mCurrentSubtype);
1903                 mImeSwitcherNotification.setContentTitle(title)
1904                         .setContentText(summary)
1905                         .setContentIntent(mImeSwitchPendingIntent);
1906                 try {
1907                     if ((mNotificationManager != null)
1908                             && !mIWindowManager.hasNavigationBar()) {
1909                         if (DEBUG) {
1910                             Slog.d(TAG, "--- show notification: label =  " + summary);
1911                         }
1912                         mNotificationManager.notifyAsUser(null,
1913                                 com.android.internal.R.string.select_input_method,
1914                                 mImeSwitcherNotification.build(), UserHandle.ALL);
1915                         mNotificationShown = true;
1916                     }
1917                 } catch (RemoteException e) {
1918                 }
1919             } else {
1920                 if (mNotificationShown && mNotificationManager != null) {
1921                     if (DEBUG) {
1922                         Slog.d(TAG, "--- hide notification");
1923                     }
1924                     mNotificationManager.cancelAsUser(null,
1925                             com.android.internal.R.string.select_input_method, UserHandle.ALL);
1926                     mNotificationShown = false;
1927                 }
1928             }
1929         } finally {
1930             Binder.restoreCallingIdentity(ident);
1931         }
1932     }
1933
1934     @Override
1935     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1936         if (!calledFromValidUser()) {
1937             return;
1938         }
1939         synchronized (mMethodMap) {
1940             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1941             for (int i = 0; i < spans.length; ++i) {
1942                 SuggestionSpan ss = spans[i];
1943                 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1944                     mSecureSuggestionSpans.put(ss, currentImi);
1945                 }
1946             }
1947         }
1948     }
1949
1950     @Override
1951     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1952         if (!calledFromValidUser()) {
1953             return false;
1954         }
1955         synchronized (mMethodMap) {
1956             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1957             // TODO: Do not send the intent if the process of the targetImi is already dead.
1958             if (targetImi != null) {
1959                 final String[] suggestions = span.getSuggestions();
1960                 if (index < 0 || index >= suggestions.length) return false;
1961                 final String className = span.getNotificationTargetClassName();
1962                 final Intent intent = new Intent();
1963                 // Ensures that only a class in the original IME package will receive the
1964                 // notification.
1965                 intent.setClassName(targetImi.getPackageName(), className);
1966                 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1967                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1968                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1969                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1970                 final long ident = Binder.clearCallingIdentity();
1971                 try {
1972                     mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1973                 } finally {
1974                     Binder.restoreCallingIdentity(ident);
1975                 }
1976                 return true;
1977             }
1978         }
1979         return false;
1980     }
1981
1982     void updateFromSettingsLocked(boolean enabledMayChange) {
1983         updateInputMethodsFromSettingsLocked(enabledMayChange);
1984         updateKeyboardFromSettingsLocked();
1985     }
1986
1987     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
1988         if (enabledMayChange) {
1989             List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1990             for (int i=0; i<enabled.size(); i++) {
1991                 // We allow the user to select "disabled until used" apps, so if they
1992                 // are enabling one of those here we now need to make it enabled.
1993                 InputMethodInfo imm = enabled.get(i);
1994                 try {
1995                     ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
1996                             PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
1997                             mSettings.getCurrentUserId());
1998                     if (ai != null && ai.enabledSetting
1999                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
2000                         if (DEBUG) {
2001                             Slog.d(TAG, "Update state(" + imm.getId()
2002                                     + "): DISABLED_UNTIL_USED -> DEFAULT");
2003                         }
2004                         mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
2005                                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
2006                                 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
2007                                 mContext.getBasePackageName());
2008                     }
2009                 } catch (RemoteException e) {
2010                 }
2011             }
2012         }
2013         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
2014         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
2015         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
2016         // enabled.
2017         String id = mSettings.getSelectedInputMethod();
2018         // There is no input method selected, try to choose new applicable input method.
2019         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
2020             id = mSettings.getSelectedInputMethod();
2021         }
2022         if (!TextUtils.isEmpty(id)) {
2023             try {
2024                 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
2025             } catch (IllegalArgumentException e) {
2026                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
2027                 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
2028             }
2029             mShortcutInputMethodsAndSubtypes.clear();
2030         } else {
2031             // There is no longer an input method set, so stop any current one.
2032             resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
2033         }
2034         // Here is not the perfect place to reset the switching controller. Ideally
2035         // mSwitchingController and mSettings should be able to share the same state.
2036         // TODO: Make sure that mSwitchingController and mSettings are sharing the
2037         // the same enabled IMEs list.
2038         mSwitchingController.resetCircularListLocked(mContext);
2039
2040     }
2041
2042     public void updateKeyboardFromSettingsLocked() {
2043         mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
2044         if (mSwitchingDialog != null
2045                 && mSwitchingDialogTitleView != null
2046                 && mSwitchingDialog.isShowing()) {
2047             final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
2048                     com.android.internal.R.id.hard_keyboard_switch);
2049             hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
2050         }
2051     }
2052
2053     private void notifyInputMethodSubtypeChanged(final int userId,
2054             @Nullable final InputMethodInfo inputMethodInfo,
2055             @Nullable final InputMethodSubtype subtype) {
2056         final InputManagerInternal inputManagerInternal =
2057                 LocalServices.getService(InputManagerInternal.class);
2058         if (inputManagerInternal != null) {
2059             inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
2060         }
2061     }
2062
2063     /* package */ void setInputMethodLocked(String id, int subtypeId) {
2064         InputMethodInfo info = mMethodMap.get(id);
2065         if (info == null) {
2066             throw new IllegalArgumentException("Unknown id: " + id);
2067         }
2068
2069         // See if we need to notify a subtype change within the same IME.
2070         if (id.equals(mCurMethodId)) {
2071             final int subtypeCount = info.getSubtypeCount();
2072             if (subtypeCount <= 0) {
2073                 return;
2074             }
2075             final InputMethodSubtype oldSubtype = mCurrentSubtype;
2076             final InputMethodSubtype newSubtype;
2077             if (subtypeId >= 0 && subtypeId < subtypeCount) {
2078                 newSubtype = info.getSubtypeAt(subtypeId);
2079             } else {
2080                 // If subtype is null, try to find the most applicable one from
2081                 // getCurrentInputMethodSubtype.
2082                 newSubtype = getCurrentInputMethodSubtypeLocked();
2083             }
2084             if (newSubtype == null || oldSubtype == null) {
2085                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
2086                         + ", new subtype = " + newSubtype);
2087                 return;
2088             }
2089             if (newSubtype != oldSubtype) {
2090                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
2091                 if (mCurMethod != null) {
2092                     try {
2093                         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
2094                         mCurMethod.changeInputMethodSubtype(newSubtype);
2095                     } catch (RemoteException e) {
2096                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
2097                         return;
2098                     }
2099                 }
2100                 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
2101             }
2102             return;
2103         }
2104
2105         // Changing to a different IME.
2106         final long ident = Binder.clearCallingIdentity();
2107         try {
2108             // Set a subtype to this input method.
2109             // subtypeId the name of a subtype which will be set.
2110             setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
2111             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
2112             // because mCurMethodId is stored as a history in
2113             // setSelectedInputMethodAndSubtypeLocked().
2114             mCurMethodId = id;
2115
2116             if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
2117                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
2118                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2119                 intent.putExtra("input_method_id", id);
2120                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2121             }
2122             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
2123         } finally {
2124             Binder.restoreCallingIdentity(ident);
2125         }
2126
2127         notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
2128                 getCurrentInputMethodSubtypeLocked());
2129     }
2130
2131     @Override
2132     public boolean showSoftInput(IInputMethodClient client, int flags,
2133             ResultReceiver resultReceiver) {
2134         if (!calledFromValidUser()) {
2135             return false;
2136         }
2137         int uid = Binder.getCallingUid();
2138         long ident = Binder.clearCallingIdentity();
2139         try {
2140             synchronized (mMethodMap) {
2141                 if (mCurClient == null || client == null
2142                         || mCurClient.client.asBinder() != client.asBinder()) {
2143                     try {
2144                         // We need to check if this is the current client with
2145                         // focus in the window manager, to allow this call to
2146                         // be made before input is started in it.
2147                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2148                             Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
2149                             return false;
2150                         }
2151                     } catch (RemoteException e) {
2152                         return false;
2153                     }
2154                 }
2155
2156                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
2157                 return showCurrentInputLocked(flags, resultReceiver);
2158             }
2159         } finally {
2160             Binder.restoreCallingIdentity(ident);
2161         }
2162     }
2163
2164     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2165         mShowRequested = true;
2166         if (mAccessibilityRequestingNoSoftKeyboard) {
2167             return false;
2168         }
2169
2170         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
2171             mShowExplicitlyRequested = true;
2172             mShowForced = true;
2173         } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
2174             mShowExplicitlyRequested = true;
2175         }
2176
2177         if (!mSystemReady) {
2178             return false;
2179         }
2180
2181         boolean res = false;
2182         if (mCurMethod != null) {
2183             if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
2184             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
2185                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
2186                     resultReceiver));
2187             mInputShown = true;
2188             if (mHaveConnection && !mVisibleBound) {
2189                 bindCurrentInputMethodService(
2190                         mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
2191                                 | Context.BIND_TREAT_LIKE_ACTIVITY
2192                                 | Context.BIND_FOREGROUND_SERVICE);
2193                 mVisibleBound = true;
2194             }
2195             res = true;
2196         } else if (mHaveConnection && SystemClock.uptimeMillis()
2197                 >= (mLastBindTime+TIME_TO_RECONNECT)) {
2198             // The client has asked to have the input method shown, but
2199             // we have been sitting here too long with a connection to the
2200             // service and no interface received, so let's disconnect/connect
2201             // to try to prod things along.
2202             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
2203                     SystemClock.uptimeMillis()-mLastBindTime,1);
2204             Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
2205             mContext.unbindService(this);
2206             bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
2207                     | Context.BIND_NOT_VISIBLE);
2208         } else {
2209             if (DEBUG) {
2210                 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
2211                         + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
2212             }
2213         }
2214
2215         return res;
2216     }
2217
2218     @Override
2219     public boolean hideSoftInput(IInputMethodClient client, int flags,
2220             ResultReceiver resultReceiver) {
2221         if (!calledFromValidUser()) {
2222             return false;
2223         }
2224         int uid = Binder.getCallingUid();
2225         long ident = Binder.clearCallingIdentity();
2226         try {
2227             synchronized (mMethodMap) {
2228                 if (mCurClient == null || client == null
2229                         || mCurClient.client.asBinder() != client.asBinder()) {
2230                     try {
2231                         // We need to check if this is the current client with
2232                         // focus in the window manager, to allow this call to
2233                         // be made before input is started in it.
2234                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2235                             if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
2236                                     + uid + ": " + client);
2237                             return false;
2238                         }
2239                     } catch (RemoteException e) {
2240                         return false;
2241                     }
2242                 }
2243
2244                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
2245                 return hideCurrentInputLocked(flags, resultReceiver);
2246             }
2247         } finally {
2248             Binder.restoreCallingIdentity(ident);
2249         }
2250     }
2251
2252     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2253         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
2254                 && (mShowExplicitlyRequested || mShowForced)) {
2255             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
2256             return false;
2257         }
2258         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
2259             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
2260             return false;
2261         }
2262
2263         // There is a chance that IMM#hideSoftInput() is called in a transient state where
2264         // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
2265         // to be updated with the new value sent from IME process.  Even in such a transient state
2266         // historically we have accepted an incoming call of IMM#hideSoftInput() from the
2267         // application process as a valid request, and have even promised such a behavior with CTS
2268         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
2269         // IMMS#InputShown indicates that the software keyboard is shown.
2270         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
2271         final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
2272                 (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
2273         boolean res;
2274         if (shouldHideSoftInput) {
2275             // The IME will report its visible state again after the following message finally
2276             // delivered to the IME process as an IPC.  Hence the inconsistency between
2277             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
2278             // the final state.
2279             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2280                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
2281             res = true;
2282         } else {
2283             res = false;
2284         }
2285         if (mHaveConnection && mVisibleBound) {
2286             mContext.unbindService(mVisibleConnection);
2287             mVisibleBound = false;
2288         }
2289         mInputShown = false;
2290         mShowRequested = false;
2291         mShowExplicitlyRequested = false;
2292         mShowForced = false;
2293         return res;
2294     }
2295
2296     @Override
2297     public InputBindResult startInputOrWindowGainedFocus(
2298             /* @InputMethodClient.StartInputReason */ final int startInputReason,
2299             IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2300             int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
2301             /* @InputConnectionInspector.missingMethods */ final int missingMethods) {
2302         if (windowToken != null) {
2303             return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
2304                     softInputMode, windowFlags, attribute, inputContext, missingMethods);
2305         } else {
2306             return startInput(startInputReason, client, inputContext, missingMethods, attribute,
2307                     controlFlags);
2308         }
2309     }
2310
2311     private InputBindResult windowGainedFocus(
2312             /* @InputMethodClient.StartInputReason */ final int startInputReason,
2313             IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2314             int windowFlags, EditorInfo attribute, IInputContext inputContext,
2315             /* @InputConnectionInspector.missingMethods */  final int missingMethods) {
2316         // Needs to check the validity before clearing calling identity
2317         final boolean calledFromValidUser = calledFromValidUser();
2318         InputBindResult res = null;
2319         long ident = Binder.clearCallingIdentity();
2320         try {
2321             synchronized (mMethodMap) {
2322                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
2323                         + InputMethodClient.getStartInputReason(startInputReason)
2324                         + " client=" + client.asBinder()
2325                         + " inputContext=" + inputContext
2326                         + " missingMethods="
2327                         + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
2328                         + " attribute=" + attribute
2329                         + " controlFlags=#" + Integer.toHexString(controlFlags)
2330                         + " softInputMode=#" + Integer.toHexString(softInputMode)
2331                         + " windowFlags=#" + Integer.toHexString(windowFlags));
2332
2333                 ClientState cs = mClients.get(client.asBinder());
2334                 if (cs == null) {
2335                     throw new IllegalArgumentException("unknown client "
2336                             + client.asBinder());
2337                 }
2338
2339                 try {
2340                     if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
2341                         // Check with the window manager to make sure this client actually
2342                         // has a window with focus.  If not, reject.  This is thread safe
2343                         // because if the focus changes some time before or after, the
2344                         // next client receiving focus that has any interest in input will
2345                         // be calling through here after that change happens.
2346                         if (DEBUG) {
2347                             Slog.w(TAG, "Focus gain on non-focused client " + cs.client
2348                                     + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
2349                         }
2350                         return null;
2351                     }
2352                 } catch (RemoteException e) {
2353                 }
2354
2355                 if (!calledFromValidUser) {
2356                     Slog.w(TAG, "A background user is requesting window. Hiding IME.");
2357                     Slog.w(TAG, "If you want to interect with IME, you need "
2358                             + "android.permission.INTERACT_ACROSS_USERS_FULL");
2359                     hideCurrentInputLocked(0, null);
2360                     return null;
2361                 }
2362
2363                 if (mCurFocusedWindow == windowToken) {
2364                     if (DEBUG) {
2365                         Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
2366                                 + " attribute=" + attribute + ", token = " + windowToken);
2367                     }
2368                     if (attribute != null) {
2369                         return startInputUncheckedLocked(cs, inputContext, missingMethods,
2370                                 attribute, controlFlags, startInputReason);
2371                     }
2372                     return null;
2373                 }
2374                 mCurFocusedWindow = windowToken;
2375                 mCurFocusedWindowClient = cs;
2376
2377                 // Should we auto-show the IME even if the caller has not
2378                 // specified what should be done with it?
2379                 // We only do this automatically if the window can resize
2380                 // to accommodate the IME (so what the user sees will give
2381                 // them good context without input information being obscured
2382                 // by the IME) or if running on a large screen where there
2383                 // is more room for the target window + IME.
2384                 final boolean doAutoShow =
2385                         (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
2386                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
2387                         || mRes.getConfiguration().isLayoutSizeAtLeast(
2388                                 Configuration.SCREENLAYOUT_SIZE_LARGE);
2389                 final boolean isTextEditor =
2390                         (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
2391
2392                 // We want to start input before showing the IME, but after closing
2393                 // it.  We want to do this after closing it to help the IME disappear
2394                 // more quickly (not get stuck behind it initializing itself for the
2395                 // new focused input, even if its window wants to hide the IME).
2396                 boolean didStart = false;
2397
2398                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
2399                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
2400                         if (!isTextEditor || !doAutoShow) {
2401                             if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
2402                                 // There is no focus view, and this window will
2403                                 // be behind any soft input window, so hide the
2404                                 // soft input window if it is shown.
2405                                 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
2406                                 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
2407                             }
2408                         } else if (isTextEditor && doAutoShow && (softInputMode &
2409                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2410                             // There is a focus view, and we are navigating forward
2411                             // into the window, so show the input window for the user.
2412                             // We only do this automatically if the window can resize
2413                             // to accommodate the IME (so what the user sees will give
2414                             // them good context without input information being obscured
2415                             // by the IME) or if running on a large screen where there
2416                             // is more room for the target window + IME.
2417                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
2418                             if (attribute != null) {
2419                                 res = startInputUncheckedLocked(cs, inputContext,
2420                                         missingMethods, attribute, controlFlags, startInputReason);
2421                                 didStart = true;
2422                             }
2423                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2424                         }
2425                         break;
2426                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2427                         // Do nothing.
2428                         break;
2429                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
2430                         if ((softInputMode &
2431                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2432                             if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
2433                             hideCurrentInputLocked(0, null);
2434                         }
2435                         break;
2436                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2437                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2438                         hideCurrentInputLocked(0, null);
2439                         break;
2440                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
2441                         if ((softInputMode &
2442                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2443                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2444                             if (attribute != null) {
2445                                 res = startInputUncheckedLocked(cs, inputContext,
2446                                         missingMethods, attribute, controlFlags, startInputReason);
2447                                 didStart = true;
2448                             }
2449                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2450                         }
2451                         break;
2452                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2453                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2454                         if (attribute != null) {
2455                             res = startInputUncheckedLocked(cs, inputContext, missingMethods,
2456                                     attribute, controlFlags, startInputReason);
2457                             didStart = true;
2458                         }
2459                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2460                         break;
2461                 }
2462
2463                 if (!didStart && attribute != null) {
2464                     res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
2465                             controlFlags, startInputReason);
2466                 }
2467             }
2468         } finally {
2469             Binder.restoreCallingIdentity(ident);
2470         }
2471
2472         return res;
2473     }
2474
2475     @Override
2476     public void showInputMethodPickerFromClient(
2477             IInputMethodClient client, int auxiliarySubtypeMode) {
2478         if (!calledFromValidUser()) {
2479             return;
2480         }
2481         synchronized (mMethodMap) {
2482             if (mCurClient == null || client == null
2483                     || mCurClient.client.asBinder() != client.asBinder()) {
2484                 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2485                         + Binder.getCallingUid() + ": " + client);
2486             }
2487
2488             // Always call subtype picker, because subtype picker is a superset of input method
2489             // picker.
2490             mHandler.sendMessage(mCaller.obtainMessageI(
2491                     MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
2492         }
2493     }
2494
2495     @Override
2496     public void setInputMethod(IBinder token, String id) {
2497         if (!calledFromValidUser()) {
2498             return;
2499         }
2500         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2501     }
2502
2503     @Override
2504     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2505         if (!calledFromValidUser()) {
2506             return;
2507         }
2508         synchronized (mMethodMap) {
2509             if (subtype != null) {
2510                 setInputMethodWithSubtypeIdLocked(token, id,
2511                         InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
2512                                 subtype.hashCode()));
2513             } else {
2514                 setInputMethod(token, id);
2515             }
2516         }
2517     }
2518
2519     @Override
2520     public void showInputMethodAndSubtypeEnablerFromClient(
2521             IInputMethodClient client, String inputMethodId) {
2522         if (!calledFromValidUser()) {
2523             return;
2524         }
2525         synchronized (mMethodMap) {
2526             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2527                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2528         }
2529     }
2530
2531     @Override
2532     public boolean switchToLastInputMethod(IBinder token) {
2533         if (!calledFromValidUser()) {
2534             return false;
2535         }
2536         synchronized (mMethodMap) {
2537             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2538             final InputMethodInfo lastImi;
2539             if (lastIme != null) {
2540                 lastImi = mMethodMap.get(lastIme.first);
2541             } else {
2542                 lastImi = null;
2543             }
2544             String targetLastImiId = null;
2545             int subtypeId = NOT_A_SUBTYPE_ID;
2546             if (lastIme != null && lastImi != null) {
2547                 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2548                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2549                 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2550                         : mCurrentSubtype.hashCode();
2551                 // If the last IME is the same as the current IME and the last subtype is not
2552                 // defined, there is no need to switch to the last IME.
2553                 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2554                     targetLastImiId = lastIme.first;
2555                     subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2556                 }
2557             }
2558
2559             if (TextUtils.isEmpty(targetLastImiId)
2560                     && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
2561                 // This is a safety net. If the currentSubtype can't be added to the history
2562                 // and the framework couldn't find the last ime, we will make the last ime be
2563                 // the most applicable enabled keyboard subtype of the system imes.
2564                 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2565                 if (enabled != null) {
2566                     final int N = enabled.size();
2567                     final String locale = mCurrentSubtype == null
2568                             ? mRes.getConfiguration().locale.toString()
2569                             : mCurrentSubtype.getLocale();
2570                     for (int i = 0; i < N; ++i) {
2571                         final InputMethodInfo imi = enabled.get(i);
2572                         if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
2573                             InputMethodSubtype keyboardSubtype =
2574                                     InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
2575                                             InputMethodUtils.getSubtypes(imi),
2576                                             InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
2577                             if (keyboardSubtype != null) {
2578                                 targetLastImiId = imi.getId();
2579                                 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2580                                         imi, keyboardSubtype.hashCode());
2581                                 if(keyboardSubtype.getLocale().equals(locale)) {
2582                                     break;
2583                                 }
2584                             }
2585                         }
2586                     }
2587                 }
2588             }
2589
2590             if (!TextUtils.isEmpty(targetLastImiId)) {
2591                 if (DEBUG) {
2592                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2593                             + ", from: " + mCurMethodId + ", " + subtypeId);
2594                 }
2595                 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
2596                 return true;
2597             } else {
2598                 return false;
2599             }
2600         }
2601     }
2602
2603     @Override
2604     public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2605         if (!calledFromValidUser()) {
2606             return false;
2607         }
2608         synchronized (mMethodMap) {
2609             if (!calledWithValidToken(token)) {
2610                 return false;
2611             }
2612             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2613                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2614                     true /* forward */);
2615             if (nextSubtype == null) {
2616                 return false;
2617             }
2618             setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
2619                     nextSubtype.mSubtypeId);
2620             return true;
2621         }
2622     }
2623
2624     @Override
2625     public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2626         if (!calledFromValidUser()) {
2627             return false;
2628         }
2629         synchronized (mMethodMap) {
2630             if (!calledWithValidToken(token)) {
2631                 return false;
2632             }
2633             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2634                     false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2635                     true /* forward */);
2636             if (nextSubtype == null) {
2637                 return false;
2638             }
2639             return true;
2640         }
2641     }
2642
2643     @Override
2644     public InputMethodSubtype getLastInputMethodSubtype() {
2645         if (!calledFromValidUser()) {
2646             return null;
2647         }
2648         synchronized (mMethodMap) {
2649             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2650             // TODO: Handle the case of the last IME with no subtypes
2651             if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2652                     || TextUtils.isEmpty(lastIme.second)) return null;
2653             final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2654             if (lastImi == null) return null;
2655             try {
2656                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2657                 final int lastSubtypeId =
2658                         InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2659                 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2660                     return null;
2661                 }
2662                 return lastImi.getSubtypeAt(lastSubtypeId);
2663             } catch (NumberFormatException e) {
2664                 return null;
2665             }
2666         }
2667     }
2668
2669     @Override
2670     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2671         if (!calledFromValidUser()) {
2672             return;
2673         }
2674         // By this IPC call, only a process which shares the same uid with the IME can add
2675         // additional input method subtypes to the IME.
2676         if (TextUtils.isEmpty(imiId) || subtypes == null) return;
2677         synchronized (mMethodMap) {
2678             if (!mSystemReady) {
2679                 return;
2680             }
2681             final InputMethodInfo imi = mMethodMap.get(imiId);
2682             if (imi == null) return;
2683             final String[] packageInfos;
2684             try {
2685                 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2686             } catch (RemoteException e) {
2687                 Slog.e(TAG, "Failed to get package infos");
2688                 return;
2689             }
2690             if (packageInfos != null) {
2691                 final int packageNum = packageInfos.length;
2692                 for (int i = 0; i < packageNum; ++i) {
2693                     if (packageInfos[i].equals(imi.getPackageName())) {
2694                         mFileManager.addInputMethodSubtypes(imi, subtypes);
2695                         final long ident = Binder.clearCallingIdentity();
2696                         try {
2697                             buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
2698                         } finally {
2699                             Binder.restoreCallingIdentity(ident);
2700                         }
2701                         return;
2702                     }
2703                 }
2704             }
2705         }
2706         return;
2707     }
2708
2709     @Override
2710     public int getInputMethodWindowVisibleHeight() {
2711         return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
2712     }
2713
2714     @Override
2715     public void clearLastInputMethodWindowForTransition(IBinder token) {
2716         if (!calledFromValidUser()) {
2717             return;
2718         }
2719         synchronized (mMethodMap) {
2720             if (!calledWithValidToken(token)) {
2721                 return;
2722             }
2723         }
2724         mWindowManagerInternal.clearLastInputMethodWindowForTransition();
2725     }
2726
2727     @Override
2728     public void notifyUserAction(int sequenceNumber) {
2729         if (DEBUG) {
2730             Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
2731         }
2732         synchronized (mMethodMap) {
2733             if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
2734                 if (DEBUG) {
2735                     Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
2736                             + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
2737                             + " actual: " + sequenceNumber);
2738                 }
2739                 return;
2740             }
2741             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2742             if (imi != null) {
2743                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
2744             }
2745         }
2746     }
2747
2748     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2749         synchronized (mMethodMap) {
2750             setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
2751         }
2752     }
2753
2754     private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
2755         if (token == null) {
2756             if (mContext.checkCallingOrSelfPermission(
2757                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
2758                     != PackageManager.PERMISSION_GRANTED) {
2759                 throw new SecurityException(
2760                         "Using null token requires permission "
2761                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2762             }
2763         } else if (mCurToken != token) {
2764             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2765                     + " token: " + token);
2766             return;
2767         }
2768
2769         final long ident = Binder.clearCallingIdentity();
2770         try {
2771             setInputMethodLocked(id, subtypeId);
2772         } finally {
2773             Binder.restoreCallingIdentity(ident);
2774         }
2775     }
2776
2777     @Override
2778     public void hideMySoftInput(IBinder token, int flags) {
2779         if (!calledFromValidUser()) {
2780             return;
2781         }
2782         synchronized (mMethodMap) {
2783             if (!calledWithValidToken(token)) {
2784                 return;
2785             }
2786             long ident = Binder.clearCallingIdentity();
2787             try {
2788                 hideCurrentInputLocked(flags, null);
2789             } finally {
2790                 Binder.restoreCallingIdentity(ident);
2791             }
2792         }
2793     }
2794
2795     @Override
2796     public void showMySoftInput(IBinder token, int flags) {
2797         if (!calledFromValidUser()) {
2798             return;
2799         }
2800         synchronized (mMethodMap) {
2801             if (!calledWithValidToken(token)) {
2802                 return;
2803             }
2804             long ident = Binder.clearCallingIdentity();
2805             try {
2806                 showCurrentInputLocked(flags, null);
2807             } finally {
2808                 Binder.restoreCallingIdentity(ident);
2809             }
2810         }
2811     }
2812
2813     void setEnabledSessionInMainThread(SessionState session) {
2814         if (mEnabledSession != session) {
2815             if (mEnabledSession != null && mEnabledSession.session != null) {
2816                 try {
2817                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2818                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
2819                 } catch (RemoteException e) {
2820                 }
2821             }
2822             mEnabledSession = session;
2823             if (mEnabledSession != null && mEnabledSession.session != null) {
2824                 try {
2825                     if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2826                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
2827                 } catch (RemoteException e) {
2828                 }
2829             }
2830         }
2831     }
2832
2833     @Override
2834     public boolean handleMessage(Message msg) {
2835         SomeArgs args;
2836         switch (msg.what) {
2837             case MSG_SHOW_IM_SUBTYPE_PICKER:
2838                 final boolean showAuxSubtypes;
2839                 switch (msg.arg1) {
2840                     case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
2841                         // This is undocumented so far, but IMM#showInputMethodPicker() has been
2842                         // implemented so that auxiliary subtypes will be excluded when the soft
2843                         // keyboard is invisible.
2844                         showAuxSubtypes = mInputShown;
2845                         break;
2846                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
2847                         showAuxSubtypes = true;
2848                         break;
2849                     case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
2850                         showAuxSubtypes = false;
2851                         break;
2852                     default:
2853                         Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
2854                         return false;
2855                 }
2856                 showInputMethodMenu(showAuxSubtypes);
2857                 return true;
2858
2859             case MSG_SHOW_IM_SUBTYPE_ENABLER:
2860                 showInputMethodAndSubtypeEnabler((String)msg.obj);
2861                 return true;
2862
2863             case MSG_SHOW_IM_CONFIG:
2864                 showConfigureInputMethods();
2865                 return true;
2866
2867             // ---------------------------------------------------------
2868
2869             case MSG_UNBIND_INPUT:
2870                 try {
2871                     ((IInputMethod)msg.obj).unbindInput();
2872                 } catch (RemoteException e) {
2873                     // There is nothing interesting about the method dying.
2874                 }
2875                 return true;
2876             case MSG_BIND_INPUT:
2877                 args = (SomeArgs)msg.obj;
2878                 try {
2879                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2880                 } catch (RemoteException e) {
2881                 }
2882                 args.recycle();
2883                 return true;
2884             case MSG_SHOW_SOFT_INPUT:
2885                 args = (SomeArgs)msg.obj;
2886                 try {
2887                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
2888                             + msg.arg1 + ", " + args.arg2 + ")");
2889                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
2890                 } catch (RemoteException e) {
2891                 }
2892                 args.recycle();
2893                 return true;
2894             case MSG_HIDE_SOFT_INPUT:
2895                 args = (SomeArgs)msg.obj;
2896                 try {
2897                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
2898                             + args.arg2 + ")");
2899                     ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
2900                 } catch (RemoteException e) {
2901                 }
2902                 args.recycle();
2903                 return true;
2904             case MSG_HIDE_CURRENT_INPUT_METHOD:
2905                 synchronized (mMethodMap) {
2906                     hideCurrentInputLocked(0, null);
2907                 }
2908                 return true;
2909             case MSG_ATTACH_TOKEN:
2910                 args = (SomeArgs)msg.obj;
2911                 try {
2912                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2913                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2914                 } catch (RemoteException e) {
2915                 }
2916                 args.recycle();
2917                 return true;
2918             case MSG_CREATE_SESSION: {
2919                 args = (SomeArgs)msg.obj;
2920                 IInputMethod method = (IInputMethod)args.arg1;
2921                 InputChannel channel = (InputChannel)args.arg2;
2922                 try {
2923                     method.createSession(channel, (IInputSessionCallback)args.arg3);
2924                 } catch (RemoteException e) {
2925                 } finally {
2926                     // Dispose the channel if the input method is not local to this process
2927                     // because the remote proxy will get its own copy when unparceled.
2928                     if (channel != null && Binder.isProxy(method)) {
2929                         channel.dispose();
2930                     }
2931                 }
2932                 args.recycle();
2933                 return true;
2934             }
2935             // ---------------------------------------------------------
2936
2937             case MSG_START_INPUT: {
2938                 final int missingMethods = msg.arg1;
2939                 final boolean restarting = msg.arg2 != 0;
2940                 args = (SomeArgs) msg.obj;
2941                 final IBinder startInputToken = (IBinder) args.arg1;
2942                 final SessionState session = (SessionState) args.arg2;
2943                 final IInputContext inputContext = (IInputContext) args.arg3;
2944                 final EditorInfo editorInfo = (EditorInfo) args.arg4;
2945                 try {
2946                     setEnabledSessionInMainThread(session);
2947                     session.method.startInput(startInputToken, inputContext, missingMethods,
2948                             editorInfo, restarting);
2949                 } catch (RemoteException e) {
2950                 }
2951                 args.recycle();
2952                 return true;
2953             }
2954
2955             // ---------------------------------------------------------
2956
2957             case MSG_UNBIND_CLIENT:
2958                 try {
2959                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
2960                 } catch (RemoteException e) {
2961                     // There is nothing interesting about the last client dying.
2962                 }
2963                 return true;
2964             case MSG_BIND_CLIENT: {
2965                 args = (SomeArgs)msg.obj;
2966                 IInputMethodClient client = (IInputMethodClient)args.arg1;
2967                 InputBindResult res = (InputBindResult)args.arg2;
2968                 try {
2969                     client.onBindMethod(res);
2970                 } catch (RemoteException e) {
2971                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
2972                 } finally {
2973                     // Dispose the channel if the input method is not local to this process
2974                     // because the remote proxy will get its own copy when unparceled.
2975                     if (res.channel != null && Binder.isProxy(client)) {
2976                         res.channel.dispose();
2977                     }
2978                 }
2979                 args.recycle();
2980                 return true;
2981             }
2982             case MSG_SET_ACTIVE:
2983                 try {
2984                     ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0);
2985                 } catch (RemoteException e) {
2986                     Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
2987                             + ((ClientState)msg.obj).pid + " uid "
2988                             + ((ClientState)msg.obj).uid);
2989                 }
2990                 return true;
2991             case MSG_SET_INTERACTIVE:
2992                 handleSetInteractive(msg.arg1 != 0);
2993                 return true;
2994             case MSG_SWITCH_IME:
2995                 handleSwitchInputMethod(msg.arg1 != 0);
2996                 return true;
2997             case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
2998                 final int sequenceNumber = msg.arg1;
2999                 final ClientState clientState = (ClientState)msg.obj;
3000                 try {
3001                     clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
3002                 } catch (RemoteException e) {
3003                     Slog.w(TAG, "Got RemoteException sending "
3004                             + "setUserActionNotificationSequenceNumber("
3005                             + sequenceNumber + ") notification to pid "
3006                             + clientState.pid + " uid "
3007                             + clientState.uid);
3008                 }
3009                 return true;
3010             }
3011             case MSG_REPORT_FULLSCREEN_MODE: {
3012                 final boolean fullscreen = msg.arg1 != 0;
3013                 final ClientState clientState = (ClientState)msg.obj;
3014                 try {
3015                     clientState.client.reportFullscreenMode(fullscreen);
3016                 } catch (RemoteException e) {
3017                     Slog.w(TAG, "Got RemoteException sending "
3018                             + "reportFullscreen(" + fullscreen + ") notification to pid="
3019                             + clientState.pid + " uid=" + clientState.uid);
3020                 }
3021                 return true;
3022             }
3023
3024             // --------------------------------------------------------------
3025             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
3026                 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
3027                 return true;
3028             case MSG_SYSTEM_UNLOCK_USER:
3029                 final int userId = msg.arg1;
3030                 onUnlockUser(userId);
3031                 return true;
3032         }
3033         return false;
3034     }
3035
3036     private void handleSetInteractive(final boolean interactive) {
3037         synchronized (mMethodMap) {
3038             mIsInteractive = interactive;
3039             updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
3040
3041             // Inform the current client of the change in active status
3042             if (mCurClient != null && mCurClient.client != null) {
3043                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
3044                         MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0,
3045                         mCurClient));
3046             }
3047         }
3048     }
3049
3050     private void handleSwitchInputMethod(final boolean forwardDirection) {
3051         synchronized (mMethodMap) {
3052             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
3053                     false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
3054             if (nextSubtype == null) {
3055                 return;
3056             }
3057             setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
3058             final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
3059             if (newInputMethodInfo == null) {
3060                 return;
3061             }
3062             final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
3063                     newInputMethodInfo, mCurrentSubtype);
3064             if (!TextUtils.isEmpty(toastText)) {
3065                 if (mSubtypeSwitchedByShortCutToast == null) {
3066                     mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText,
3067                             Toast.LENGTH_SHORT);
3068                 } else {
3069                     mSubtypeSwitchedByShortCutToast.setText(toastText);
3070                 }
3071                 mSubtypeSwitchedByShortCutToast.show();
3072             }
3073         }
3074     }
3075
3076     private boolean chooseNewDefaultIMELocked() {
3077         final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
3078                 mSettings.getEnabledInputMethodListLocked());
3079         if (imi != null) {
3080             if (DEBUG) {
3081                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
3082             }
3083             resetSelectedInputMethodAndSubtypeLocked(imi.getId());
3084             return true;
3085         }
3086
3087         return false;
3088     }
3089
3090     void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
3091         if (DEBUG) {
3092             Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
3093                     + " \n ------ caller=" + Debug.getCallers(10));
3094         }
3095         if (!mSystemReady) {
3096             Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
3097             return;
3098         }
3099         mMethodList.clear();
3100         mMethodMap.clear();
3101         mMethodMapUpdateCount++;
3102         mMyPackageMonitor.clearPackagesToMonitorComponentChangeLocked();
3103
3104         // Use for queryIntentServicesAsUser
3105         final PackageManager pm = mContext.getPackageManager();
3106
3107         // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
3108         // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
3109         // services depending on the unlock state for the specified user.
3110         final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
3111                 new Intent(InputMethod.SERVICE_INTERFACE),
3112                 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
3113                 mSettings.getCurrentUserId());
3114
3115         final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
3116                 mFileManager.getAllAdditionalInputMethodSubtypes();
3117         for (int i = 0; i < services.size(); ++i) {
3118             ResolveInfo ri = services.get(i);
3119             ServiceInfo si = ri.serviceInfo;
3120             final String imeId = InputMethodInfo.computeId(ri);
3121             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3122                 Slog.w(TAG, "Skipping input method " + imeId
3123                         + ": it does not require the permission "
3124                         + android.Manifest.permission.BIND_INPUT_METHOD);
3125                 continue;
3126             }
3127
3128             if (DEBUG) Slog.d(TAG, "Checking " + imeId);
3129
3130             final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
3131             try {
3132                 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
3133                 mMethodList.add(p);
3134                 final String id = p.getId();
3135                 mMethodMap.put(id, p);
3136
3137                 if (DEBUG) {
3138                     Slog.d(TAG, "Found an input method " + p);
3139                 }
3140             } catch (Exception e) {
3141                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
3142             }
3143         }
3144
3145         // Construct the set of possible IME packages for onPackageChanged() to avoid false
3146         // negatives when the package state remains to be the same but only the component state is
3147         // changed.
3148         {
3149             // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
3150             // of this query is to avoid false negatives.  PackageManager.MATCH_ALL could be more
3151             // conservative, but it seems we cannot use it for now (Issue 35176630).
3152             final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
3153                     new Intent(InputMethod.SERVICE_INTERFACE),
3154                     PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId());
3155             final int N = allInputMethodServices.size();
3156             for (int i = 0; i < N; ++i) {
3157                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
3158                 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3159                     continue;
3160                 }
3161                 mMyPackageMonitor.addPackageToMonitorComponentChangeLocked(si.packageName);
3162             }
3163         }
3164
3165         // TODO: The following code should find better place to live.
3166         if (!resetDefaultEnabledIme) {
3167             boolean enabledImeFound = false;
3168             final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
3169             final int N = enabledImes.size();
3170             for (int i = 0; i < N; ++i) {
3171                 final InputMethodInfo imi = enabledImes.get(i);
3172                 if (mMethodList.contains(imi)) {
3173                     enabledImeFound = true;
3174                     break;
3175                 }
3176             }
3177             if (!enabledImeFound) {
3178                 if (DEBUG) {
3179                     Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
3180                 }
3181                 resetDefaultEnabledIme = true;
3182                 resetSelectedInputMethodAndSubtypeLocked("");
3183             }
3184         }
3185
3186         if (resetDefaultEnabledIme) {
3187             final ArrayList<InputMethodInfo> defaultEnabledIme =
3188                     InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList);
3189             final int N = defaultEnabledIme.size();
3190             for (int i = 0; i < N; ++i) {
3191                 final InputMethodInfo imi =  defaultEnabledIme.get(i);
3192                 if (DEBUG) {
3193                     Slog.d(TAG, "--- enable ime = " + imi);
3194                 }
3195                 setInputMethodEnabledLocked(imi.getId(), true);
3196             }
3197         }
3198
3199         final String defaultImiId = mSettings.getSelectedInputMethod();
3200         if (!TextUtils.isEmpty(defaultImiId)) {
3201             if (!mMethodMap.containsKey(defaultImiId)) {
3202                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
3203                 if (chooseNewDefaultIMELocked()) {
3204                     updateInputMethodsFromSettingsLocked(true);
3205                 }
3206             } else {
3207                 // Double check that the default IME is certainly enabled.
3208                 setInputMethodEnabledLocked(defaultImiId, true);
3209             }
3210         }
3211         // Here is not the perfect place to reset the switching controller. Ideally
3212         // mSwitchingController and mSettings should be able to share the same state.
3213         // TODO: Make sure that mSwitchingController and mSettings are sharing the
3214         // the same enabled IMEs list.
3215         mSwitchingController.resetCircularListLocked(mContext);
3216     }
3217
3218     // ----------------------------------------------------------------------
3219
3220     private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
3221         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
3222         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3223                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3224                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3225         if (!TextUtils.isEmpty(inputMethodId)) {
3226             intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
3227         }
3228         final int userId;
3229         synchronized (mMethodMap) {
3230             userId = mSettings.getCurrentUserId();
3231         }
3232         mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
3233     }
3234
3235     private void showConfigureInputMethods() {
3236         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
3237         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3238                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3239                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3240         mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
3241     }
3242
3243     private boolean isScreenLocked() {
3244         return mKeyguardManager != null
3245                 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
3246     }
3247
3248     private void showInputMethodMenu(boolean showAuxSubtypes) {
3249         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
3250
3251         final Context context = mContext;
3252         final boolean isScreenLocked = isScreenLocked();
3253
3254         final String lastInputMethodId = mSettings.getSelectedInputMethod();
3255         int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
3256         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
3257
3258         synchronized (mMethodMap) {
3259             final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3260                     mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
3261                             mContext);
3262             if (immis == null || immis.size() == 0) {
3263                 return;
3264             }
3265
3266             hideInputMethodMenuLocked();
3267
3268             final List<ImeSubtypeListItem> imList =
3269                     mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
3270                             showAuxSubtypes, isScreenLocked);
3271
3272             if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
3273                 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
3274                 if (currentSubtype != null) {
3275                     final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
3276                     lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3277                             currentImi, currentSubtype.hashCode());
3278                 }
3279             }
3280
3281             final int N = imList.size();
3282             mIms = new InputMethodInfo[N];
3283             mSubtypeIds = new int[N];
3284             int checkedItem = 0;
3285             for (int i = 0; i < N; ++i) {
3286                 final ImeSubtypeListItem item = imList.get(i);
3287                 mIms[i] = item.mImi;
3288                 mSubtypeIds[i] = item.mSubtypeId;
3289                 if (mIms[i].getId().equals(lastInputMethodId)) {
3290                     int subtypeId = mSubtypeIds[i];
3291                     if ((subtypeId == NOT_A_SUBTYPE_ID)
3292                             || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
3293                             || (subtypeId == lastInputMethodSubtypeId)) {
3294                         checkedItem = i;
3295                     }
3296                 }
3297             }
3298
3299             final Context settingsContext = new ContextThemeWrapper(context,
3300                     com.android.internal.R.style.Theme_DeviceDefault_Settings);
3301
3302             mDialogBuilder = new AlertDialog.Builder(settingsContext);
3303             mDialogBuilder.setOnCancelListener(new OnCancelListener() {
3304                 @Override
3305                 public void onCancel(DialogInterface dialog) {
3306                     hideInputMethodMenu();
3307                 }
3308             });
3309
3310             final Context dialogContext = mDialogBuilder.getContext();
3311             final TypedArray a = dialogContext.obtainStyledAttributes(null,
3312                     com.android.internal.R.styleable.DialogPreference,
3313                     com.android.internal.R.attr.alertDialogStyle, 0);
3314             final Drawable dialogIcon = a.getDrawable(
3315                     com.android.internal.R.styleable.DialogPreference_dialogIcon);
3316             a.recycle();
3317
3318             mDialogBuilder.setIcon(dialogIcon);
3319
3320             final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
3321             final View tv = inflater.inflate(
3322                     com.android.internal.R.layout.input_method_switch_dialog_title, null);
3323             mDialogBuilder.setCustomTitle(tv);
3324
3325             // Setup layout for a toggle switch of the hardware keyboard
3326             mSwitchingDialogTitleView = tv;
3327             mSwitchingDialogTitleView
3328                     .findViewById(com.android.internal.R.id.hard_keyboard_section)
3329                     .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
3330                             ? View.VISIBLE : View.GONE);
3331             final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
3332                     com.android.internal.R.id.hard_keyboard_switch);
3333             hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
3334             hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
3335                 @Override
3336                 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
3337                     mSettings.setShowImeWithHardKeyboard(isChecked);
3338                     // Ensure that the input method dialog is dismissed when changing
3339                     // the hardware keyboard state.
3340                     hideInputMethodMenu();
3341                 }
3342             });
3343
3344             final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
3345                     com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
3346             final OnClickListener choiceListener = new OnClickListener() {
3347                 @Override
3348                 public void onClick(final DialogInterface dialog, final int which) {
3349                     synchronized (mMethodMap) {
3350                         if (mIms == null || mIms.length <= which || mSubtypeIds == null
3351                                 || mSubtypeIds.length <= which) {
3352                             return;
3353                         }
3354                         final InputMethodInfo im = mIms[which];
3355                         int subtypeId = mSubtypeIds[which];
3356                         adapter.mCheckedItem = which;
3357                         adapter.notifyDataSetChanged();
3358                         hideInputMethodMenu();
3359                         if (im != null) {
3360                             if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
3361                                 subtypeId = NOT_A_SUBTYPE_ID;
3362                             }
3363                             setInputMethodLocked(im.getId(), subtypeId);
3364                         }
3365                     }
3366                 }
3367             };
3368             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
3369
3370             mSwitchingDialog = mDialogBuilder.create();
3371             mSwitchingDialog.setCanceledOnTouchOutside(true);
3372             final Window w = mSwitchingDialog.getWindow();
3373             final WindowManager.LayoutParams attrs = w.getAttributes();
3374             w.setType(TYPE_INPUT_METHOD_DIALOG);
3375             // Use an alternate token for the dialog for that window manager can group the token
3376             // with other IME windows based on type vs. grouping based on whichever token happens
3377             // to get selected by the system later on.
3378             attrs.token = mSwitchingDialogToken;
3379             attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
3380             attrs.setTitle("Select input method");
3381             w.setAttributes(attrs);
3382             updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
3383             mSwitchingDialog.show();
3384         }
3385     }
3386
3387     private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
3388         private final LayoutInflater mInflater;
3389         private final int mTextViewResourceId;
3390         private final List<ImeSubtypeListItem> mItemsList;
3391         public int mCheckedItem;
3392         public ImeSubtypeListAdapter(Context context, int textViewResourceId,
3393                 List<ImeSubtypeListItem> itemsList, int checkedItem) {
3394             super(context, textViewResourceId, itemsList);
3395
3396             mTextViewResourceId = textViewResourceId;
3397             mItemsList = itemsList;
3398             mCheckedItem = checkedItem;
3399             mInflater = context.getSystemService(LayoutInflater.class);
3400         }
3401
3402         @Override
3403         public View getView(int position, View convertView, ViewGroup parent) {
3404             final View view = convertView != null ? convertView
3405                     : mInflater.inflate(mTextViewResourceId, null);
3406             if (position < 0 || position >= mItemsList.size()) return view;
3407             final ImeSubtypeListItem item = mItemsList.get(position);
3408             final CharSequence imeName = item.mImeName;
3409             final CharSequence subtypeName = item.mSubtypeName;
3410             final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
3411             final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
3412             if (TextUtils.isEmpty(subtypeName)) {
3413                 firstTextView.setText(imeName);
3414                 secondTextView.setVisibility(View.GONE);
3415             } else {
3416                 firstTextView.setText(subtypeName);
3417                 secondTextView.setText(imeName);
3418                 secondTextView.setVisibility(View.VISIBLE);
3419             }
3420             final RadioButton radioButton =
3421                     (RadioButton)view.findViewById(com.android.internal.R.id.radio);
3422             radioButton.setChecked(position == mCheckedItem);
3423             return view;
3424         }
3425     }
3426
3427     void hideInputMethodMenu() {
3428         synchronized (mMethodMap) {
3429             hideInputMethodMenuLocked();
3430         }
3431     }
3432
3433     void hideInputMethodMenuLocked() {
3434         if (DEBUG) Slog.v(TAG, "Hide switching menu");
3435
3436         if (mSwitchingDialog != null) {
3437             mSwitchingDialog.dismiss();
3438             mSwitchingDialog = null;
3439         }
3440
3441         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
3442         mDialogBuilder = null;
3443         mIms = null;
3444     }
3445
3446     // ----------------------------------------------------------------------
3447
3448     @Override
3449     public boolean setInputMethodEnabled(String id, boolean enabled) {
3450         // TODO: Make this work even for non-current users?
3451         if (!calledFromValidUser()) {
3452             return false;
3453         }
3454         synchronized (mMethodMap) {
3455             if (mContext.checkCallingOrSelfPermission(
3456                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
3457                     != PackageManager.PERMISSION_GRANTED) {
3458                 throw new SecurityException(
3459                         "Requires permission "
3460                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
3461             }
3462
3463             long ident = Binder.clearCallingIdentity();
3464             try {
3465                 return setInputMethodEnabledLocked(id, enabled);
3466             } finally {
3467                 Binder.restoreCallingIdentity(ident);
3468             }
3469         }
3470     }
3471
3472     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
3473         // Make sure this is a valid input method.
3474         InputMethodInfo imm = mMethodMap.get(id);
3475         if (imm == null) {
3476             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
3477         }
3478
3479         List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
3480                 .getEnabledInputMethodsAndSubtypeListLocked();
3481
3482         if (enabled) {
3483             for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
3484                 if (pair.first.equals(id)) {
3485                     // We are enabling this input method, but it is already enabled.
3486                     // Nothing to do. The previous state was enabled.
3487                     return true;
3488                 }
3489             }
3490             mSettings.appendAndPutEnabledInputMethodLocked(id, false);
3491             // Previous state was disabled.
3492             return false;
3493         } else {
3494             StringBuilder builder = new StringBuilder();
3495             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3496                     builder, enabledInputMethodsList, id)) {
3497                 // Disabled input method is currently selected, switch to another one.
3498                 final String selId = mSettings.getSelectedInputMethod();
3499                 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
3500                     Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
3501                     resetSelectedInputMethodAndSubtypeLocked("");
3502                 }
3503                 // Previous state was enabled.
3504                 return true;
3505             } else {
3506                 // We are disabling the input method but it is already disabled.
3507                 // Nothing to do.  The previous state was disabled.
3508                 return false;
3509             }
3510         }
3511     }
3512
3513     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
3514             boolean setSubtypeOnly) {
3515         // Update the history of InputMethod and Subtype
3516         mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
3517
3518         mCurUserActionNotificationSequenceNumber =
3519                 Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
3520         if (DEBUG) {
3521             Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
3522                     + mCurUserActionNotificationSequenceNumber);
3523         }
3524
3525         if (mCurClient != null && mCurClient.client != null) {
3526             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
3527                     MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
3528                     mCurUserActionNotificationSequenceNumber, mCurClient));
3529         }
3530
3531         // Set Subtype here
3532         if (imi == null || subtypeId < 0) {
3533             mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3534             mCurrentSubtype = null;
3535         } else {
3536             if (subtypeId < imi.getSubtypeCount()) {
3537                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
3538                 mSettings.putSelectedSubtype(subtype.hashCode());
3539                 mCurrentSubtype = subtype;
3540             } else {
3541                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3542                 // If the subtype is not specified, choose the most applicable one
3543                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
3544             }
3545         }
3546
3547         if (!setSubtypeOnly) {
3548             // Set InputMethod here
3549             mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
3550         }
3551     }
3552
3553     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
3554         InputMethodInfo imi = mMethodMap.get(newDefaultIme);
3555         int lastSubtypeId = NOT_A_SUBTYPE_ID;
3556         // newDefaultIme is empty when there is no candidate for the selected IME.
3557         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
3558             String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
3559             if (subtypeHashCode != null) {
3560                 try {
3561                     lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3562                             imi, Integer.parseInt(subtypeHashCode));
3563                 } catch (NumberFormatException e) {
3564                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
3565                 }
3566             }
3567         }
3568         setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
3569     }
3570
3571     // If there are no selected shortcuts, tries finding the most applicable ones.
3572     private Pair<InputMethodInfo, InputMethodSubtype>
3573             findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
3574         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
3575         InputMethodInfo mostApplicableIMI = null;
3576         InputMethodSubtype mostApplicableSubtype = null;
3577         boolean foundInSystemIME = false;
3578
3579         // Search applicable subtype for each InputMethodInfo
3580         for (InputMethodInfo imi: imis) {
3581             final String imiId = imi.getId();
3582             if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3583                 continue;
3584             }
3585             InputMethodSubtype subtype = null;
3586             final List<InputMethodSubtype> enabledSubtypes =
3587                     mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3588             // 1. Search by the current subtype's locale from enabledSubtypes.
3589             if (mCurrentSubtype != null) {
3590                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3591                         mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
3592             }
3593             // 2. Search by the system locale from enabledSubtypes.
3594             // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
3595             if (subtype == null) {
3596                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3597                         mRes, enabledSubtypes, mode, null, true);
3598             }
3599             final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
3600                     InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
3601             final ArrayList<InputMethodSubtype> subtypesForSearch =
3602                     overridingImplicitlyEnabledSubtypes.isEmpty()
3603                             ? InputMethodUtils.getSubtypes(imi)
3604                             : overridingImplicitlyEnabledSubtypes;
3605             // 4. Search by the current subtype's locale from all subtypes.
3606             if (subtype == null && mCurrentSubtype != null) {
3607                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3608                         mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
3609             }
3610             // 5. Search by the system locale from all subtypes.
3611             // 6. Search the first enabled subtype matched with mode from all subtypes.
3612             if (subtype == null) {
3613                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3614                         mRes, subtypesForSearch, mode, null, true);
3615             }
3616             if (subtype != null) {
3617                 if (imiId.equals(mCurMethodId)) {
3618                     // The current input method is the most applicable IME.
3619                     mostApplicableIMI = imi;
3620                     mostApplicableSubtype = subtype;
3621                     break;
3622                 } else if (!foundInSystemIME) {
3623                     // The system input method is 2nd applicable IME.
3624                     mostApplicableIMI = imi;
3625                     mostApplicableSubtype = subtype;
3626                     if ((imi.getServiceInfo().applicationInfo.flags
3627                             & ApplicationInfo.FLAG_SYSTEM) != 0) {
3628                         foundInSystemIME = true;
3629                     }
3630                 }
3631             }
3632         }
3633         if (DEBUG) {
3634             if (mostApplicableIMI != null) {
3635                 Slog.w(TAG, "Most applicable shortcut input method was:"
3636                         + mostApplicableIMI.getId());
3637                 if (mostApplicableSubtype != null) {
3638                     Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3639                             + "," + mostApplicableSubtype.getMode() + ","
3640                             + mostApplicableSubtype.getLocale());
3641                 }
3642             }
3643         }
3644         if (mostApplicableIMI != null) {
3645             return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
3646         } else {
3647             return null;
3648         }
3649     }
3650
3651     /**
3652      * @return Return the current subtype of this input method.
3653      */
3654     @Override
3655     public InputMethodSubtype getCurrentInputMethodSubtype() {
3656         // TODO: Make this work even for non-current users?
3657         if (!calledFromValidUser()) {
3658             return null;
3659         }
3660         synchronized (mMethodMap) {
3661             return getCurrentInputMethodSubtypeLocked();
3662         }
3663     }
3664
3665     private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3666         if (mCurMethodId == null) {
3667             return null;
3668         }
3669         final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3670         final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3671         if (imi == null || imi.getSubtypeCount() == 0) {
3672             return null;
3673         }
3674         if (!subtypeIsSelected || mCurrentSubtype == null
3675                 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3676             int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
3677             if (subtypeId == NOT_A_SUBTYPE_ID) {
3678                 // If there are no selected subtypes, the framework will try to find
3679                 // the most applicable subtype from explicitly or implicitly enabled
3680                 // subtypes.
3681                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3682                         mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3683                 // If there is only one explicitly or implicitly enabled subtype,
3684                 // just returns it.
3685                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3686                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3687                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3688                     mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3689                             mRes, explicitlyOrImplicitlyEnabledSubtypes,
3690                             InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
3691                     if (mCurrentSubtype == null) {
3692                         mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3693                                 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3694                                 true);
3695                     }
3696                 }
3697             } else {
3698                 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3699             }
3700         }
3701         return mCurrentSubtype;
3702     }
3703
3704     // TODO: We should change the return type from List to List<Parcelable>
3705     @SuppressWarnings("rawtypes")
3706     @Override
3707     public List getShortcutInputMethodsAndSubtypes() {
3708         synchronized (mMethodMap) {
3709             ArrayList<Object> ret = new ArrayList<>();
3710             if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3711                 // If there are no selected shortcut subtypes, the framework will try to find
3712                 // the most applicable subtype from all subtypes whose mode is
3713                 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3714                 Pair<InputMethodInfo, InputMethodSubtype> info =
3715                     findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3716                             InputMethodUtils.SUBTYPE_MODE_VOICE);
3717                 if (info != null) {
3718                     ret.add(info.first);
3719                     ret.add(info.second);
3720                 }
3721                 return ret;
3722             }
3723             for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3724                 ret.add(imi);
3725                 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3726                     ret.add(subtype);
3727                 }
3728             }
3729             return ret;
3730         }
3731     }
3732
3733     @Override
3734     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3735         // TODO: Make this work even for non-current users?
3736         if (!calledFromValidUser()) {
3737             return false;
3738         }
3739         synchronized (mMethodMap) {
3740             if (subtype != null && mCurMethodId != null) {
3741                 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3742                 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
3743                 if (subtypeId != NOT_A_SUBTYPE_ID) {
3744                     setInputMethodLocked(mCurMethodId, subtypeId);
3745                     return true;
3746                 }
3747             }
3748             return false;
3749         }
3750     }
3751
3752     // TODO: Cache the state for each user and reset when the cached user is removed.
3753     private static class InputMethodFileManager {
3754         private static final String SYSTEM_PATH = "system";
3755         private static final String INPUT_METHOD_PATH = "inputmethod";
3756         private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3757         private static final String NODE_SUBTYPES = "subtypes";
3758         private static final String NODE_SUBTYPE = "subtype";
3759         private static final String NODE_IMI = "imi";
3760         private static final String ATTR_ID = "id";
3761         private static final String ATTR_LABEL = "label";
3762         private static final String ATTR_ICON = "icon";
3763         private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
3764         private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3765         private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
3766         private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3767         private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3768         private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3769         private static final String ATTR_IS_ASCII_CAPABLE = "isAsciiCapable";
3770         private final AtomicFile mAdditionalInputMethodSubtypeFile;
3771         private final HashMap<String, InputMethodInfo> mMethodMap;
3772         private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3773                 new HashMap<>();
3774         public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3775             if (methodMap == null) {
3776                 throw new NullPointerException("methodMap is null");
3777             }
3778             mMethodMap = methodMap;
3779             final File systemDir = userId == UserHandle.USER_SYSTEM
3780                     ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3781                     : Environment.getUserSystemDirectory(userId);
3782             final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3783             if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
3784                 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3785             }
3786             final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3787             mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3788             if (!subtypeFile.exists()) {
3789                 // If "subtypes.xml" doesn't exist, create a blank file.
3790                 writeAdditionalInputMethodSubtypes(
3791                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3792             } else {
3793                 readAdditionalInputMethodSubtypes(
3794                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3795             }
3796         }
3797
3798         private void deleteAllInputMethodSubtypes(String imiId) {
3799             synchronized (mMethodMap) {
3800                 mAdditionalSubtypesMap.remove(imiId);
3801                 writeAdditionalInputMethodSubtypes(
3802                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3803             }
3804         }
3805
3806         public void addInputMethodSubtypes(
3807                 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3808             synchronized (mMethodMap) {
3809                 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
3810                 final int N = additionalSubtypes.length;
3811                 for (int i = 0; i < N; ++i) {
3812                     final InputMethodSubtype subtype = additionalSubtypes[i];
3813                     if (!subtypes.contains(subtype)) {
3814                         subtypes.add(subtype);
3815                     } else {
3816                         Slog.w(TAG, "Duplicated subtype definition found: "
3817                                 + subtype.getLocale() + ", " + subtype.getMode());
3818                     }
3819                 }
3820                 mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3821                 writeAdditionalInputMethodSubtypes(
3822                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3823             }
3824         }
3825
3826         public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3827             synchronized (mMethodMap) {
3828                 return mAdditionalSubtypesMap;
3829             }
3830         }
3831
3832         private static void writeAdditionalInputMethodSubtypes(
3833                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3834                 HashMap<String, InputMethodInfo> methodMap) {
3835             // Safety net for the case that this function is called before methodMap is set.
3836             final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3837             FileOutputStream fos = null;
3838             try {
3839                 fos = subtypesFile.startWrite();
3840                 final XmlSerializer out = new FastXmlSerializer();
3841                 out.setOutput(fos, StandardCharsets.UTF_8.name());
3842                 out.startDocument(null, true);
3843                 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3844                 out.startTag(null, NODE_SUBTYPES);
3845                 for (String imiId : allSubtypes.keySet()) {
3846                     if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3847                         Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3848                         continue;
3849                     }
3850                     out.startTag(null, NODE_IMI);
3851                     out.attribute(null, ATTR_ID, imiId);
3852                     final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3853                     final int N = subtypesList.size();
3854                     for (int i = 0; i < N; ++i) {
3855                         final InputMethodSubtype subtype = subtypesList.get(i);
3856                         out.startTag(null, NODE_SUBTYPE);
3857                         if (subtype.hasSubtypeId()) {
3858                             out.attribute(null, ATTR_IME_SUBTYPE_ID,
3859                                     String.valueOf(subtype.getSubtypeId()));
3860                         }
3861                         out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3862                         out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3863                         out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3864                         out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
3865                                 subtype.getLanguageTag());
3866                         out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3867                         out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3868                         out.attribute(null, ATTR_IS_AUXILIARY,
3869                                 String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3870                         out.attribute(null, ATTR_IS_ASCII_CAPABLE,
3871                                 String.valueOf(subtype.isAsciiCapable() ? 1 : 0));
3872                         out.endTag(null, NODE_SUBTYPE);
3873                     }
3874                     out.endTag(null, NODE_IMI);
3875                 }
3876                 out.endTag(null, NODE_SUBTYPES);
3877                 out.endDocument();
3878                 subtypesFile.finishWrite(fos);
3879             } catch (java.io.IOException e) {
3880                 Slog.w(TAG, "Error writing subtypes", e);
3881                 if (fos != null) {
3882                     subtypesFile.failWrite(fos);
3883                 }
3884             }
3885         }
3886
3887         private static void readAdditionalInputMethodSubtypes(
3888                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3889             if (allSubtypes == null || subtypesFile == null) return;
3890             allSubtypes.clear();
3891             try (final FileInputStream fis = subtypesFile.openRead()) {
3892                 final XmlPullParser parser = Xml.newPullParser();
3893                 parser.setInput(fis, StandardCharsets.UTF_8.name());
3894                 int type = parser.getEventType();
3895                 // Skip parsing until START_TAG
3896                 while ((type = parser.next()) != XmlPullParser.START_TAG
3897                         && type != XmlPullParser.END_DOCUMENT) {}
3898                 String firstNodeName = parser.getName();
3899                 if (!NODE_SUBTYPES.equals(firstNodeName)) {
3900                     throw new XmlPullParserException("Xml doesn't start with subtypes");
3901                 }
3902                 final int depth =parser.getDepth();
3903                 String currentImiId = null;
3904                 ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3905                 while (((type = parser.next()) != XmlPullParser.END_TAG
3906                         || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3907                     if (type != XmlPullParser.START_TAG)
3908                         continue;
3909                     final String nodeName = parser.getName();
3910                     if (NODE_IMI.equals(nodeName)) {
3911                         currentImiId = parser.getAttributeValue(null, ATTR_ID);
3912                         if (TextUtils.isEmpty(currentImiId)) {
3913                             Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3914                             continue;
3915                         }
3916                         tempSubtypesArray = new ArrayList<>();
3917                         allSubtypes.put(currentImiId, tempSubtypesArray);
3918                     } else if (NODE_SUBTYPE.equals(nodeName)) {
3919                         if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3920                             Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3921                             continue;
3922                         }
3923                         final int icon = Integer.parseInt(
3924                                 parser.getAttributeValue(null, ATTR_ICON));
3925                         final int label = Integer.parseInt(
3926                                 parser.getAttributeValue(null, ATTR_LABEL));
3927                         final String imeSubtypeLocale =
3928                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3929                         final String languageTag =
3930                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
3931                         final String imeSubtypeMode =
3932                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3933                         final String imeSubtypeExtraValue =
3934                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3935                         final boolean isAuxiliary = "1".equals(String.valueOf(
3936                                 parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3937                         final boolean isAsciiCapable = "1".equals(String.valueOf(
3938                                 parser.getAttributeValue(null, ATTR_IS_ASCII_CAPABLE)));
3939                         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder()
3940                                 .setSubtypeNameResId(label)
3941                                 .setSubtypeIconResId(icon)
3942                                 .setSubtypeLocale(imeSubtypeLocale)
3943                                 .setLanguageTag(languageTag)
3944                                 .setSubtypeMode(imeSubtypeMode)
3945                                 .setSubtypeExtraValue(imeSubtypeExtraValue)
3946                                 .setIsAuxiliary(isAuxiliary)
3947                                 .setIsAsciiCapable(isAsciiCapable);
3948                         final String subtypeIdString =
3949                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_ID);
3950                         if (subtypeIdString != null) {
3951                             builder.setSubtypeId(Integer.parseInt(subtypeIdString));
3952                         }
3953                         tempSubtypesArray.add(builder.build());
3954                     }
3955                 }
3956             } catch (XmlPullParserException | IOException | NumberFormatException e) {
3957                 Slog.w(TAG, "Error reading subtypes", e);
3958                 return;
3959             }
3960         }
3961     }
3962
3963     private static final class LocalServiceImpl implements InputMethodManagerInternal {
3964         @NonNull
3965         private final Handler mHandler;
3966
3967         LocalServiceImpl(@NonNull final Handler handler) {
3968             mHandler = handler;
3969         }
3970
3971         @Override
3972         public void setInteractive(boolean interactive) {
3973             // Do everything in handler so as not to block the caller.
3974             mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INTERACTIVE,
3975                     interactive ? 1 : 0, 0));
3976         }
3977
3978         @Override
3979         public void switchInputMethod(boolean forwardDirection) {
3980             // Do everything in handler so as not to block the caller.
3981             mHandler.sendMessage(mHandler.obtainMessage(MSG_SWITCH_IME,
3982                     forwardDirection ? 1 : 0, 0));
3983         }
3984
3985         @Override
3986         public void hideCurrentInputMethod() {
3987             mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
3988             mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
3989         }
3990     }
3991
3992     private static String imeWindowStatusToString(final int imeWindowVis) {
3993         final StringBuilder sb = new StringBuilder();
3994         boolean first = true;
3995         if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
3996             sb.append("Active");
3997             first = false;
3998         }
3999         if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
4000             if (!first) {
4001                 sb.append("|");
4002             }
4003             sb.append("Visible");
4004         }
4005         return sb.toString();
4006     }
4007
4008     @Override
4009     public IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
4010             @Nullable Uri contentUri, @Nullable String packageName) {
4011         if (!calledFromValidUser()) {
4012             return null;
4013         }
4014
4015         if (token == null) {
4016             throw new NullPointerException("token");
4017         }
4018         if (packageName == null) {
4019             throw new NullPointerException("packageName");
4020         }
4021         if (contentUri == null) {
4022             throw new NullPointerException("contentUri");
4023         }
4024         final String contentUriScheme = contentUri.getScheme();
4025         if (!"content".equals(contentUriScheme)) {
4026             throw new InvalidParameterException("contentUri must have content scheme");
4027         }
4028
4029         synchronized (mMethodMap) {
4030             final int uid = Binder.getCallingUid();
4031             if (mCurMethodId == null) {
4032                 return null;
4033             }
4034             if (mCurToken != token) {
4035                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
4036                         + " token=" + token);
4037                 return null;
4038             }
4039             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
4040             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
4041             // nature of our system.  Let's compare it with our internal record.
4042             if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
4043                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
4044                     + mCurAttribute.packageName + " packageName=" + packageName);
4045                 return null;
4046             }
4047             // This user ID can never bee spoofed.
4048             final int imeUserId = UserHandle.getUserId(uid);
4049             // This user ID can never bee spoofed.
4050             final int appUserId = UserHandle.getUserId(mCurClient.uid);
4051             // This user ID may be invalid if "contentUri" embedded an invalid user ID.
4052             final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
4053                     imeUserId);
4054             final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
4055             // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
4056             // actually has the right to grant a read permission for "contentUriWithoutUserId" that
4057             // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
4058             // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
4059             // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
4060             // actually allowed to "uid", which is guaranteed to be the IME's one.
4061             return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
4062                     packageName, contentUriOwnerUserId, appUserId);
4063         }
4064     }
4065
4066     @Override
4067     public void reportFullscreenMode(IBinder token, boolean fullscreen) {
4068         if (!calledFromValidUser()) {
4069             return;
4070         }
4071         synchronized (mMethodMap) {
4072             if (!calledWithValidToken(token)) {
4073                 return;
4074             }
4075             if (mCurClient != null && mCurClient.client != null) {
4076                 mInFullscreenMode = fullscreen;
4077                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
4078                         MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
4079             }
4080         }
4081     }
4082
4083     @Override
4084     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
4085         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
4086                 != PackageManager.PERMISSION_GRANTED) {
4087
4088             pw.println("Permission Denial: can't dump InputMethodManager from from pid="
4089                     + Binder.getCallingPid()
4090                     + ", uid=" + Binder.getCallingUid());
4091             return;
4092         }
4093
4094         IInputMethod method;
4095         ClientState client;
4096         ClientState focusedWindowClient;
4097
4098         final Printer p = new PrintWriterPrinter(pw);
4099
4100         synchronized (mMethodMap) {
4101             p.println("Current Input Method Manager state:");
4102             int N = mMethodList.size();
4103             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
4104             for (int i=0; i<N; i++) {
4105                 InputMethodInfo info = mMethodList.get(i);
4106                 p.println("  InputMethod #" + i + ":");
4107                 info.dump(p, "    ");
4108             }
4109             p.println("  Clients:");
4110             for (ClientState ci : mClients.values()) {
4111                 p.println("  Client " + ci + ":");
4112                 p.println("    client=" + ci.client);
4113                 p.println("    inputContext=" + ci.inputContext);
4114                 p.println("    sessionRequested=" + ci.sessionRequested);
4115                 p.println("    curSession=" + ci.curSession);
4116             }
4117             p.println("  mCurMethodId=" + mCurMethodId);
4118             client = mCurClient;
4119             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
4120             p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
4121             focusedWindowClient = mCurFocusedWindowClient;
4122             p.println("  mCurFocusedWindowClient=" + focusedWindowClient);
4123             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
4124                     + " mBoundToMethod=" + mBoundToMethod);
4125             p.println("  mCurToken=" + mCurToken);
4126             p.println("  mCurIntent=" + mCurIntent);
4127             method = mCurMethod;
4128             p.println("  mCurMethod=" + mCurMethod);
4129             p.println("  mEnabledSession=" + mEnabledSession);
4130             p.println("  mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
4131             p.println("  mShowRequested=" + mShowRequested
4132                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
4133                     + " mShowForced=" + mShowForced
4134                     + " mInputShown=" + mInputShown);
4135             p.println("  mInFullscreenMode=" + mInFullscreenMode);
4136             p.println("  mCurUserActionNotificationSequenceNumber="
4137                     + mCurUserActionNotificationSequenceNumber);
4138             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
4139             p.println("  mSettingsObserver=" + mSettingsObserver);
4140             p.println("  mSwitchingController:");
4141             mSwitchingController.dump(p);
4142             p.println("  mSettings:");
4143             mSettings.dumpLocked(p, "    ");
4144         }
4145
4146         p.println(" ");
4147         if (client != null) {
4148             pw.flush();
4149             try {
4150                 TransferPipe.dumpAsync(client.client.asBinder(), fd, args);
4151             } catch (IOException | RemoteException e) {
4152                 p.println("Failed to dump input method client: " + e);
4153             }
4154         } else {
4155             p.println("No input method client.");
4156         }
4157
4158         if (focusedWindowClient != null && client != focusedWindowClient) {
4159             p.println(" ");
4160             p.println("Warning: Current input method client doesn't match the last focused. "
4161                     + "window.");
4162             p.println("Dumping input method client in the last focused window just in case.");
4163             p.println(" ");
4164             pw.flush();
4165             try {
4166                 TransferPipe.dumpAsync(focusedWindowClient.client.asBinder(), fd, args);
4167             } catch (IOException | RemoteException e) {
4168                 p.println("Failed to dump input method client in focused window: " + e);
4169             }
4170         }
4171
4172         p.println(" ");
4173         if (method != null) {
4174             pw.flush();
4175             try {
4176                 TransferPipe.dumpAsync(method.asBinder(), fd, args);
4177             } catch (IOException | RemoteException e) {
4178                 p.println("Failed to dump input method service: " + e);
4179             }
4180         } else {
4181             p.println("No input method service.");
4182         }
4183     }
4184 }