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