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