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