InputMethodManagerService.java revision 5bc8e732bd831a308a5bc1720b0e4c9300d32f67
1/*
2 * Copyright (C) 2006-2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
5 * use this file except in compliance with the License. You may obtain a copy of
6 * the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13 * License for the specific language governing permissions and limitations under
14 * the License.
15 */
16
17package com.android.server;
18
19import com.android.internal.content.PackageMonitor;
20import com.android.internal.os.AtomicFile;
21import com.android.internal.os.HandlerCaller;
22import com.android.internal.util.FastXmlSerializer;
23import com.android.internal.view.IInputContext;
24import com.android.internal.view.IInputMethod;
25import com.android.internal.view.IInputMethodCallback;
26import com.android.internal.view.IInputMethodClient;
27import com.android.internal.view.IInputMethodManager;
28import com.android.internal.view.IInputMethodSession;
29import com.android.internal.view.InputBindResult;
30import com.android.server.EventLogTags;
31
32import org.xmlpull.v1.XmlPullParser;
33import org.xmlpull.v1.XmlPullParserException;
34import org.xmlpull.v1.XmlSerializer;
35
36import android.app.ActivityManagerNative;
37import android.app.AlertDialog;
38import android.app.KeyguardManager;
39import android.app.Notification;
40import android.app.NotificationManager;
41import android.app.PendingIntent;
42import android.content.ComponentName;
43import android.content.ContentResolver;
44import android.content.Context;
45import android.content.DialogInterface;
46import android.content.DialogInterface.OnCancelListener;
47import android.content.Intent;
48import android.content.IntentFilter;
49import android.content.ServiceConnection;
50import android.content.pm.ApplicationInfo;
51import android.content.pm.PackageInfo;
52import android.content.pm.PackageManager;
53import android.content.pm.PackageManager.NameNotFoundException;
54import android.content.pm.ResolveInfo;
55import android.content.pm.ServiceInfo;
56import android.content.res.Configuration;
57import android.content.res.Resources;
58import android.content.res.TypedArray;
59import android.database.ContentObserver;
60import android.graphics.BitmapFactory;
61import android.inputmethodservice.InputMethodService;
62import android.os.Binder;
63import android.os.Environment;
64import android.os.Handler;
65import android.os.IBinder;
66import android.os.IInterface;
67import android.os.Message;
68import android.os.Parcel;
69import android.os.RemoteException;
70import android.os.ResultReceiver;
71import android.os.ServiceManager;
72import android.os.SystemClock;
73import android.provider.Settings;
74import android.provider.Settings.Secure;
75import android.provider.Settings.SettingNotFoundException;
76import android.text.TextUtils;
77import android.text.style.SuggestionSpan;
78import android.util.EventLog;
79import android.util.LruCache;
80import android.util.Pair;
81import android.util.PrintWriterPrinter;
82import android.util.Printer;
83import android.util.Slog;
84import android.util.Xml;
85import android.view.IWindowManager;
86import android.view.WindowManager;
87import android.view.inputmethod.EditorInfo;
88import android.view.inputmethod.InputBinding;
89import android.view.inputmethod.InputMethod;
90import android.view.inputmethod.InputMethodInfo;
91import android.view.inputmethod.InputMethodManager;
92import android.view.inputmethod.InputMethodSubtype;
93
94import java.io.File;
95import java.io.FileDescriptor;
96import java.io.FileInputStream;
97import java.io.FileOutputStream;
98import java.io.IOException;
99import java.io.PrintWriter;
100import java.util.ArrayList;
101import java.util.Collections;
102import java.util.Comparator;
103import java.util.HashMap;
104import java.util.HashSet;
105import java.util.List;
106import java.util.Set;
107import java.util.TreeMap;
108
109/**
110 * This class provides a system service that manages input methods.
111 */
112public class InputMethodManagerService extends IInputMethodManager.Stub
113        implements ServiceConnection, Handler.Callback {
114    static final boolean DEBUG = false;
115    static final String TAG = "InputManagerService";
116
117    static final int MSG_SHOW_IM_PICKER = 1;
118    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
119    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
120    static final int MSG_SHOW_IM_CONFIG = 4;
121
122    static final int MSG_UNBIND_INPUT = 1000;
123    static final int MSG_BIND_INPUT = 1010;
124    static final int MSG_SHOW_SOFT_INPUT = 1020;
125    static final int MSG_HIDE_SOFT_INPUT = 1030;
126    static final int MSG_ATTACH_TOKEN = 1040;
127    static final int MSG_CREATE_SESSION = 1050;
128
129    static final int MSG_START_INPUT = 2000;
130    static final int MSG_RESTART_INPUT = 2010;
131
132    static final int MSG_UNBIND_METHOD = 3000;
133    static final int MSG_BIND_METHOD = 3010;
134
135    static final long TIME_TO_RECONNECT = 10*1000;
136
137    static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
138
139    private static final int NOT_A_SUBTYPE_ID = -1;
140    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
141    private static final String SUBTYPE_MODE_KEYBOARD = "keyboard";
142    private static final String SUBTYPE_MODE_VOICE = "voice";
143    private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
144
145    final Context mContext;
146    final Resources mRes;
147    final Handler mHandler;
148    final InputMethodSettings mSettings;
149    final SettingsObserver mSettingsObserver;
150    final StatusBarManagerService mStatusBar;
151    final IWindowManager mIWindowManager;
152    final HandlerCaller mCaller;
153    private final InputMethodFileManager mFileManager;
154
155    final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
156
157    // All known input methods.  mMethodMap also serves as the global
158    // lock for this class.
159    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
160    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
161    private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
162            new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
163
164    // Ongoing notification
165    private final NotificationManager mNotificationManager;
166    private final KeyguardManager mKeyguardManager;
167    private final Notification mImeSwitcherNotification;
168    private final PendingIntent mImeSwitchPendingIntent;
169    private boolean mShowOngoingImeSwitcherForPhones;
170    private boolean mNotificationShown;
171
172    class SessionState {
173        final ClientState client;
174        final IInputMethod method;
175        final IInputMethodSession session;
176
177        @Override
178        public String toString() {
179            return "SessionState{uid " + client.uid + " pid " + client.pid
180                    + " method " + Integer.toHexString(
181                            System.identityHashCode(method))
182                    + " session " + Integer.toHexString(
183                            System.identityHashCode(session))
184                    + "}";
185        }
186
187        SessionState(ClientState _client, IInputMethod _method,
188                IInputMethodSession _session) {
189            client = _client;
190            method = _method;
191            session = _session;
192        }
193    }
194
195    class ClientState {
196        final IInputMethodClient client;
197        final IInputContext inputContext;
198        final int uid;
199        final int pid;
200        final InputBinding binding;
201
202        boolean sessionRequested;
203        SessionState curSession;
204
205        @Override
206        public String toString() {
207            return "ClientState{" + Integer.toHexString(
208                    System.identityHashCode(this)) + " uid " + uid
209                    + " pid " + pid + "}";
210        }
211
212        ClientState(IInputMethodClient _client, IInputContext _inputContext,
213                int _uid, int _pid) {
214            client = _client;
215            inputContext = _inputContext;
216            uid = _uid;
217            pid = _pid;
218            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
219        }
220    }
221
222    final HashMap<IBinder, ClientState> mClients
223            = new HashMap<IBinder, ClientState>();
224
225    /**
226     * Set once the system is ready to run third party code.
227     */
228    boolean mSystemReady;
229
230    /**
231     * Id of the currently selected input method.
232     */
233    String mCurMethodId;
234
235    /**
236     * The current binding sequence number, incremented every time there is
237     * a new bind performed.
238     */
239    int mCurSeq;
240
241    /**
242     * The client that is currently bound to an input method.
243     */
244    ClientState mCurClient;
245
246    /**
247     * The last window token that gained focus.
248     */
249    IBinder mCurFocusedWindow;
250
251    /**
252     * The input context last provided by the current client.
253     */
254    IInputContext mCurInputContext;
255
256    /**
257     * The attributes last provided by the current client.
258     */
259    EditorInfo mCurAttribute;
260
261    /**
262     * The input method ID of the input method service that we are currently
263     * connected to or in the process of connecting to.
264     */
265    String mCurId;
266
267    /**
268     * The current subtype of the current input method.
269     */
270    private InputMethodSubtype mCurrentSubtype;
271
272    // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
273    private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
274            mShortcutInputMethodsAndSubtypes =
275                new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
276
277    /**
278     * Set to true if our ServiceConnection is currently actively bound to
279     * a service (whether or not we have gotten its IBinder back yet).
280     */
281    boolean mHaveConnection;
282
283    /**
284     * Set if the client has asked for the input method to be shown.
285     */
286    boolean mShowRequested;
287
288    /**
289     * Set if we were explicitly told to show the input method.
290     */
291    boolean mShowExplicitlyRequested;
292
293    /**
294     * Set if we were forced to be shown.
295     */
296    boolean mShowForced;
297
298    /**
299     * Set if we last told the input method to show itself.
300     */
301    boolean mInputShown;
302
303    /**
304     * The Intent used to connect to the current input method.
305     */
306    Intent mCurIntent;
307
308    /**
309     * The token we have made for the currently active input method, to
310     * identify it in the future.
311     */
312    IBinder mCurToken;
313
314    /**
315     * If non-null, this is the input method service we are currently connected
316     * to.
317     */
318    IInputMethod mCurMethod;
319
320    /**
321     * Time that we last initiated a bind to the input method, to determine
322     * if we should try to disconnect and reconnect to it.
323     */
324    long mLastBindTime;
325
326    /**
327     * Have we called mCurMethod.bindInput()?
328     */
329    boolean mBoundToMethod;
330
331    /**
332     * Currently enabled session.  Only touched by service thread, not
333     * protected by a lock.
334     */
335    SessionState mEnabledSession;
336
337    /**
338     * True if the screen is on.  The value is true initially.
339     */
340    boolean mScreenOn = true;
341
342    int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
343    int mImeWindowVis;
344
345    AlertDialog.Builder mDialogBuilder;
346    AlertDialog mSwitchingDialog;
347    InputMethodInfo[] mIms;
348    CharSequence[] mItems;
349    int[] mSubtypeIds;
350
351    class SettingsObserver extends ContentObserver {
352        SettingsObserver(Handler handler) {
353            super(handler);
354            ContentResolver resolver = mContext.getContentResolver();
355            resolver.registerContentObserver(Settings.Secure.getUriFor(
356                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
357            resolver.registerContentObserver(Settings.Secure.getUriFor(
358                    Settings.Secure.ENABLED_INPUT_METHODS), false, this);
359            resolver.registerContentObserver(Settings.Secure.getUriFor(
360                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
361        }
362
363        @Override public void onChange(boolean selfChange) {
364            synchronized (mMethodMap) {
365                updateFromSettingsLocked();
366            }
367        }
368    }
369
370    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
371        @Override
372        public void onReceive(Context context, Intent intent) {
373            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
374                mScreenOn = true;
375            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
376                mScreenOn = false;
377            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
378                hideInputMethodMenu();
379                return;
380            } else {
381                Slog.w(TAG, "Unexpected intent " + intent);
382            }
383
384            // Inform the current client of the change in active status
385            try {
386                if (mCurClient != null && mCurClient.client != null) {
387                    mCurClient.client.setActive(mScreenOn);
388                }
389            } catch (RemoteException e) {
390                Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
391                        + mCurClient.pid + " uid " + mCurClient.uid);
392            }
393        }
394    }
395
396    class MyPackageMonitor extends PackageMonitor {
397
398        @Override
399        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
400            synchronized (mMethodMap) {
401                String curInputMethodId = Settings.Secure.getString(mContext
402                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
403                final int N = mMethodList.size();
404                if (curInputMethodId != null) {
405                    for (int i=0; i<N; i++) {
406                        InputMethodInfo imi = mMethodList.get(i);
407                        if (imi.getId().equals(curInputMethodId)) {
408                            for (String pkg : packages) {
409                                if (imi.getPackageName().equals(pkg)) {
410                                    if (!doit) {
411                                        return true;
412                                    }
413                                    resetSelectedInputMethodAndSubtypeLocked("");
414                                    chooseNewDefaultIMELocked();
415                                    return true;
416                                }
417                            }
418                        }
419                    }
420                }
421            }
422            return false;
423        }
424
425        @Override
426        public void onSomePackagesChanged() {
427            synchronized (mMethodMap) {
428                InputMethodInfo curIm = null;
429                String curInputMethodId = Settings.Secure.getString(mContext
430                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
431                final int N = mMethodList.size();
432                if (curInputMethodId != null) {
433                    for (int i=0; i<N; i++) {
434                        InputMethodInfo imi = mMethodList.get(i);
435                        final String imiId = imi.getId();
436                        if (imiId.equals(curInputMethodId)) {
437                            curIm = imi;
438                        }
439
440                        int change = isPackageDisappearing(imi.getPackageName());
441                        if (isPackageModified(imi.getPackageName())) {
442                            mFileManager.deleteAllInputMethodSubtypes(imiId);
443                        }
444                        if (change == PACKAGE_TEMPORARY_CHANGE
445                                || change == PACKAGE_PERMANENT_CHANGE) {
446                            Slog.i(TAG, "Input method uninstalled, disabling: "
447                                    + imi.getComponent());
448                            setInputMethodEnabledLocked(imi.getId(), false);
449                        }
450                    }
451                }
452
453                buildInputMethodListLocked(mMethodList, mMethodMap);
454
455                boolean changed = false;
456
457                if (curIm != null) {
458                    int change = isPackageDisappearing(curIm.getPackageName());
459                    if (change == PACKAGE_TEMPORARY_CHANGE
460                            || change == PACKAGE_PERMANENT_CHANGE) {
461                        ServiceInfo si = null;
462                        try {
463                            si = mContext.getPackageManager().getServiceInfo(
464                                    curIm.getComponent(), 0);
465                        } catch (PackageManager.NameNotFoundException ex) {
466                        }
467                        if (si == null) {
468                            // Uh oh, current input method is no longer around!
469                            // Pick another one...
470                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
471                            mImeWindowVis = 0;
472                            mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
473                                    mBackDisposition);
474                            if (!chooseNewDefaultIMELocked()) {
475                                changed = true;
476                                curIm = null;
477                                Slog.i(TAG, "Unsetting current input method");
478                                resetSelectedInputMethodAndSubtypeLocked("");
479                            }
480                        }
481                    }
482                }
483
484                if (curIm == null) {
485                    // We currently don't have a default input method... is
486                    // one now available?
487                    changed = chooseNewDefaultIMELocked();
488                }
489
490                if (changed) {
491                    updateFromSettingsLocked();
492                }
493            }
494        }
495    }
496
497    class MethodCallback extends IInputMethodCallback.Stub {
498        final IInputMethod mMethod;
499
500        MethodCallback(IInputMethod method) {
501            mMethod = method;
502        }
503
504        @Override
505        public void finishedEvent(int seq, boolean handled) throws RemoteException {
506        }
507
508        @Override
509        public void sessionCreated(IInputMethodSession session) throws RemoteException {
510            onSessionCreated(mMethod, session);
511        }
512    }
513
514    public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
515        mContext = context;
516        mRes = context.getResources();
517        mHandler = new Handler(this);
518        mIWindowManager = IWindowManager.Stub.asInterface(
519                ServiceManager.getService(Context.WINDOW_SERVICE));
520        mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
521            @Override
522            public void executeMessage(Message msg) {
523                handleMessage(msg);
524            }
525        });
526
527        mKeyguardManager = (KeyguardManager)
528                mContext.getSystemService(Context.KEYGUARD_SERVICE);
529        mNotificationManager = (NotificationManager)
530                mContext.getSystemService(Context.NOTIFICATION_SERVICE);
531        mImeSwitcherNotification = new Notification();
532        mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
533        mImeSwitcherNotification.when = 0;
534        mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
535        mImeSwitcherNotification.tickerText = null;
536        mImeSwitcherNotification.defaults = 0; // please be quiet
537        mImeSwitcherNotification.sound = null;
538        mImeSwitcherNotification.vibrate = null;
539        Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
540        mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
541
542        mShowOngoingImeSwitcherForPhones = false;
543
544        synchronized (mMethodMap) {
545            mFileManager = new InputMethodFileManager(mMethodMap);
546        }
547
548        (new MyPackageMonitor()).register(mContext, true);
549
550        IntentFilter screenOnOffFilt = new IntentFilter();
551        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
552        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
553        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
554        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
555
556        mStatusBar = statusBar;
557        statusBar.setIconVisibility("ime", false);
558        mNotificationShown = false;
559
560        // mSettings should be created before buildInputMethodListLocked
561        mSettings = new InputMethodSettings(
562                mRes, context.getContentResolver(), mMethodMap, mMethodList);
563        buildInputMethodListLocked(mMethodList, mMethodMap);
564        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
565
566        if (TextUtils.isEmpty(Settings.Secure.getString(
567                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
568            InputMethodInfo defIm = null;
569            for (InputMethodInfo imi: mMethodList) {
570                if (defIm == null && imi.getIsDefaultResourceId() != 0) {
571                    try {
572                        Resources res = context.createPackageContext(
573                                imi.getPackageName(), 0).getResources();
574                        if (res.getBoolean(imi.getIsDefaultResourceId())) {
575                            defIm = imi;
576                            Slog.i(TAG, "Selected default: " + imi.getId());
577                        }
578                    } catch (PackageManager.NameNotFoundException ex) {
579                    } catch (Resources.NotFoundException ex) {
580                    }
581                }
582            }
583            if (defIm == null && mMethodList.size() > 0) {
584                defIm = mMethodList.get(0);
585                Slog.i(TAG, "No default found, using " + defIm.getId());
586            }
587            if (defIm != null) {
588                setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
589            }
590        }
591
592        mSettingsObserver = new SettingsObserver(mHandler);
593        updateFromSettingsLocked();
594    }
595
596    @Override
597    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
598            throws RemoteException {
599        try {
600            return super.onTransact(code, data, reply, flags);
601        } catch (RuntimeException e) {
602            // The input method manager only throws security exceptions, so let's
603            // log all others.
604            if (!(e instanceof SecurityException)) {
605                Slog.e(TAG, "Input Method Manager Crash", e);
606            }
607            throw e;
608        }
609    }
610
611    public void systemReady() {
612        synchronized (mMethodMap) {
613            if (!mSystemReady) {
614                mSystemReady = true;
615                mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
616                        com.android.internal.R.bool.show_ongoing_ime_switcher);
617                try {
618                    startInputInnerLocked();
619                } catch (RuntimeException e) {
620                    Slog.w(TAG, "Unexpected exception", e);
621                }
622            }
623        }
624    }
625
626    @Override
627    public List<InputMethodInfo> getInputMethodList() {
628        synchronized (mMethodMap) {
629            return new ArrayList<InputMethodInfo>(mMethodList);
630        }
631    }
632
633    @Override
634    public List<InputMethodInfo> getEnabledInputMethodList() {
635        synchronized (mMethodMap) {
636            return mSettings.getEnabledInputMethodListLocked();
637        }
638    }
639
640    private HashMap<InputMethodInfo, List<InputMethodSubtype>>
641            getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
642        HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
643                new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
644        for (InputMethodInfo imi: getEnabledInputMethodList()) {
645            enabledInputMethodAndSubtypes.put(
646                    imi, getEnabledInputMethodSubtypeListLocked(imi, true));
647        }
648        return enabledInputMethodAndSubtypes;
649    }
650
651    public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(InputMethodInfo imi,
652            boolean allowsImplicitlySelectedSubtypes) {
653        if (imi == null && mCurMethodId != null) {
654            imi = mMethodMap.get(mCurMethodId);
655        }
656        List<InputMethodSubtype> enabledSubtypes =
657                mSettings.getEnabledInputMethodSubtypeListLocked(imi);
658        if (allowsImplicitlySelectedSubtypes && enabledSubtypes.isEmpty()) {
659            enabledSubtypes = getApplicableSubtypesLocked(mRes, getSubtypes(imi));
660        }
661        return InputMethodSubtype.sort(mContext, 0, imi, enabledSubtypes);
662    }
663
664    @Override
665    public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
666            boolean allowsImplicitlySelectedSubtypes) {
667        synchronized (mMethodMap) {
668            return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
669        }
670    }
671
672    @Override
673    public void addClient(IInputMethodClient client,
674            IInputContext inputContext, int uid, int pid) {
675        synchronized (mMethodMap) {
676            mClients.put(client.asBinder(), new ClientState(client,
677                    inputContext, uid, pid));
678        }
679    }
680
681    @Override
682    public void removeClient(IInputMethodClient client) {
683        synchronized (mMethodMap) {
684            mClients.remove(client.asBinder());
685        }
686    }
687
688    void executeOrSendMessage(IInterface target, Message msg) {
689         if (target.asBinder() instanceof Binder) {
690             mCaller.sendMessage(msg);
691         } else {
692             handleMessage(msg);
693             msg.recycle();
694         }
695    }
696
697    void unbindCurrentClientLocked() {
698        if (mCurClient != null) {
699            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
700                    + mCurClient.client.asBinder());
701            if (mBoundToMethod) {
702                mBoundToMethod = false;
703                if (mCurMethod != null) {
704                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
705                            MSG_UNBIND_INPUT, mCurMethod));
706                }
707            }
708            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
709                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
710            mCurClient.sessionRequested = false;
711
712            // Call setActive(false) on the old client
713            try {
714                mCurClient.client.setActive(false);
715            } catch (RemoteException e) {
716                Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
717                        + mCurClient.pid + " uid " + mCurClient.uid);
718            }
719            mCurClient = null;
720
721            hideInputMethodMenuLocked();
722        }
723    }
724
725    private int getImeShowFlags() {
726        int flags = 0;
727        if (mShowForced) {
728            flags |= InputMethod.SHOW_FORCED
729                    | InputMethod.SHOW_EXPLICIT;
730        } else if (mShowExplicitlyRequested) {
731            flags |= InputMethod.SHOW_EXPLICIT;
732        }
733        return flags;
734    }
735
736    private int getAppShowFlags() {
737        int flags = 0;
738        if (mShowForced) {
739            flags |= InputMethodManager.SHOW_FORCED;
740        } else if (!mShowExplicitlyRequested) {
741            flags |= InputMethodManager.SHOW_IMPLICIT;
742        }
743        return flags;
744    }
745
746    InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
747        if (!mBoundToMethod) {
748            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
749                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
750            mBoundToMethod = true;
751        }
752        final SessionState session = mCurClient.curSession;
753        if (initial) {
754            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
755                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
756        } else {
757            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
758                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
759        }
760        if (mShowRequested) {
761            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
762            showCurrentInputLocked(getAppShowFlags(), null);
763        }
764        return needResult
765                ? new InputBindResult(session.session, mCurId, mCurSeq)
766                : null;
767    }
768
769    InputBindResult startInputLocked(IInputMethodClient client,
770            IInputContext inputContext, EditorInfo attribute,
771            boolean initial, boolean needResult) {
772        // If no method is currently selected, do nothing.
773        if (mCurMethodId == null) {
774            return mNoBinding;
775        }
776
777        ClientState cs = mClients.get(client.asBinder());
778        if (cs == null) {
779            throw new IllegalArgumentException("unknown client "
780                    + client.asBinder());
781        }
782
783        try {
784            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
785                // Check with the window manager to make sure this client actually
786                // has a window with focus.  If not, reject.  This is thread safe
787                // because if the focus changes some time before or after, the
788                // next client receiving focus that has any interest in input will
789                // be calling through here after that change happens.
790                Slog.w(TAG, "Starting input on non-focused client " + cs.client
791                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
792                return null;
793            }
794        } catch (RemoteException e) {
795        }
796
797        if (mCurClient != cs) {
798            // If the client is changing, we need to switch over to the new
799            // one.
800            unbindCurrentClientLocked();
801            if (DEBUG) Slog.v(TAG, "switching to client: client = "
802                    + cs.client.asBinder());
803
804            // If the screen is on, inform the new client it is active
805            if (mScreenOn) {
806                try {
807                    cs.client.setActive(mScreenOn);
808                } catch (RemoteException e) {
809                    Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
810                            + cs.pid + " uid " + cs.uid);
811                }
812            }
813        }
814
815        // Bump up the sequence for this client and attach it.
816        mCurSeq++;
817        if (mCurSeq <= 0) mCurSeq = 1;
818        mCurClient = cs;
819        mCurInputContext = inputContext;
820        mCurAttribute = attribute;
821
822        // Check if the input method is changing.
823        if (mCurId != null && mCurId.equals(mCurMethodId)) {
824            if (cs.curSession != null) {
825                // Fast case: if we are already connected to the input method,
826                // then just return it.
827                return attachNewInputLocked(initial, needResult);
828            }
829            if (mHaveConnection) {
830                if (mCurMethod != null) {
831                    if (!cs.sessionRequested) {
832                        cs.sessionRequested = true;
833                        if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
834                        executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
835                                MSG_CREATE_SESSION, mCurMethod,
836                                new MethodCallback(mCurMethod)));
837                    }
838                    // Return to client, and we will get back with it when
839                    // we have had a session made for it.
840                    return new InputBindResult(null, mCurId, mCurSeq);
841                } else if (SystemClock.uptimeMillis()
842                        < (mLastBindTime+TIME_TO_RECONNECT)) {
843                    // In this case we have connected to the service, but
844                    // don't yet have its interface.  If it hasn't been too
845                    // long since we did the connection, we'll return to
846                    // the client and wait to get the service interface so
847                    // we can report back.  If it has been too long, we want
848                    // to fall through so we can try a disconnect/reconnect
849                    // to see if we can get back in touch with the service.
850                    return new InputBindResult(null, mCurId, mCurSeq);
851                } else {
852                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
853                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
854                }
855            }
856        }
857
858        return startInputInnerLocked();
859    }
860
861    InputBindResult startInputInnerLocked() {
862        if (mCurMethodId == null) {
863            return mNoBinding;
864        }
865
866        if (!mSystemReady) {
867            // If the system is not yet ready, we shouldn't be running third
868            // party code.
869            return new InputBindResult(null, mCurMethodId, mCurSeq);
870        }
871
872        InputMethodInfo info = mMethodMap.get(mCurMethodId);
873        if (info == null) {
874            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
875        }
876
877        unbindCurrentMethodLocked(false);
878
879        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
880        mCurIntent.setComponent(info.getComponent());
881        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
882                com.android.internal.R.string.input_method_binding_label);
883        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
884                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
885        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
886            mLastBindTime = SystemClock.uptimeMillis();
887            mHaveConnection = true;
888            mCurId = info.getId();
889            mCurToken = new Binder();
890            try {
891                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
892                mIWindowManager.addWindowToken(mCurToken,
893                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
894            } catch (RemoteException e) {
895            }
896            return new InputBindResult(null, mCurId, mCurSeq);
897        } else {
898            mCurIntent = null;
899            Slog.w(TAG, "Failure connecting to input method service: "
900                    + mCurIntent);
901        }
902        return null;
903    }
904
905    @Override
906    public InputBindResult startInput(IInputMethodClient client,
907            IInputContext inputContext, EditorInfo attribute,
908            boolean initial, boolean needResult) {
909        synchronized (mMethodMap) {
910            final long ident = Binder.clearCallingIdentity();
911            try {
912                return startInputLocked(client, inputContext, attribute,
913                        initial, needResult);
914            } finally {
915                Binder.restoreCallingIdentity(ident);
916            }
917        }
918    }
919
920    @Override
921    public void finishInput(IInputMethodClient client) {
922    }
923
924    @Override
925    public void onServiceConnected(ComponentName name, IBinder service) {
926        synchronized (mMethodMap) {
927            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
928                mCurMethod = IInputMethod.Stub.asInterface(service);
929                if (mCurToken == null) {
930                    Slog.w(TAG, "Service connected without a token!");
931                    unbindCurrentMethodLocked(false);
932                    return;
933                }
934                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
935                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
936                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
937                if (mCurClient != null) {
938                    if (DEBUG) Slog.v(TAG, "Creating first session while with client "
939                            + mCurClient);
940                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
941                            MSG_CREATE_SESSION, mCurMethod,
942                            new MethodCallback(mCurMethod)));
943                }
944            }
945        }
946    }
947
948    void onSessionCreated(IInputMethod method, IInputMethodSession session) {
949        synchronized (mMethodMap) {
950            if (mCurMethod != null && method != null
951                    && mCurMethod.asBinder() == method.asBinder()) {
952                if (mCurClient != null) {
953                    mCurClient.curSession = new SessionState(mCurClient,
954                            method, session);
955                    mCurClient.sessionRequested = false;
956                    InputBindResult res = attachNewInputLocked(true, true);
957                    if (res.method != null) {
958                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
959                                MSG_BIND_METHOD, mCurClient.client, res));
960                    }
961                }
962            }
963        }
964    }
965
966    void unbindCurrentMethodLocked(boolean reportToClient) {
967        if (mHaveConnection) {
968            mContext.unbindService(this);
969            mHaveConnection = false;
970        }
971
972        if (mCurToken != null) {
973            try {
974                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
975                mIWindowManager.removeWindowToken(mCurToken);
976            } catch (RemoteException e) {
977            }
978            mCurToken = null;
979        }
980
981        mCurId = null;
982        clearCurMethodLocked();
983
984        if (reportToClient && mCurClient != null) {
985            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
986                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
987        }
988    }
989
990    private void finishSession(SessionState sessionState) {
991        if (sessionState != null && sessionState.session != null) {
992            try {
993                sessionState.session.finishSession();
994            } catch (RemoteException e) {
995                Slog.w(TAG, "Session failed to close due to remote exception", e);
996            }
997        }
998    }
999
1000    void clearCurMethodLocked() {
1001        if (mCurMethod != null) {
1002            for (ClientState cs : mClients.values()) {
1003                cs.sessionRequested = false;
1004                finishSession(cs.curSession);
1005                cs.curSession = null;
1006            }
1007
1008            finishSession(mEnabledSession);
1009            mEnabledSession = null;
1010            mCurMethod = null;
1011        }
1012        mStatusBar.setIconVisibility("ime", false);
1013    }
1014
1015    @Override
1016    public void onServiceDisconnected(ComponentName name) {
1017        synchronized (mMethodMap) {
1018            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1019                    + " mCurIntent=" + mCurIntent);
1020            if (mCurMethod != null && mCurIntent != null
1021                    && name.equals(mCurIntent.getComponent())) {
1022                clearCurMethodLocked();
1023                // We consider this to be a new bind attempt, since the system
1024                // should now try to restart the service for us.
1025                mLastBindTime = SystemClock.uptimeMillis();
1026                mShowRequested = mInputShown;
1027                mInputShown = false;
1028                if (mCurClient != null) {
1029                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1030                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1031                }
1032            }
1033        }
1034    }
1035
1036    @Override
1037    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1038        int uid = Binder.getCallingUid();
1039        long ident = Binder.clearCallingIdentity();
1040        try {
1041            if (token == null || mCurToken != token) {
1042                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
1043                return;
1044            }
1045
1046            synchronized (mMethodMap) {
1047                if (iconId == 0) {
1048                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1049                    mStatusBar.setIconVisibility("ime", false);
1050                } else if (packageName != null) {
1051                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1052                    CharSequence contentDescription = null;
1053                    try {
1054                        PackageManager packageManager = mContext.getPackageManager();
1055                        contentDescription = packageManager.getApplicationLabel(
1056                                packageManager.getApplicationInfo(packageName, 0));
1057                    } catch (NameNotFoundException nnfe) {
1058                        /* ignore */
1059                    }
1060                    mStatusBar.setIcon("ime", packageName, iconId, 0,
1061                            contentDescription  != null ? contentDescription.toString() : null);
1062                    mStatusBar.setIconVisibility("ime", true);
1063                }
1064            }
1065        } finally {
1066            Binder.restoreCallingIdentity(ident);
1067        }
1068    }
1069
1070    private boolean needsToShowImeSwitchOngoingNotification() {
1071        if (!mShowOngoingImeSwitcherForPhones) return false;
1072        synchronized (mMethodMap) {
1073            List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1074            final int N = imis.size();
1075            if (N > 2) return true;
1076            if (N < 1) return false;
1077            int nonAuxCount = 0;
1078            int auxCount = 0;
1079            InputMethodSubtype nonAuxSubtype = null;
1080            InputMethodSubtype auxSubtype = null;
1081            for(int i = 0; i < N; ++i) {
1082                final InputMethodInfo imi = imis.get(i);
1083                final List<InputMethodSubtype> subtypes = getEnabledInputMethodSubtypeListLocked(
1084                        imi, true);
1085                final int subtypeCount = subtypes.size();
1086                if (subtypeCount == 0) {
1087                    ++nonAuxCount;
1088                } else {
1089                    for (int j = 0; j < subtypeCount; ++j) {
1090                        final InputMethodSubtype subtype = subtypes.get(j);
1091                        if (!subtype.isAuxiliary()) {
1092                            ++nonAuxCount;
1093                            nonAuxSubtype = subtype;
1094                        } else {
1095                            ++auxCount;
1096                            auxSubtype = subtype;
1097                        }
1098                    }
1099                }
1100            }
1101            if (nonAuxCount > 1 || auxCount > 1) {
1102                return true;
1103            } else if (nonAuxCount == 1 && auxCount == 1) {
1104                if (nonAuxSubtype != null && auxSubtype != null
1105                        && nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1106                        && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1107                    return false;
1108                }
1109                return true;
1110            }
1111            return false;
1112        }
1113    }
1114
1115    @Override
1116    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1117        int uid = Binder.getCallingUid();
1118        long ident = Binder.clearCallingIdentity();
1119        try {
1120            if (token == null || mCurToken != token) {
1121                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
1122                return;
1123            }
1124
1125            synchronized (mMethodMap) {
1126                mImeWindowVis = vis;
1127                mBackDisposition = backDisposition;
1128                mStatusBar.setImeWindowStatus(token, vis, backDisposition);
1129                final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
1130                final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1131                if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
1132                    final PackageManager pm = mContext.getPackageManager();
1133                    final CharSequence title = mRes.getText(
1134                            com.android.internal.R.string.select_input_method);
1135                    final CharSequence imiLabel = imi.loadLabel(pm);
1136                    final CharSequence summary = mCurrentSubtype != null
1137                            ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
1138                                        imi.getPackageName(), imi.getServiceInfo().applicationInfo),
1139                                                (TextUtils.isEmpty(imiLabel) ?
1140                                                        "" : " (" + imiLabel + ")"))
1141                            : imiLabel;
1142
1143                    mImeSwitcherNotification.setLatestEventInfo(
1144                            mContext, title, summary, mImeSwitchPendingIntent);
1145                    mNotificationManager.notify(
1146                            com.android.internal.R.string.select_input_method,
1147                            mImeSwitcherNotification);
1148                    mNotificationShown = true;
1149                } else {
1150                    if (mNotificationShown) {
1151                        mNotificationManager.cancel(
1152                                com.android.internal.R.string.select_input_method);
1153                        mNotificationShown = false;
1154                    }
1155                }
1156            }
1157        } finally {
1158            Binder.restoreCallingIdentity(ident);
1159        }
1160    }
1161
1162    @Override
1163    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1164        synchronized (mMethodMap) {
1165            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1166            for (int i = 0; i < spans.length; ++i) {
1167                SuggestionSpan ss = spans[i];
1168                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1169                    mSecureSuggestionSpans.put(ss, currentImi);
1170                    final InputMethodInfo targetImi = mSecureSuggestionSpans.get(ss);
1171                }
1172            }
1173        }
1174    }
1175
1176    @Override
1177    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1178        synchronized (mMethodMap) {
1179            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1180            // TODO: Do not send the intent if the process of the targetImi is already dead.
1181            if (targetImi != null) {
1182                final String[] suggestions = span.getSuggestions();
1183                if (index < 0 || index >= suggestions.length) return false;
1184                final String className = span.getNotificationTargetClassName();
1185                final Intent intent = new Intent();
1186                // Ensures that only a class in the original IME package will receive the
1187                // notification.
1188                intent.setClassName(targetImi.getPackageName(), className);
1189                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1190                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1191                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1192                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1193                mContext.sendBroadcast(intent);
1194                return true;
1195            }
1196        }
1197        return false;
1198    }
1199
1200    void updateFromSettingsLocked() {
1201        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1202        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1203        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1204        // enabled.
1205        String id = Settings.Secure.getString(mContext.getContentResolver(),
1206                Settings.Secure.DEFAULT_INPUT_METHOD);
1207        // There is no input method selected, try to choose new applicable input method.
1208        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1209            id = Settings.Secure.getString(mContext.getContentResolver(),
1210                    Settings.Secure.DEFAULT_INPUT_METHOD);
1211        }
1212        if (!TextUtils.isEmpty(id)) {
1213            try {
1214                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
1215            } catch (IllegalArgumentException e) {
1216                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1217                mCurMethodId = null;
1218                unbindCurrentMethodLocked(true);
1219            }
1220            mShortcutInputMethodsAndSubtypes.clear();
1221        } else {
1222            // There is no longer an input method set, so stop any current one.
1223            mCurMethodId = null;
1224            unbindCurrentMethodLocked(true);
1225        }
1226    }
1227
1228    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1229        InputMethodInfo info = mMethodMap.get(id);
1230        if (info == null) {
1231            throw new IllegalArgumentException("Unknown id: " + id);
1232        }
1233
1234        if (id.equals(mCurMethodId)) {
1235            InputMethodSubtype subtype = null;
1236            if (subtypeId >= 0 && subtypeId < info.getSubtypeCount()) {
1237                subtype = info.getSubtypeAt(subtypeId);
1238            }
1239            if (subtype != mCurrentSubtype) {
1240                synchronized (mMethodMap) {
1241                    if (subtype != null) {
1242                        setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1243                    }
1244                    if (mCurMethod != null) {
1245                        try {
1246                            final Configuration conf = mRes.getConfiguration();
1247                            final boolean haveHardKeyboard = conf.keyboard
1248                                    != Configuration.KEYBOARD_NOKEYS;
1249                            final boolean hardKeyShown = haveHardKeyboard
1250                                    && conf.hardKeyboardHidden
1251                                            != Configuration.HARDKEYBOARDHIDDEN_YES;
1252                            mImeWindowVis = (mInputShown || hardKeyShown) ? (
1253                                    InputMethodService.IME_ACTIVE | InputMethodService.IME_VISIBLE)
1254                                    : 0;
1255                            mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
1256                                    mBackDisposition);
1257                            // If subtype is null, try to find the most applicable one from
1258                            // getCurrentInputMethodSubtype.
1259                            if (subtype == null) {
1260                                subtype = getCurrentInputMethodSubtype();
1261                            }
1262                            mCurMethod.changeInputMethodSubtype(subtype);
1263                        } catch (RemoteException e) {
1264                            return;
1265                        }
1266                    }
1267                }
1268            }
1269            return;
1270        }
1271
1272        final long ident = Binder.clearCallingIdentity();
1273        try {
1274            // Set a subtype to this input method.
1275            // subtypeId the name of a subtype which will be set.
1276            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1277            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1278            // because mCurMethodId is stored as a history in
1279            // setSelectedInputMethodAndSubtypeLocked().
1280            mCurMethodId = id;
1281
1282            if (ActivityManagerNative.isSystemReady()) {
1283                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1284                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1285                intent.putExtra("input_method_id", id);
1286                mContext.sendBroadcast(intent);
1287            }
1288            unbindCurrentClientLocked();
1289        } finally {
1290            Binder.restoreCallingIdentity(ident);
1291        }
1292    }
1293
1294    @Override
1295    public boolean showSoftInput(IInputMethodClient client, int flags,
1296            ResultReceiver resultReceiver) {
1297        int uid = Binder.getCallingUid();
1298        long ident = Binder.clearCallingIdentity();
1299        try {
1300            synchronized (mMethodMap) {
1301                if (mCurClient == null || client == null
1302                        || mCurClient.client.asBinder() != client.asBinder()) {
1303                    try {
1304                        // We need to check if this is the current client with
1305                        // focus in the window manager, to allow this call to
1306                        // be made before input is started in it.
1307                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1308                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1309                            return false;
1310                        }
1311                    } catch (RemoteException e) {
1312                        return false;
1313                    }
1314                }
1315
1316                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1317                return showCurrentInputLocked(flags, resultReceiver);
1318            }
1319        } finally {
1320            Binder.restoreCallingIdentity(ident);
1321        }
1322    }
1323
1324    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1325        mShowRequested = true;
1326        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1327            mShowExplicitlyRequested = true;
1328        }
1329        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1330            mShowExplicitlyRequested = true;
1331            mShowForced = true;
1332        }
1333
1334        if (!mSystemReady) {
1335            return false;
1336        }
1337
1338        boolean res = false;
1339        if (mCurMethod != null) {
1340            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1341                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1342                    resultReceiver));
1343            mInputShown = true;
1344            res = true;
1345        } else if (mHaveConnection && SystemClock.uptimeMillis()
1346                < (mLastBindTime+TIME_TO_RECONNECT)) {
1347            // The client has asked to have the input method shown, but
1348            // we have been sitting here too long with a connection to the
1349            // service and no interface received, so let's disconnect/connect
1350            // to try to prod things along.
1351            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1352                    SystemClock.uptimeMillis()-mLastBindTime,1);
1353            mContext.unbindService(this);
1354            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1355        }
1356
1357        return res;
1358    }
1359
1360    @Override
1361    public boolean hideSoftInput(IInputMethodClient client, int flags,
1362            ResultReceiver resultReceiver) {
1363        int uid = Binder.getCallingUid();
1364        long ident = Binder.clearCallingIdentity();
1365        try {
1366            synchronized (mMethodMap) {
1367                if (mCurClient == null || client == null
1368                        || mCurClient.client.asBinder() != client.asBinder()) {
1369                    try {
1370                        // We need to check if this is the current client with
1371                        // focus in the window manager, to allow this call to
1372                        // be made before input is started in it.
1373                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1374                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1375                                    + uid + ": " + client);
1376                            mImeWindowVis = 0;
1377                            mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis,
1378                                    mBackDisposition);
1379                            return false;
1380                        }
1381                    } catch (RemoteException e) {
1382                        mImeWindowVis = 0;
1383                        mStatusBar.setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
1384                        return false;
1385                    }
1386                }
1387
1388                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1389                return hideCurrentInputLocked(flags, resultReceiver);
1390            }
1391        } finally {
1392            Binder.restoreCallingIdentity(ident);
1393        }
1394    }
1395
1396    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1397        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1398                && (mShowExplicitlyRequested || mShowForced)) {
1399            if (DEBUG) Slog.v(TAG,
1400                    "Not hiding: explicit show not cancelled by non-explicit hide");
1401            return false;
1402        }
1403        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1404            if (DEBUG) Slog.v(TAG,
1405                    "Not hiding: forced show not cancelled by not-always hide");
1406            return false;
1407        }
1408        boolean res;
1409        if (mInputShown && mCurMethod != null) {
1410            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1411                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1412            res = true;
1413        } else {
1414            res = false;
1415        }
1416        mInputShown = false;
1417        mShowRequested = false;
1418        mShowExplicitlyRequested = false;
1419        mShowForced = false;
1420        return res;
1421    }
1422
1423    @Override
1424    public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1425            boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1426            boolean first, int windowFlags) {
1427        long ident = Binder.clearCallingIdentity();
1428        try {
1429            synchronized (mMethodMap) {
1430                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1431                        + " viewHasFocus=" + viewHasFocus
1432                        + " isTextEditor=" + isTextEditor
1433                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1434                        + " first=" + first + " flags=#"
1435                        + Integer.toHexString(windowFlags));
1436
1437                if (mCurClient == null || client == null
1438                        || mCurClient.client.asBinder() != client.asBinder()) {
1439                    try {
1440                        // We need to check if this is the current client with
1441                        // focus in the window manager, to allow this call to
1442                        // be made before input is started in it.
1443                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1444                            Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
1445                            return;
1446                        }
1447                    } catch (RemoteException e) {
1448                    }
1449                }
1450
1451                if (mCurFocusedWindow == windowToken) {
1452                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1453                    return;
1454                }
1455                mCurFocusedWindow = windowToken;
1456
1457                // Should we auto-show the IME even if the caller has not
1458                // specified what should be done with it?
1459                // We only do this automatically if the window can resize
1460                // to accommodate the IME (so what the user sees will give
1461                // them good context without input information being obscured
1462                // by the IME) or if running on a large screen where there
1463                // is more room for the target window + IME.
1464                final boolean doAutoShow =
1465                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1466                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1467                        || mRes.getConfiguration().isLayoutSizeAtLeast(
1468                                Configuration.SCREENLAYOUT_SIZE_LARGE);
1469
1470                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1471                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1472                        if (!isTextEditor || !doAutoShow) {
1473                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1474                                // There is no focus view, and this window will
1475                                // be behind any soft input window, so hide the
1476                                // soft input window if it is shown.
1477                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1478                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1479                            }
1480                        } else if (isTextEditor && doAutoShow && (softInputMode &
1481                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1482                            // There is a focus view, and we are navigating forward
1483                            // into the window, so show the input window for the user.
1484                            // We only do this automatically if the window an resize
1485                            // to accomodate the IME (so what the user sees will give
1486                            // them good context without input information being obscured
1487                            // by the IME) or if running on a large screen where there
1488                            // is more room for the target window + IME.
1489                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1490                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1491                        }
1492                        break;
1493                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1494                        // Do nothing.
1495                        break;
1496                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1497                        if ((softInputMode &
1498                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1499                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1500                            hideCurrentInputLocked(0, null);
1501                        }
1502                        break;
1503                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1504                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1505                        hideCurrentInputLocked(0, null);
1506                        break;
1507                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1508                        if ((softInputMode &
1509                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1510                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1511                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1512                        }
1513                        break;
1514                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1515                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1516                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1517                        break;
1518                }
1519            }
1520        } finally {
1521            Binder.restoreCallingIdentity(ident);
1522        }
1523    }
1524
1525    @Override
1526    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1527        synchronized (mMethodMap) {
1528            if (mCurClient == null || client == null
1529                    || mCurClient.client.asBinder() != client.asBinder()) {
1530                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1531                        + Binder.getCallingUid() + ": " + client);
1532            }
1533
1534            // Always call subtype picker, because subtype picker is a superset of input method
1535            // picker.
1536            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1537        }
1538    }
1539
1540    @Override
1541    public void setInputMethod(IBinder token, String id) {
1542        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
1543    }
1544
1545    @Override
1546    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
1547        synchronized (mMethodMap) {
1548            if (subtype != null) {
1549                setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
1550                        mMethodMap.get(id), subtype.hashCode()));
1551            } else {
1552                setInputMethod(token, id);
1553            }
1554        }
1555    }
1556
1557    @Override
1558    public void showInputMethodAndSubtypeEnablerFromClient(
1559            IInputMethodClient client, String inputMethodId) {
1560        synchronized (mMethodMap) {
1561            if (mCurClient == null || client == null
1562                || mCurClient.client.asBinder() != client.asBinder()) {
1563                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1564            }
1565            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1566                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
1567        }
1568    }
1569
1570    @Override
1571    public boolean switchToLastInputMethod(IBinder token) {
1572        synchronized (mMethodMap) {
1573            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1574            final InputMethodInfo lastImi;
1575            if (lastIme != null) {
1576                lastImi = mMethodMap.get(lastIme.first);
1577            } else {
1578                lastImi = null;
1579            }
1580            String targetLastImiId = null;
1581            int subtypeId = NOT_A_SUBTYPE_ID;
1582            if (lastIme != null && lastImi != null) {
1583                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
1584                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1585                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
1586                        : mCurrentSubtype.hashCode();
1587                // If the last IME is the same as the current IME and the last subtype is not
1588                // defined, there is no need to switch to the last IME.
1589                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
1590                    targetLastImiId = lastIme.first;
1591                    subtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1592                }
1593            }
1594
1595            if (TextUtils.isEmpty(targetLastImiId) && !canAddToLastInputMethod(mCurrentSubtype)) {
1596                // This is a safety net. If the currentSubtype can't be added to the history
1597                // and the framework couldn't find the last ime, we will make the last ime be
1598                // the most applicable enabled keyboard subtype of the system imes.
1599                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1600                if (enabled != null) {
1601                    final int N = enabled.size();
1602                    final String locale = mCurrentSubtype == null
1603                            ? mRes.getConfiguration().locale.toString()
1604                            : mCurrentSubtype.getLocale();
1605                    for (int i = 0; i < N; ++i) {
1606                        final InputMethodInfo imi = enabled.get(i);
1607                        if (imi.getSubtypeCount() > 0 && isSystemIme(imi)) {
1608                            InputMethodSubtype keyboardSubtype =
1609                                    findLastResortApplicableSubtypeLocked(mRes, getSubtypes(imi),
1610                                            SUBTYPE_MODE_KEYBOARD, locale, true);
1611                            if (keyboardSubtype != null) {
1612                                targetLastImiId = imi.getId();
1613                                subtypeId = getSubtypeIdFromHashCode(
1614                                        imi, keyboardSubtype.hashCode());
1615                                if(keyboardSubtype.getLocale().equals(locale)) {
1616                                    break;
1617                                }
1618                            }
1619                        }
1620                    }
1621                }
1622            }
1623
1624            if (!TextUtils.isEmpty(targetLastImiId)) {
1625                if (DEBUG) {
1626                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
1627                            + ", from: " + mCurMethodId + ", " + subtypeId);
1628                }
1629                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
1630                return true;
1631            } else {
1632                return false;
1633            }
1634        }
1635    }
1636
1637    @Override
1638    public InputMethodSubtype getLastInputMethodSubtype() {
1639        synchronized (mMethodMap) {
1640            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1641            // TODO: Handle the case of the last IME with no subtypes
1642            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
1643                    || TextUtils.isEmpty(lastIme.second)) return null;
1644            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
1645            if (lastImi == null) return null;
1646            try {
1647                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
1648                final int lastSubtypeId = getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
1649                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
1650                    return null;
1651                }
1652                return lastImi.getSubtypeAt(lastSubtypeId);
1653            } catch (NumberFormatException e) {
1654                return null;
1655            }
1656        }
1657    }
1658
1659    @Override
1660    public boolean setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
1661        // By this IPC call, only a process which shares the same uid with the IME can add
1662        // additional input method subtypes to the IME.
1663        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return false;
1664        synchronized (mMethodMap) {
1665            final InputMethodInfo imi = mMethodMap.get(imiId);
1666            if (imi == null) return false;
1667            final PackageManager pm = mContext.getPackageManager();
1668            final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
1669            if (packageInfos != null) {
1670                final int packageNum = packageInfos.length;
1671                for (int i = 0; i < packageNum; ++i) {
1672                    if (packageInfos[i].equals(imi.getPackageName())) {
1673                        mFileManager.addInputMethodSubtypes(imi, subtypes);
1674                        buildInputMethodListLocked(mMethodList, mMethodMap);
1675                        return true;
1676                    }
1677                }
1678            }
1679        }
1680        return false;
1681    }
1682
1683    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
1684        synchronized (mMethodMap) {
1685            if (token == null) {
1686                if (mContext.checkCallingOrSelfPermission(
1687                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
1688                        != PackageManager.PERMISSION_GRANTED) {
1689                    throw new SecurityException(
1690                            "Using null token requires permission "
1691                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1692                }
1693            } else if (mCurToken != token) {
1694                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1695                        + " token: " + token);
1696                return;
1697            }
1698
1699            long ident = Binder.clearCallingIdentity();
1700            try {
1701                setInputMethodLocked(id, subtypeId);
1702            } finally {
1703                Binder.restoreCallingIdentity(ident);
1704            }
1705        }
1706    }
1707
1708    @Override
1709    public void hideMySoftInput(IBinder token, int flags) {
1710        synchronized (mMethodMap) {
1711            if (token == null || mCurToken != token) {
1712                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1713                        + Binder.getCallingUid() + " token: " + token);
1714                return;
1715            }
1716            long ident = Binder.clearCallingIdentity();
1717            try {
1718                hideCurrentInputLocked(flags, null);
1719            } finally {
1720                Binder.restoreCallingIdentity(ident);
1721            }
1722        }
1723    }
1724
1725    @Override
1726    public void showMySoftInput(IBinder token, int flags) {
1727        synchronized (mMethodMap) {
1728            if (token == null || mCurToken != token) {
1729                Slog.w(TAG, "Ignoring showMySoftInput of uid "
1730                        + Binder.getCallingUid() + " token: " + token);
1731                return;
1732            }
1733            long ident = Binder.clearCallingIdentity();
1734            try {
1735                showCurrentInputLocked(flags, null);
1736            } finally {
1737                Binder.restoreCallingIdentity(ident);
1738            }
1739        }
1740    }
1741
1742    void setEnabledSessionInMainThread(SessionState session) {
1743        if (mEnabledSession != session) {
1744            if (mEnabledSession != null) {
1745                try {
1746                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1747                    mEnabledSession.method.setSessionEnabled(
1748                            mEnabledSession.session, false);
1749                } catch (RemoteException e) {
1750                }
1751            }
1752            mEnabledSession = session;
1753            try {
1754                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1755                session.method.setSessionEnabled(
1756                        session.session, true);
1757            } catch (RemoteException e) {
1758            }
1759        }
1760    }
1761
1762    @Override
1763    public boolean handleMessage(Message msg) {
1764        HandlerCaller.SomeArgs args;
1765        switch (msg.what) {
1766            case MSG_SHOW_IM_PICKER:
1767                showInputMethodMenu();
1768                return true;
1769
1770            case MSG_SHOW_IM_SUBTYPE_PICKER:
1771                showInputMethodSubtypeMenu();
1772                return true;
1773
1774            case MSG_SHOW_IM_SUBTYPE_ENABLER:
1775                args = (HandlerCaller.SomeArgs)msg.obj;
1776                showInputMethodAndSubtypeEnabler((String)args.arg1);
1777                return true;
1778
1779            case MSG_SHOW_IM_CONFIG:
1780                showConfigureInputMethods();
1781                return true;
1782
1783            // ---------------------------------------------------------
1784
1785            case MSG_UNBIND_INPUT:
1786                try {
1787                    ((IInputMethod)msg.obj).unbindInput();
1788                } catch (RemoteException e) {
1789                    // There is nothing interesting about the method dying.
1790                }
1791                return true;
1792            case MSG_BIND_INPUT:
1793                args = (HandlerCaller.SomeArgs)msg.obj;
1794                try {
1795                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1796                } catch (RemoteException e) {
1797                }
1798                return true;
1799            case MSG_SHOW_SOFT_INPUT:
1800                args = (HandlerCaller.SomeArgs)msg.obj;
1801                try {
1802                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1803                            (ResultReceiver)args.arg2);
1804                } catch (RemoteException e) {
1805                }
1806                return true;
1807            case MSG_HIDE_SOFT_INPUT:
1808                args = (HandlerCaller.SomeArgs)msg.obj;
1809                try {
1810                    ((IInputMethod)args.arg1).hideSoftInput(0,
1811                            (ResultReceiver)args.arg2);
1812                } catch (RemoteException e) {
1813                }
1814                return true;
1815            case MSG_ATTACH_TOKEN:
1816                args = (HandlerCaller.SomeArgs)msg.obj;
1817                try {
1818                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1819                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1820                } catch (RemoteException e) {
1821                }
1822                return true;
1823            case MSG_CREATE_SESSION:
1824                args = (HandlerCaller.SomeArgs)msg.obj;
1825                try {
1826                    ((IInputMethod)args.arg1).createSession(
1827                            (IInputMethodCallback)args.arg2);
1828                } catch (RemoteException e) {
1829                }
1830                return true;
1831            // ---------------------------------------------------------
1832
1833            case MSG_START_INPUT:
1834                args = (HandlerCaller.SomeArgs)msg.obj;
1835                try {
1836                    SessionState session = (SessionState)args.arg1;
1837                    setEnabledSessionInMainThread(session);
1838                    session.method.startInput((IInputContext)args.arg2,
1839                            (EditorInfo)args.arg3);
1840                } catch (RemoteException e) {
1841                }
1842                return true;
1843            case MSG_RESTART_INPUT:
1844                args = (HandlerCaller.SomeArgs)msg.obj;
1845                try {
1846                    SessionState session = (SessionState)args.arg1;
1847                    setEnabledSessionInMainThread(session);
1848                    session.method.restartInput((IInputContext)args.arg2,
1849                            (EditorInfo)args.arg3);
1850                } catch (RemoteException e) {
1851                }
1852                return true;
1853
1854            // ---------------------------------------------------------
1855
1856            case MSG_UNBIND_METHOD:
1857                try {
1858                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1859                } catch (RemoteException e) {
1860                    // There is nothing interesting about the last client dying.
1861                }
1862                return true;
1863            case MSG_BIND_METHOD:
1864                args = (HandlerCaller.SomeArgs)msg.obj;
1865                try {
1866                    ((IInputMethodClient)args.arg1).onBindMethod(
1867                            (InputBindResult)args.arg2);
1868                } catch (RemoteException e) {
1869                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
1870                }
1871                return true;
1872        }
1873        return false;
1874    }
1875
1876    private boolean isSystemIme(InputMethodInfo inputMethod) {
1877        return (inputMethod.getServiceInfo().applicationInfo.flags
1878                & ApplicationInfo.FLAG_SYSTEM) != 0;
1879    }
1880
1881    private static ArrayList<InputMethodSubtype> getSubtypes(InputMethodInfo imi) {
1882        ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
1883        final int subtypeCount = imi.getSubtypeCount();
1884        for (int i = 0; i < subtypeCount; ++i) {
1885            subtypes.add(imi.getSubtypeAt(i));
1886        }
1887        return subtypes;
1888    }
1889
1890    private boolean chooseNewDefaultIMELocked() {
1891        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1892        if (enabled != null && enabled.size() > 0) {
1893            // We'd prefer to fall back on a system IME, since that is safer.
1894            int i=enabled.size();
1895            while (i > 0) {
1896                i--;
1897                if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1898                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
1899                    break;
1900                }
1901            }
1902            InputMethodInfo imi = enabled.get(i);
1903            if (DEBUG) {
1904                Slog.d(TAG, "New default IME was selected: " + imi.getId());
1905            }
1906            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
1907            return true;
1908        }
1909
1910        return false;
1911    }
1912
1913    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1914            HashMap<String, InputMethodInfo> map) {
1915        list.clear();
1916        map.clear();
1917
1918        PackageManager pm = mContext.getPackageManager();
1919        final Configuration config = mRes.getConfiguration();
1920        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1921        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1922                Secure.DISABLED_SYSTEM_INPUT_METHODS);
1923        if (disabledSysImes == null) disabledSysImes = "";
1924
1925        List<ResolveInfo> services = pm.queryIntentServices(
1926                new Intent(InputMethod.SERVICE_INTERFACE),
1927                PackageManager.GET_META_DATA);
1928
1929        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
1930                mFileManager.getAllAdditionalInputMethodSubtypes();
1931        for (int i = 0; i < services.size(); ++i) {
1932            ResolveInfo ri = services.get(i);
1933            ServiceInfo si = ri.serviceInfo;
1934            ComponentName compName = new ComponentName(si.packageName, si.name);
1935            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1936                    si.permission)) {
1937                Slog.w(TAG, "Skipping input method " + compName
1938                        + ": it does not require the permission "
1939                        + android.Manifest.permission.BIND_INPUT_METHOD);
1940                continue;
1941            }
1942
1943            if (DEBUG) Slog.d(TAG, "Checking " + compName);
1944
1945            try {
1946                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
1947                list.add(p);
1948                final String id = p.getId();
1949                map.put(id, p);
1950
1951                // System IMEs are enabled by default, unless there's a hard keyboard
1952                // and the system IME was explicitly disabled
1953                if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1954                    setInputMethodEnabledLocked(id, true);
1955                }
1956
1957                if (DEBUG) {
1958                    Slog.d(TAG, "Found a third-party input method " + p);
1959                }
1960
1961            } catch (XmlPullParserException e) {
1962                Slog.w(TAG, "Unable to load input method " + compName, e);
1963            } catch (IOException e) {
1964                Slog.w(TAG, "Unable to load input method " + compName, e);
1965            }
1966        }
1967
1968        String defaultIme = Settings.Secure.getString(mContext
1969                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1970        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
1971            if (chooseNewDefaultIMELocked()) {
1972                updateFromSettingsLocked();
1973            }
1974        }
1975    }
1976
1977    // ----------------------------------------------------------------------
1978
1979    private void showInputMethodMenu() {
1980        showInputMethodMenuInternal(false);
1981    }
1982
1983    private void showInputMethodSubtypeMenu() {
1984        showInputMethodMenuInternal(true);
1985    }
1986
1987    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
1988        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
1989        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1990                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1991                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1992        if (!TextUtils.isEmpty(inputMethodId)) {
1993            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
1994        }
1995        mContext.startActivity(intent);
1996    }
1997
1998    private void showConfigureInputMethods() {
1999        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2000        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2001                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2002                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2003        mContext.startActivity(intent);
2004    }
2005
2006    private void showInputMethodMenuInternal(boolean showSubtypes) {
2007        if (DEBUG) Slog.v(TAG, "Show switching menu");
2008
2009        final Context context = mContext;
2010
2011        final PackageManager pm = context.getPackageManager();
2012
2013        String lastInputMethodId = Settings.Secure.getString(context
2014                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2015        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2016        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2017
2018        synchronized (mMethodMap) {
2019            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2020                    getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
2021            if (immis == null || immis.size() == 0) {
2022                return;
2023            }
2024
2025            hideInputMethodMenuLocked();
2026
2027            final TreeMap<InputMethodInfo, List<InputMethodSubtype>> sortedImmis =
2028                    new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
2029                            new Comparator<InputMethodInfo>() {
2030                                @Override
2031                                public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
2032                                    if (imi2 == null) return 0;
2033                                    if (imi1 == null) return 1;
2034                                    if (pm == null) {
2035                                        return imi1.getId().compareTo(imi2.getId());
2036                                    }
2037                                    CharSequence imiId1 = imi1.loadLabel(pm) + "/" + imi1.getId();
2038                                    CharSequence imiId2 = imi2.loadLabel(pm) + "/" + imi2.getId();
2039                                    return imiId1.toString().compareTo(imiId2.toString());
2040                                }
2041                            });
2042
2043            sortedImmis.putAll(immis);
2044
2045            final ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>> imList =
2046                    new ArrayList<Pair<CharSequence, Pair<InputMethodInfo, Integer>>>();
2047
2048            for (InputMethodInfo imi : sortedImmis.keySet()) {
2049                if (imi == null) continue;
2050                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
2051                HashSet<String> enabledSubtypeSet = new HashSet<String>();
2052                for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
2053                    enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
2054                }
2055                ArrayList<InputMethodSubtype> subtypes = getSubtypes(imi);
2056                final CharSequence label = imi.loadLabel(pm);
2057                if (showSubtypes && enabledSubtypeSet.size() > 0) {
2058                    final int subtypeCount = imi.getSubtypeCount();
2059                    if (DEBUG) {
2060                        Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
2061                    }
2062                    for (int j = 0; j < subtypeCount; ++j) {
2063                        final InputMethodSubtype subtype = imi.getSubtypeAt(j);
2064                        final String subtypeHashCode = String.valueOf(subtype.hashCode());
2065                        // We show all enabled IMEs and subtypes when an IME is shown.
2066                        if (enabledSubtypeSet.contains(subtypeHashCode)
2067                                && (mInputShown || !subtype.isAuxiliary())) {
2068                            final CharSequence title;
2069                            final String mode = subtype.getMode();
2070                            title = TextUtils.concat(subtype.getDisplayName(context,
2071                                    imi.getPackageName(), imi.getServiceInfo().applicationInfo),
2072                                    (TextUtils.isEmpty(label) ? "" : " (" + label + ")"));
2073                            imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>(
2074                                    title, new Pair<InputMethodInfo, Integer>(imi, j)));
2075                            // Removing this subtype from enabledSubtypeSet because we no longer
2076                            // need to add an entry of this subtype to imList to avoid duplicated
2077                            // entries.
2078                            enabledSubtypeSet.remove(subtypeHashCode);
2079                        }
2080                    }
2081                } else {
2082                    imList.add(new Pair<CharSequence, Pair<InputMethodInfo, Integer>>(
2083                            label, new Pair<InputMethodInfo, Integer>(imi, NOT_A_SUBTYPE_ID)));
2084                }
2085            }
2086
2087            final int N = imList.size();
2088            mItems = new CharSequence[N];
2089            for (int i = 0; i < N; ++i) {
2090                mItems[i] = imList.get(i).first;
2091            }
2092            mIms = new InputMethodInfo[N];
2093            mSubtypeIds = new int[N];
2094            int checkedItem = 0;
2095            for (int i = 0; i < N; ++i) {
2096                Pair<InputMethodInfo, Integer> value = imList.get(i).second;
2097                mIms[i] = value.first;
2098                mSubtypeIds[i] = value.second;
2099                if (mIms[i].getId().equals(lastInputMethodId)) {
2100                    int subtypeId = mSubtypeIds[i];
2101                    if ((subtypeId == NOT_A_SUBTYPE_ID)
2102                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2103                            || (subtypeId == lastInputMethodSubtypeId)) {
2104                        checkedItem = i;
2105                    }
2106                }
2107            }
2108
2109            AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
2110                @Override
2111                public void onClick(DialogInterface dialog, int which) {
2112                    hideInputMethodMenu();
2113                }
2114            };
2115
2116            TypedArray a = context.obtainStyledAttributes(null,
2117                    com.android.internal.R.styleable.DialogPreference,
2118                    com.android.internal.R.attr.alertDialogStyle, 0);
2119            mDialogBuilder = new AlertDialog.Builder(context)
2120                    .setTitle(com.android.internal.R.string.select_input_method)
2121                    .setOnCancelListener(new OnCancelListener() {
2122                        @Override
2123                        public void onCancel(DialogInterface dialog) {
2124                            hideInputMethodMenu();
2125                        }
2126                    })
2127                    .setIcon(a.getDrawable(
2128                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
2129            a.recycle();
2130
2131            mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
2132                    new AlertDialog.OnClickListener() {
2133                        @Override
2134                        public void onClick(DialogInterface dialog, int which) {
2135                            synchronized (mMethodMap) {
2136                                if (mIms == null || mIms.length <= which
2137                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
2138                                    return;
2139                                }
2140                                InputMethodInfo im = mIms[which];
2141                                int subtypeId = mSubtypeIds[which];
2142                                hideInputMethodMenu();
2143                                if (im != null) {
2144                                    if ((subtypeId < 0)
2145                                            || (subtypeId >= im.getSubtypeCount())) {
2146                                        subtypeId = NOT_A_SUBTYPE_ID;
2147                                    }
2148                                    setInputMethodLocked(im.getId(), subtypeId);
2149                                }
2150                            }
2151                        }
2152                    });
2153
2154            if (showSubtypes && !(mKeyguardManager.isKeyguardLocked()
2155                    && mKeyguardManager.isKeyguardSecure())) {
2156                mDialogBuilder.setPositiveButton(
2157                        com.android.internal.R.string.configure_input_methods,
2158                        new DialogInterface.OnClickListener() {
2159                            @Override
2160                            public void onClick(DialogInterface dialog, int whichButton) {
2161                                showConfigureInputMethods();
2162                            }
2163                        });
2164            }
2165            mSwitchingDialog = mDialogBuilder.create();
2166            mSwitchingDialog.setCanceledOnTouchOutside(true);
2167            mSwitchingDialog.getWindow().setType(
2168                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2169            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2170            mSwitchingDialog.show();
2171        }
2172    }
2173
2174    void hideInputMethodMenu() {
2175        synchronized (mMethodMap) {
2176            hideInputMethodMenuLocked();
2177        }
2178    }
2179
2180    void hideInputMethodMenuLocked() {
2181        if (DEBUG) Slog.v(TAG, "Hide switching menu");
2182
2183        if (mSwitchingDialog != null) {
2184            mSwitchingDialog.dismiss();
2185            mSwitchingDialog = null;
2186        }
2187
2188        mDialogBuilder = null;
2189        mItems = null;
2190        mIms = null;
2191    }
2192
2193    // ----------------------------------------------------------------------
2194
2195    @Override
2196    public boolean setInputMethodEnabled(String id, boolean enabled) {
2197        synchronized (mMethodMap) {
2198            if (mContext.checkCallingOrSelfPermission(
2199                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2200                    != PackageManager.PERMISSION_GRANTED) {
2201                throw new SecurityException(
2202                        "Requires permission "
2203                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2204            }
2205
2206            long ident = Binder.clearCallingIdentity();
2207            try {
2208                return setInputMethodEnabledLocked(id, enabled);
2209            } finally {
2210                Binder.restoreCallingIdentity(ident);
2211            }
2212        }
2213    }
2214
2215    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2216        // Make sure this is a valid input method.
2217        InputMethodInfo imm = mMethodMap.get(id);
2218        if (imm == null) {
2219            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2220        }
2221
2222        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2223                .getEnabledInputMethodsAndSubtypeListLocked();
2224
2225        if (enabled) {
2226            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2227                if (pair.first.equals(id)) {
2228                    // We are enabling this input method, but it is already enabled.
2229                    // Nothing to do. The previous state was enabled.
2230                    return true;
2231                }
2232            }
2233            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
2234            // Previous state was disabled.
2235            return false;
2236        } else {
2237            StringBuilder builder = new StringBuilder();
2238            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2239                    builder, enabledInputMethodsList, id)) {
2240                // Disabled input method is currently selected, switch to another one.
2241                String selId = Settings.Secure.getString(mContext.getContentResolver(),
2242                        Settings.Secure.DEFAULT_INPUT_METHOD);
2243                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
2244                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
2245                    resetSelectedInputMethodAndSubtypeLocked("");
2246                }
2247                // Previous state was enabled.
2248                return true;
2249            } else {
2250                // We are disabling the input method but it is already disabled.
2251                // Nothing to do.  The previous state was disabled.
2252                return false;
2253            }
2254        }
2255    }
2256
2257    private boolean canAddToLastInputMethod(InputMethodSubtype subtype) {
2258        if (subtype == null) return true;
2259        return !subtype.isAuxiliary();
2260    }
2261
2262    private void saveCurrentInputMethodAndSubtypeToHistory() {
2263        String subtypeId = NOT_A_SUBTYPE_ID_STR;
2264        if (mCurrentSubtype != null) {
2265            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
2266        }
2267        if (canAddToLastInputMethod(mCurrentSubtype)) {
2268            mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
2269        }
2270    }
2271
2272    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
2273            boolean setSubtypeOnly) {
2274        // Update the history of InputMethod and Subtype
2275        saveCurrentInputMethodAndSubtypeToHistory();
2276
2277        // Set Subtype here
2278        if (imi == null || subtypeId < 0) {
2279            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2280            mCurrentSubtype = null;
2281        } else {
2282            if (subtypeId < imi.getSubtypeCount()) {
2283                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
2284                mSettings.putSelectedSubtype(subtype.hashCode());
2285                mCurrentSubtype = subtype;
2286            } else {
2287                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2288                mCurrentSubtype = null;
2289            }
2290        }
2291
2292        if (!setSubtypeOnly) {
2293            // Set InputMethod here
2294            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
2295        }
2296    }
2297
2298    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
2299        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
2300        int lastSubtypeId = NOT_A_SUBTYPE_ID;
2301        // newDefaultIme is empty when there is no candidate for the selected IME.
2302        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
2303            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2304            if (subtypeHashCode != null) {
2305                try {
2306                    lastSubtypeId = getSubtypeIdFromHashCode(
2307                            imi, Integer.valueOf(subtypeHashCode));
2308                } catch (NumberFormatException e) {
2309                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2310                }
2311            }
2312        }
2313        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2314    }
2315
2316    private int getSelectedInputMethodSubtypeId(String id) {
2317        InputMethodInfo imi = mMethodMap.get(id);
2318        if (imi == null) {
2319            return NOT_A_SUBTYPE_ID;
2320        }
2321        int subtypeId;
2322        try {
2323            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
2324                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
2325        } catch (SettingNotFoundException e) {
2326            return NOT_A_SUBTYPE_ID;
2327        }
2328        return getSubtypeIdFromHashCode(imi, subtypeId);
2329    }
2330
2331    private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
2332        if (imi != null) {
2333            final int subtypeCount = imi.getSubtypeCount();
2334            for (int i = 0; i < subtypeCount; ++i) {
2335                InputMethodSubtype ims = imi.getSubtypeAt(i);
2336                if (subtypeHashCode == ims.hashCode()) {
2337                    return i;
2338                }
2339            }
2340        }
2341        return NOT_A_SUBTYPE_ID;
2342    }
2343
2344    private static ArrayList<InputMethodSubtype> getApplicableSubtypesLocked(
2345            Resources res, List<InputMethodSubtype> subtypes) {
2346        final String systemLocale = res.getConfiguration().locale.toString();
2347        if (TextUtils.isEmpty(systemLocale)) return new ArrayList<InputMethodSubtype>();
2348        HashMap<String, InputMethodSubtype> applicableModeAndSubtypesMap =
2349                new HashMap<String, InputMethodSubtype>();
2350        final int N = subtypes.size();
2351        boolean containsKeyboardSubtype = false;
2352        for (int i = 0; i < N; ++i) {
2353            InputMethodSubtype subtype = subtypes.get(i);
2354            final String locale = subtype.getLocale();
2355            final String mode = subtype.getMode();
2356            // When system locale starts with subtype's locale, that subtype will be applicable
2357            // for system locale
2358            // For instance, it's clearly applicable for cases like system locale = en_US and
2359            // subtype = en, but it is not necessarily considered applicable for cases like system
2360            // locale = en and subtype = en_US.
2361            // We just call systemLocale.startsWith(locale) in this function because there is no
2362            // need to find applicable subtypes aggressively unlike
2363            // findLastResortApplicableSubtypeLocked.
2364            if (systemLocale.startsWith(locale)) {
2365                InputMethodSubtype applicableSubtype = applicableModeAndSubtypesMap.get(mode);
2366                // If more applicable subtypes are contained, skip.
2367                if (applicableSubtype != null
2368                        && systemLocale.equals(applicableSubtype.getLocale())) continue;
2369                applicableModeAndSubtypesMap.put(mode, subtype);
2370                if (!containsKeyboardSubtype
2371                        && SUBTYPE_MODE_KEYBOARD.equalsIgnoreCase(subtype.getMode())) {
2372                    containsKeyboardSubtype = true;
2373                }
2374            }
2375        }
2376        final ArrayList<InputMethodSubtype> applicableSubtypes = new ArrayList<InputMethodSubtype>(
2377                applicableModeAndSubtypesMap.values());
2378        if (!containsKeyboardSubtype) {
2379            InputMethodSubtype lastResortKeyboardSubtype = findLastResortApplicableSubtypeLocked(
2380                    res, subtypes, SUBTYPE_MODE_KEYBOARD, systemLocale, true);
2381            if (lastResortKeyboardSubtype != null) {
2382                applicableSubtypes.add(lastResortKeyboardSubtype);
2383            }
2384        }
2385        return applicableSubtypes;
2386    }
2387
2388    /**
2389     * If there are no selected subtypes, tries finding the most applicable one according to the
2390     * given locale.
2391     * @param subtypes this function will search the most applicable subtype in subtypes
2392     * @param mode subtypes will be filtered by mode
2393     * @param locale subtypes will be filtered by locale
2394     * @param canIgnoreLocaleAsLastResort if this function can't find the most applicable subtype,
2395     * it will return the first subtype matched with mode
2396     * @return the most applicable subtypeId
2397     */
2398    private static InputMethodSubtype findLastResortApplicableSubtypeLocked(
2399            Resources res, List<InputMethodSubtype> subtypes, String mode, String locale,
2400            boolean canIgnoreLocaleAsLastResort) {
2401        if (subtypes == null || subtypes.size() == 0) {
2402            return null;
2403        }
2404        if (TextUtils.isEmpty(locale)) {
2405            locale = res.getConfiguration().locale.toString();
2406        }
2407        final String language = locale.substring(0, 2);
2408        boolean partialMatchFound = false;
2409        InputMethodSubtype applicableSubtype = null;
2410        InputMethodSubtype firstMatchedModeSubtype = null;
2411        final int N = subtypes.size();
2412        for (int i = 0; i < N; ++i) {
2413            InputMethodSubtype subtype = subtypes.get(i);
2414            final String subtypeLocale = subtype.getLocale();
2415            // An applicable subtype should match "mode". If mode is null, mode will be ignored,
2416            // and all subtypes with all modes can be candidates.
2417            if (mode == null || subtypes.get(i).getMode().equalsIgnoreCase(mode)) {
2418                if (firstMatchedModeSubtype == null) {
2419                    firstMatchedModeSubtype = subtype;
2420                }
2421                if (locale.equals(subtypeLocale)) {
2422                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
2423                    applicableSubtype = subtype;
2424                    break;
2425                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
2426                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
2427                    applicableSubtype = subtype;
2428                    partialMatchFound = true;
2429                }
2430            }
2431        }
2432
2433        if (applicableSubtype == null && canIgnoreLocaleAsLastResort) {
2434            return firstMatchedModeSubtype;
2435        }
2436
2437        // The first subtype applicable to the system locale will be defined as the most applicable
2438        // subtype.
2439        if (DEBUG) {
2440            if (applicableSubtype != null) {
2441                Slog.d(TAG, "Applicable InputMethodSubtype was found: "
2442                        + applicableSubtype.getMode() + "," + applicableSubtype.getLocale());
2443            }
2444        }
2445        return applicableSubtype;
2446    }
2447
2448    // If there are no selected shortcuts, tries finding the most applicable ones.
2449    private Pair<InputMethodInfo, InputMethodSubtype>
2450            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2451        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2452        InputMethodInfo mostApplicableIMI = null;
2453        InputMethodSubtype mostApplicableSubtype = null;
2454        boolean foundInSystemIME = false;
2455
2456        // Search applicable subtype for each InputMethodInfo
2457        for (InputMethodInfo imi: imis) {
2458            final String imiId = imi.getId();
2459            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2460                continue;
2461            }
2462            InputMethodSubtype subtype = null;
2463            final List<InputMethodSubtype> enabledSubtypes =
2464                    getEnabledInputMethodSubtypeList(imi, true);
2465            // 1. Search by the current subtype's locale from enabledSubtypes.
2466            if (mCurrentSubtype != null) {
2467                subtype = findLastResortApplicableSubtypeLocked(
2468                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
2469            }
2470            // 2. Search by the system locale from enabledSubtypes.
2471            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
2472            if (subtype == null) {
2473                subtype = findLastResortApplicableSubtypeLocked(
2474                        mRes, enabledSubtypes, mode, null, true);
2475            }
2476            // 4. Search by the current subtype's locale from all subtypes.
2477            if (subtype == null && mCurrentSubtype != null) {
2478                subtype = findLastResortApplicableSubtypeLocked(
2479                        mRes, getSubtypes(imi), mode, mCurrentSubtype.getLocale(), false);
2480            }
2481            // 5. Search by the system locale from all subtypes.
2482            // 6. Search the first enabled subtype matched with mode from all subtypes.
2483            if (subtype == null) {
2484                subtype = findLastResortApplicableSubtypeLocked(
2485                        mRes, getSubtypes(imi), mode, null, true);
2486            }
2487            if (subtype != null) {
2488                if (imiId.equals(mCurMethodId)) {
2489                    // The current input method is the most applicable IME.
2490                    mostApplicableIMI = imi;
2491                    mostApplicableSubtype = subtype;
2492                    break;
2493                } else if (!foundInSystemIME) {
2494                    // The system input method is 2nd applicable IME.
2495                    mostApplicableIMI = imi;
2496                    mostApplicableSubtype = subtype;
2497                    if ((imi.getServiceInfo().applicationInfo.flags
2498                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
2499                        foundInSystemIME = true;
2500                    }
2501                }
2502            }
2503        }
2504        if (DEBUG) {
2505            if (mostApplicableIMI != null) {
2506                Slog.w(TAG, "Most applicable shortcut input method was:"
2507                        + mostApplicableIMI.getId());
2508                if (mostApplicableSubtype != null) {
2509                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
2510                            + "," + mostApplicableSubtype.getMode() + ","
2511                            + mostApplicableSubtype.getLocale());
2512                }
2513            }
2514        }
2515        if (mostApplicableIMI != null) {
2516            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
2517                    mostApplicableSubtype);
2518        } else {
2519            return null;
2520        }
2521    }
2522
2523    /**
2524     * @return Return the current subtype of this input method.
2525     */
2526    @Override
2527    public InputMethodSubtype getCurrentInputMethodSubtype() {
2528        boolean subtypeIsSelected = false;
2529        try {
2530            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
2531                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
2532        } catch (SettingNotFoundException e) {
2533        }
2534        synchronized (mMethodMap) {
2535            if (!subtypeIsSelected || mCurrentSubtype == null) {
2536                String lastInputMethodId = Settings.Secure.getString(
2537                        mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
2538                int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
2539                if (subtypeId == NOT_A_SUBTYPE_ID) {
2540                    InputMethodInfo imi = mMethodMap.get(lastInputMethodId);
2541                    if (imi != null) {
2542                        // If there are no selected subtypes, the framework will try to find
2543                        // the most applicable subtype from explicitly or implicitly enabled
2544                        // subtypes.
2545                        List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
2546                                getEnabledInputMethodSubtypeList(imi, true);
2547                        // If there is only one explicitly or implicitly enabled subtype,
2548                        // just returns it.
2549                        if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
2550                            mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
2551                        } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
2552                            mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2553                                    mRes, explicitlyOrImplicitlyEnabledSubtypes,
2554                                    SUBTYPE_MODE_KEYBOARD, null, true);
2555                            if (mCurrentSubtype == null) {
2556                                mCurrentSubtype = findLastResortApplicableSubtypeLocked(
2557                                        mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
2558                                        true);
2559                            }
2560                        }
2561                    }
2562                } else {
2563                    mCurrentSubtype =
2564                            getSubtypes(mMethodMap.get(lastInputMethodId)).get(subtypeId);
2565                }
2566            }
2567            return mCurrentSubtype;
2568        }
2569    }
2570
2571    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
2572            InputMethodSubtype subtype) {
2573        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
2574            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
2575        } else {
2576            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
2577            subtypes.add(subtype);
2578            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
2579        }
2580    }
2581
2582    // TODO: We should change the return type from List to List<Parcelable>
2583    @Override
2584    public List getShortcutInputMethodsAndSubtypes() {
2585        synchronized (mMethodMap) {
2586            ArrayList<Object> ret = new ArrayList<Object>();
2587            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
2588                // If there are no selected shortcut subtypes, the framework will try to find
2589                // the most applicable subtype from all subtypes whose mode is
2590                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
2591                Pair<InputMethodInfo, InputMethodSubtype> info =
2592                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
2593                            SUBTYPE_MODE_VOICE);
2594                if (info != null) {
2595                    ret.add(info.first);
2596                    ret.add(info.second);
2597                }
2598                return ret;
2599            }
2600            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
2601                ret.add(imi);
2602                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
2603                    ret.add(subtype);
2604                }
2605            }
2606            return ret;
2607        }
2608    }
2609
2610    @Override
2611    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
2612        synchronized (mMethodMap) {
2613            if (subtype != null && mCurMethodId != null) {
2614                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2615                int subtypeId = getSubtypeIdFromHashCode(imi, subtype.hashCode());
2616                if (subtypeId != NOT_A_SUBTYPE_ID) {
2617                    setInputMethodLocked(mCurMethodId, subtypeId);
2618                    return true;
2619                }
2620            }
2621            return false;
2622        }
2623    }
2624
2625    /**
2626     * Utility class for putting and getting settings for InputMethod
2627     * TODO: Move all putters and getters of settings to this class.
2628     */
2629    private static class InputMethodSettings {
2630        // The string for enabled input method is saved as follows:
2631        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
2632        private static final char INPUT_METHOD_SEPARATER = ':';
2633        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
2634        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
2635                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
2636
2637        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
2638                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
2639
2640        private final Resources mRes;
2641        private final ContentResolver mResolver;
2642        private final HashMap<String, InputMethodInfo> mMethodMap;
2643        private final ArrayList<InputMethodInfo> mMethodList;
2644
2645        private String mEnabledInputMethodsStrCache;
2646
2647        private static void buildEnabledInputMethodsSettingString(
2648                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
2649            String id = pair.first;
2650            ArrayList<String> subtypes = pair.second;
2651            builder.append(id);
2652            // Inputmethod and subtypes are saved in the settings as follows:
2653            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
2654            for (String subtypeId: subtypes) {
2655                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
2656            }
2657        }
2658
2659        public InputMethodSettings(
2660                Resources res, ContentResolver resolver,
2661                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
2662            mRes = res;
2663            mResolver = resolver;
2664            mMethodMap = methodMap;
2665            mMethodList = methodList;
2666        }
2667
2668        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2669            return createEnabledInputMethodListLocked(
2670                    getEnabledInputMethodsAndSubtypeListLocked());
2671        }
2672
2673        public List<Pair<InputMethodInfo, ArrayList<String>>>
2674                getEnabledInputMethodAndSubtypeHashCodeListLocked() {
2675            return createEnabledInputMethodAndSubtypeHashCodeListLocked(
2676                    getEnabledInputMethodsAndSubtypeListLocked());
2677        }
2678
2679        public List<InputMethodSubtype> getEnabledInputMethodSubtypeListLocked(
2680                InputMethodInfo imi) {
2681            List<Pair<String, ArrayList<String>>> imsList =
2682                    getEnabledInputMethodsAndSubtypeListLocked();
2683            ArrayList<InputMethodSubtype> enabledSubtypes =
2684                    new ArrayList<InputMethodSubtype>();
2685            if (imi != null) {
2686                for (Pair<String, ArrayList<String>> imsPair : imsList) {
2687                    InputMethodInfo info = mMethodMap.get(imsPair.first);
2688                    if (info != null && info.getId().equals(imi.getId())) {
2689                        final int subtypeCount = info.getSubtypeCount();
2690                        for (int i = 0; i < subtypeCount; ++i) {
2691                            InputMethodSubtype ims = info.getSubtypeAt(i);
2692                            for (String s: imsPair.second) {
2693                                if (String.valueOf(ims.hashCode()).equals(s)) {
2694                                    enabledSubtypes.add(ims);
2695                                }
2696                            }
2697                        }
2698                        break;
2699                    }
2700                }
2701            }
2702            return enabledSubtypes;
2703        }
2704
2705        // At the initial boot, the settings for input methods are not set,
2706        // so we need to enable IME in that case.
2707        public void enableAllIMEsIfThereIsNoEnabledIME() {
2708            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2709                StringBuilder sb = new StringBuilder();
2710                final int N = mMethodList.size();
2711                for (int i = 0; i < N; i++) {
2712                    InputMethodInfo imi = mMethodList.get(i);
2713                    Slog.i(TAG, "Adding: " + imi.getId());
2714                    if (i > 0) sb.append(':');
2715                    sb.append(imi.getId());
2716                }
2717                putEnabledInputMethodsStr(sb.toString());
2718            }
2719        }
2720
2721        private List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
2722            ArrayList<Pair<String, ArrayList<String>>> imsList
2723                    = new ArrayList<Pair<String, ArrayList<String>>>();
2724            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2725            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2726                return imsList;
2727            }
2728            mInputMethodSplitter.setString(enabledInputMethodsStr);
2729            while (mInputMethodSplitter.hasNext()) {
2730                String nextImsStr = mInputMethodSplitter.next();
2731                mSubtypeSplitter.setString(nextImsStr);
2732                if (mSubtypeSplitter.hasNext()) {
2733                    ArrayList<String> subtypeHashes = new ArrayList<String>();
2734                    // The first element is ime id.
2735                    String imeId = mSubtypeSplitter.next();
2736                    while (mSubtypeSplitter.hasNext()) {
2737                        subtypeHashes.add(mSubtypeSplitter.next());
2738                    }
2739                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2740                }
2741            }
2742            return imsList;
2743        }
2744
2745        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2746            if (reloadInputMethodStr) {
2747                getEnabledInputMethodsStr();
2748            }
2749            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2750                // Add in the newly enabled input method.
2751                putEnabledInputMethodsStr(id);
2752            } else {
2753                putEnabledInputMethodsStr(
2754                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2755            }
2756        }
2757
2758        /**
2759         * Build and put a string of EnabledInputMethods with removing specified Id.
2760         * @return the specified id was removed or not.
2761         */
2762        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2763                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2764            boolean isRemoved = false;
2765            boolean needsAppendSeparator = false;
2766            for (Pair<String, ArrayList<String>> ims: imsList) {
2767                String curId = ims.first;
2768                if (curId.equals(id)) {
2769                    // We are disabling this input method, and it is
2770                    // currently enabled.  Skip it to remove from the
2771                    // new list.
2772                    isRemoved = true;
2773                } else {
2774                    if (needsAppendSeparator) {
2775                        builder.append(INPUT_METHOD_SEPARATER);
2776                    } else {
2777                        needsAppendSeparator = true;
2778                    }
2779                    buildEnabledInputMethodsSettingString(builder, ims);
2780                }
2781            }
2782            if (isRemoved) {
2783                // Update the setting with the new list of input methods.
2784                putEnabledInputMethodsStr(builder.toString());
2785            }
2786            return isRemoved;
2787        }
2788
2789        private List<InputMethodInfo> createEnabledInputMethodListLocked(
2790                List<Pair<String, ArrayList<String>>> imsList) {
2791            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2792            for (Pair<String, ArrayList<String>> ims: imsList) {
2793                InputMethodInfo info = mMethodMap.get(ims.first);
2794                if (info != null) {
2795                    res.add(info);
2796                }
2797            }
2798            return res;
2799        }
2800
2801        private List<Pair<InputMethodInfo, ArrayList<String>>>
2802                createEnabledInputMethodAndSubtypeHashCodeListLocked(
2803                        List<Pair<String, ArrayList<String>>> imsList) {
2804            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2805                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2806            for (Pair<String, ArrayList<String>> ims : imsList) {
2807                InputMethodInfo info = mMethodMap.get(ims.first);
2808                if (info != null) {
2809                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2810                }
2811            }
2812            return res;
2813        }
2814
2815        private void putEnabledInputMethodsStr(String str) {
2816            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2817            mEnabledInputMethodsStrCache = str;
2818        }
2819
2820        private String getEnabledInputMethodsStr() {
2821            mEnabledInputMethodsStrCache = Settings.Secure.getString(
2822                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
2823            if (DEBUG) {
2824                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2825            }
2826            return mEnabledInputMethodsStrCache;
2827        }
2828
2829        private void saveSubtypeHistory(
2830                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2831            StringBuilder builder = new StringBuilder();
2832            boolean isImeAdded = false;
2833            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2834                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2835                        newSubtypeId);
2836                isImeAdded = true;
2837            }
2838            for (Pair<String, String> ime: savedImes) {
2839                String imeId = ime.first;
2840                String subtypeId = ime.second;
2841                if (TextUtils.isEmpty(subtypeId)) {
2842                    subtypeId = NOT_A_SUBTYPE_ID_STR;
2843                }
2844                if (isImeAdded) {
2845                    builder.append(INPUT_METHOD_SEPARATER);
2846                } else {
2847                    isImeAdded = true;
2848                }
2849                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2850                        subtypeId);
2851            }
2852            // Remove the last INPUT_METHOD_SEPARATER
2853            putSubtypeHistoryStr(builder.toString());
2854        }
2855
2856        public void addSubtypeToHistory(String imeId, String subtypeId) {
2857            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2858            for (Pair<String, String> ime: subtypeHistory) {
2859                if (ime.first.equals(imeId)) {
2860                    if (DEBUG) {
2861                        Slog.v(TAG, "Subtype found in the history: " + imeId + ", "
2862                                + ime.second);
2863                    }
2864                    // We should break here
2865                    subtypeHistory.remove(ime);
2866                    break;
2867                }
2868            }
2869            if (DEBUG) {
2870                Slog.v(TAG, "Add subtype to the history: " + imeId + ", " + subtypeId);
2871            }
2872            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2873        }
2874
2875        private void putSubtypeHistoryStr(String str) {
2876            if (DEBUG) {
2877                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2878            }
2879            Settings.Secure.putString(
2880                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2881        }
2882
2883        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2884            // Gets the first one from the history
2885            return getLastSubtypeForInputMethodLockedInternal(null);
2886        }
2887
2888        public String getLastSubtypeForInputMethodLocked(String imeId) {
2889            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2890            if (ime != null) {
2891                return ime.second;
2892            } else {
2893                return null;
2894            }
2895        }
2896
2897        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2898            List<Pair<String, ArrayList<String>>> enabledImes =
2899                    getEnabledInputMethodsAndSubtypeListLocked();
2900            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2901            for (Pair<String, String> imeAndSubtype : subtypeHistory) {
2902                final String imeInTheHistory = imeAndSubtype.first;
2903                // If imeId is empty, returns the first IME and subtype in the history
2904                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2905                    final String subtypeInTheHistory = imeAndSubtype.second;
2906                    final String subtypeHashCode =
2907                            getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(
2908                                    enabledImes, imeInTheHistory, subtypeInTheHistory);
2909                    if (!TextUtils.isEmpty(subtypeHashCode)) {
2910                        if (DEBUG) {
2911                            Slog.d(TAG, "Enabled subtype found in the history: " + subtypeHashCode);
2912                        }
2913                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2914                    }
2915                }
2916            }
2917            if (DEBUG) {
2918                Slog.d(TAG, "No enabled IME found in the history");
2919            }
2920            return null;
2921        }
2922
2923        private String getEnabledSubtypeHashCodeForInputMethodAndSubtypeLocked(List<Pair<String,
2924                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2925            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2926                if (enabledIme.first.equals(imeId)) {
2927                    final ArrayList<String> explicitlyEnabledSubtypes = enabledIme.second;
2928                    if (explicitlyEnabledSubtypes.size() == 0) {
2929                        // If there are no explicitly enabled subtypes, applicable subtypes are
2930                        // enabled implicitly.
2931                        InputMethodInfo ime = mMethodMap.get(imeId);
2932                        // If IME is enabled and no subtypes are enabled, applicable subtypes
2933                        // are enabled implicitly, so needs to treat them to be enabled.
2934                        if (ime != null && ime.getSubtypeCount() > 0) {
2935                            List<InputMethodSubtype> implicitlySelectedSubtypes =
2936                                    getApplicableSubtypesLocked(mRes, getSubtypes(ime));
2937                            if (implicitlySelectedSubtypes != null) {
2938                                final int N = implicitlySelectedSubtypes.size();
2939                                for (int i = 0; i < N; ++i) {
2940                                    final InputMethodSubtype st = implicitlySelectedSubtypes.get(i);
2941                                    if (String.valueOf(st.hashCode()).equals(subtypeHashCode)) {
2942                                        return subtypeHashCode;
2943                                    }
2944                                }
2945                            }
2946                        }
2947                    } else {
2948                        for (String s: explicitlyEnabledSubtypes) {
2949                            if (s.equals(subtypeHashCode)) {
2950                                // If both imeId and subtypeId are enabled, return subtypeId.
2951                                return s;
2952                            }
2953                        }
2954                    }
2955                    // If imeId was enabled but subtypeId was disabled.
2956                    return NOT_A_SUBTYPE_ID_STR;
2957                }
2958            }
2959            // If both imeId and subtypeId are disabled, return null
2960            return null;
2961        }
2962
2963        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2964            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2965            final String subtypeHistoryStr = getSubtypeHistoryStr();
2966            if (TextUtils.isEmpty(subtypeHistoryStr)) {
2967                return imsList;
2968            }
2969            mInputMethodSplitter.setString(subtypeHistoryStr);
2970            while (mInputMethodSplitter.hasNext()) {
2971                String nextImsStr = mInputMethodSplitter.next();
2972                mSubtypeSplitter.setString(nextImsStr);
2973                if (mSubtypeSplitter.hasNext()) {
2974                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
2975                    // The first element is ime id.
2976                    String imeId = mSubtypeSplitter.next();
2977                    while (mSubtypeSplitter.hasNext()) {
2978                        subtypeId = mSubtypeSplitter.next();
2979                        break;
2980                    }
2981                    imsList.add(new Pair<String, String>(imeId, subtypeId));
2982                }
2983            }
2984            return imsList;
2985        }
2986
2987        private String getSubtypeHistoryStr() {
2988            if (DEBUG) {
2989                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2990                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2991            }
2992            return Settings.Secure.getString(
2993                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2994        }
2995
2996        public void putSelectedInputMethod(String imeId) {
2997            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2998        }
2999
3000        public void putSelectedSubtype(int subtypeId) {
3001            Settings.Secure.putInt(
3002                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
3003        }
3004    }
3005
3006    private static class InputMethodFileManager {
3007        private static final String SYSTEM_PATH = "system";
3008        private static final String INPUT_METHOD_PATH = "inputmethod";
3009        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3010        private static final String NODE_SUBTYPES = "subtypes";
3011        private static final String NODE_SUBTYPE = "subtype";
3012        private static final String NODE_IMI = "imi";
3013        private static final String ATTR_ID = "id";
3014        private static final String ATTR_LABEL = "label";
3015        private static final String ATTR_ICON = "icon";
3016        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3017        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3018        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3019        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3020        private final AtomicFile mAdditionalInputMethodSubtypeFile;
3021        private final HashMap<String, InputMethodInfo> mMethodMap;
3022        private final HashMap<String, List<InputMethodSubtype>> mSubtypesMap =
3023                new HashMap<String, List<InputMethodSubtype>>();
3024        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap) {
3025            if (methodMap == null) {
3026                throw new NullPointerException("methodMap is null");
3027            }
3028            mMethodMap = methodMap;
3029            final File systemDir = new File(Environment.getDataDirectory(), SYSTEM_PATH);
3030            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3031            if (!inputMethodDir.mkdirs()) {
3032                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3033            }
3034            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3035            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3036            if (!subtypeFile.exists()) {
3037                // If "subtypes.xml" doesn't exist, create a blank file.
3038                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3039                        methodMap);
3040            } else {
3041                readAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile);
3042            }
3043        }
3044
3045        private void deleteAllInputMethodSubtypes(String imiId) {
3046            synchronized (mMethodMap) {
3047                mSubtypesMap.remove(imiId);
3048                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3049                        mMethodMap);
3050            }
3051        }
3052
3053        public void addInputMethodSubtypes(
3054                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3055            synchronized (mMethodMap) {
3056                final HashSet<InputMethodSubtype> existingSubtypes =
3057                        new HashSet<InputMethodSubtype>();
3058                for (int i = 0; i < imi.getSubtypeCount(); ++i) {
3059                    existingSubtypes.add(imi.getSubtypeAt(i));
3060                }
3061
3062                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3063                final int N = additionalSubtypes.length;
3064                for (int i = 0; i < N; ++i) {
3065                    final InputMethodSubtype subtype = additionalSubtypes[i];
3066                    if (!subtypes.contains(subtype) && !existingSubtypes.contains(subtype)) {
3067                        subtypes.add(subtype);
3068                    }
3069                }
3070                mSubtypesMap.put(imi.getId(), subtypes);
3071                writeAdditionalInputMethodSubtypes(mSubtypesMap, mAdditionalInputMethodSubtypeFile,
3072                        mMethodMap);
3073            }
3074        }
3075
3076        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3077            synchronized (mMethodMap) {
3078                return mSubtypesMap;
3079            }
3080        }
3081
3082        private static void writeAdditionalInputMethodSubtypes(
3083                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3084                HashMap<String, InputMethodInfo> methodMap) {
3085            // Safety net for the case that this function is called before methodMap is set.
3086            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3087            FileOutputStream fos = null;
3088            try {
3089                fos = subtypesFile.startWrite();
3090                final XmlSerializer out = new FastXmlSerializer();
3091                out.setOutput(fos, "utf-8");
3092                out.startDocument(null, true);
3093                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3094                out.startTag(null, NODE_SUBTYPES);
3095                for (String imiId : allSubtypes.keySet()) {
3096                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3097                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3098                        continue;
3099                    }
3100                    out.startTag(null, NODE_IMI);
3101                    out.attribute(null, ATTR_ID, imiId);
3102                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3103                    final int N = subtypesList.size();
3104                    for (int i = 0; i < N; ++i) {
3105                        final InputMethodSubtype subtype = subtypesList.get(i);
3106                        out.startTag(null, NODE_SUBTYPE);
3107                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3108                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3109                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3110                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3111                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3112                        out.attribute(null, ATTR_IS_AUXILIARY,
3113                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3114                        out.endTag(null, NODE_SUBTYPE);
3115                    }
3116                    out.endTag(null, NODE_IMI);
3117                }
3118                out.endTag(null, NODE_SUBTYPES);
3119                out.endDocument();
3120                subtypesFile.finishWrite(fos);
3121            } catch (java.io.IOException e) {
3122                Slog.w(TAG, "Error writing subtypes", e);
3123                if (fos != null) {
3124                    subtypesFile.failWrite(fos);
3125                }
3126            }
3127        }
3128
3129        private static void readAdditionalInputMethodSubtypes(
3130                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3131            if (allSubtypes == null || subtypesFile == null) return;
3132            allSubtypes.clear();
3133            FileInputStream fis = null;
3134            try {
3135                fis = subtypesFile.openRead();
3136                final XmlPullParser parser = Xml.newPullParser();
3137                parser.setInput(fis, null);
3138                int type = parser.getEventType();
3139                // Skip parsing until START_TAG
3140                while ((type = parser.next()) != XmlPullParser.START_TAG
3141                        && type != XmlPullParser.END_DOCUMENT) {}
3142                String firstNodeName = parser.getName();
3143                if (!NODE_SUBTYPES.equals(firstNodeName)) {
3144                    throw new XmlPullParserException("Xml doesn't start with subtypes");
3145                }
3146                final int depth =parser.getDepth();
3147                String currentImiId = null;
3148                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3149                while (((type = parser.next()) != XmlPullParser.END_TAG
3150                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3151                    if (type != XmlPullParser.START_TAG)
3152                        continue;
3153                    final String nodeName = parser.getName();
3154                    if (NODE_IMI.equals(nodeName)) {
3155                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
3156                        if (TextUtils.isEmpty(currentImiId)) {
3157                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3158                            continue;
3159                        }
3160                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3161                        allSubtypes.put(currentImiId, tempSubtypesArray);
3162                    } else if (NODE_SUBTYPE.equals(nodeName)) {
3163                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3164                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3165                            continue;
3166                        }
3167                        final int icon = Integer.valueOf(
3168                                parser.getAttributeValue(null, ATTR_ICON));
3169                        final int label = Integer.valueOf(
3170                                parser.getAttributeValue(null, ATTR_LABEL));
3171                        final String imeSubtypeLocale =
3172                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3173                        final String imeSubtypeMode =
3174                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3175                        final String imeSubtypeExtraValue =
3176                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3177                        final boolean isAuxiliary = "1".equals(String.valueOf(
3178                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3179                        final InputMethodSubtype subtype =
3180                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
3181                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
3182                        tempSubtypesArray.add(subtype);
3183                    }
3184                }
3185            } catch (XmlPullParserException e) {
3186                Slog.w(TAG, "Error reading subtypes: " + e);
3187                return;
3188            } catch (java.io.IOException e) {
3189                Slog.w(TAG, "Error reading subtypes: " + e);
3190                return;
3191            } catch (NumberFormatException e) {
3192                Slog.w(TAG, "Error reading subtypes: " + e);
3193                return;
3194            } finally {
3195                if (fis != null) {
3196                    try {
3197                        fis.close();
3198                    } catch (java.io.IOException e1) {
3199                        Slog.w(TAG, "Failed to close.");
3200                    }
3201                }
3202            }
3203        }
3204    }
3205
3206    // ----------------------------------------------------------------------
3207
3208    @Override
3209    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3210        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3211                != PackageManager.PERMISSION_GRANTED) {
3212
3213            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3214                    + Binder.getCallingPid()
3215                    + ", uid=" + Binder.getCallingUid());
3216            return;
3217        }
3218
3219        IInputMethod method;
3220        ClientState client;
3221
3222        final Printer p = new PrintWriterPrinter(pw);
3223
3224        synchronized (mMethodMap) {
3225            p.println("Current Input Method Manager state:");
3226            int N = mMethodList.size();
3227            p.println("  Input Methods:");
3228            for (int i=0; i<N; i++) {
3229                InputMethodInfo info = mMethodList.get(i);
3230                p.println("  InputMethod #" + i + ":");
3231                info.dump(p, "    ");
3232            }
3233            p.println("  Clients:");
3234            for (ClientState ci : mClients.values()) {
3235                p.println("  Client " + ci + ":");
3236                p.println("    client=" + ci.client);
3237                p.println("    inputContext=" + ci.inputContext);
3238                p.println("    sessionRequested=" + ci.sessionRequested);
3239                p.println("    curSession=" + ci.curSession);
3240            }
3241            p.println("  mCurMethodId=" + mCurMethodId);
3242            client = mCurClient;
3243            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
3244            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
3245            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3246                    + " mBoundToMethod=" + mBoundToMethod);
3247            p.println("  mCurToken=" + mCurToken);
3248            p.println("  mCurIntent=" + mCurIntent);
3249            method = mCurMethod;
3250            p.println("  mCurMethod=" + mCurMethod);
3251            p.println("  mEnabledSession=" + mEnabledSession);
3252            p.println("  mShowRequested=" + mShowRequested
3253                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3254                    + " mShowForced=" + mShowForced
3255                    + " mInputShown=" + mInputShown);
3256            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
3257        }
3258
3259        p.println(" ");
3260        if (client != null) {
3261            pw.flush();
3262            try {
3263                client.client.asBinder().dump(fd, args);
3264            } catch (RemoteException e) {
3265                p.println("Input method client dead: " + e);
3266            }
3267        } else {
3268            p.println("No input method client.");
3269        }
3270
3271        p.println(" ");
3272        if (method != null) {
3273            pw.flush();
3274            try {
3275                method.asBinder().dump(fd, args);
3276            } catch (RemoteException e) {
3277                p.println("Input method service dead: " + e);
3278            }
3279        } else {
3280            p.println("No input method service.");
3281        }
3282    }
3283}
3284