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