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