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