InputMethodManagerService.java revision 03eb319a3a7fe6fe9ab9eba6fc1f727285850906
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.HandlerCaller;
21import com.android.internal.view.IInputContext;
22import com.android.internal.view.IInputMethod;
23import com.android.internal.view.IInputMethodCallback;
24import com.android.internal.view.IInputMethodClient;
25import com.android.internal.view.IInputMethodManager;
26import com.android.internal.view.IInputMethodSession;
27import com.android.internal.view.InputBindResult;
28
29import com.android.server.StatusBarManagerService;
30
31import org.xmlpull.v1.XmlPullParserException;
32
33import android.app.ActivityManagerNative;
34import android.app.AlertDialog;
35import android.app.PendingIntent;
36import android.content.ComponentName;
37import android.content.ContentResolver;
38import android.content.Context;
39import android.content.DialogInterface;
40import android.content.IntentFilter;
41import android.content.DialogInterface.OnCancelListener;
42import android.content.Intent;
43import android.content.ServiceConnection;
44import android.content.pm.ApplicationInfo;
45import android.content.pm.PackageManager;
46import android.content.pm.ResolveInfo;
47import android.content.pm.ServiceInfo;
48import android.content.res.Configuration;
49import android.content.res.Resources;
50import android.content.res.TypedArray;
51import android.database.ContentObserver;
52import android.os.Binder;
53import android.os.Handler;
54import android.os.IBinder;
55import android.os.IInterface;
56import android.os.Message;
57import android.os.Parcel;
58import android.os.RemoteException;
59import android.os.ResultReceiver;
60import android.os.ServiceManager;
61import android.os.SystemClock;
62import android.provider.Settings;
63import android.provider.Settings.Secure;
64import android.provider.Settings.SettingNotFoundException;
65import android.text.TextUtils;
66import android.util.EventLog;
67import android.util.Pair;
68import android.util.Slog;
69import android.util.PrintWriterPrinter;
70import android.util.Printer;
71import android.view.IWindowManager;
72import android.view.WindowManager;
73import android.view.inputmethod.EditorInfo;
74import android.view.inputmethod.InputBinding;
75import android.view.inputmethod.InputMethod;
76import android.view.inputmethod.InputMethodInfo;
77import android.view.inputmethod.InputMethodManager;
78import android.view.inputmethod.InputMethodSubtype;
79
80import java.io.FileDescriptor;
81import java.io.IOException;
82import java.io.PrintWriter;
83import java.text.Collator;
84import java.util.ArrayList;
85import java.util.HashMap;
86import java.util.HashSet;
87import java.util.List;
88import java.util.Map;
89import java.util.TreeMap;
90
91/**
92 * This class provides a system service that manages input methods.
93 */
94public class InputMethodManagerService extends IInputMethodManager.Stub
95        implements ServiceConnection, Handler.Callback {
96    static final boolean DEBUG = false;
97    static final String TAG = "InputManagerService";
98
99    static final int MSG_SHOW_IM_PICKER = 1;
100    static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
101    static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
102
103    static final int MSG_UNBIND_INPUT = 1000;
104    static final int MSG_BIND_INPUT = 1010;
105    static final int MSG_SHOW_SOFT_INPUT = 1020;
106    static final int MSG_HIDE_SOFT_INPUT = 1030;
107    static final int MSG_ATTACH_TOKEN = 1040;
108    static final int MSG_CREATE_SESSION = 1050;
109
110    static final int MSG_START_INPUT = 2000;
111    static final int MSG_RESTART_INPUT = 2010;
112
113    static final int MSG_UNBIND_METHOD = 3000;
114    static final int MSG_BIND_METHOD = 3010;
115
116    static final long TIME_TO_RECONNECT = 10*1000;
117
118    private static final int NOT_A_SUBTYPE_ID = -1;
119    private static final String NOT_A_SUBTYPE_ID_STR = String.valueOf(NOT_A_SUBTYPE_ID);
120    // If IME doesn't support the system locale, the default subtype will be the first defined one.
121    private static final int DEFAULT_SUBTYPE_ID = 0;
122
123    final Context mContext;
124    final Handler mHandler;
125    final InputMethodSettings mSettings;
126    final SettingsObserver mSettingsObserver;
127    final StatusBarManagerService mStatusBar;
128    final IWindowManager mIWindowManager;
129    final HandlerCaller mCaller;
130
131    final InputBindResult mNoBinding = new InputBindResult(null, null, -1);
132
133    // All known input methods.  mMethodMap also serves as the global
134    // lock for this class.
135    final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
136    final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
137
138    class SessionState {
139        final ClientState client;
140        final IInputMethod method;
141        final IInputMethodSession session;
142
143        @Override
144        public String toString() {
145            return "SessionState{uid " + client.uid + " pid " + client.pid
146                    + " method " + Integer.toHexString(
147                            System.identityHashCode(method))
148                    + " session " + Integer.toHexString(
149                            System.identityHashCode(session))
150                    + "}";
151        }
152
153        SessionState(ClientState _client, IInputMethod _method,
154                IInputMethodSession _session) {
155            client = _client;
156            method = _method;
157            session = _session;
158        }
159    }
160
161    class ClientState {
162        final IInputMethodClient client;
163        final IInputContext inputContext;
164        final int uid;
165        final int pid;
166        final InputBinding binding;
167
168        boolean sessionRequested;
169        SessionState curSession;
170
171        @Override
172        public String toString() {
173            return "ClientState{" + Integer.toHexString(
174                    System.identityHashCode(this)) + " uid " + uid
175                    + " pid " + pid + "}";
176        }
177
178        ClientState(IInputMethodClient _client, IInputContext _inputContext,
179                int _uid, int _pid) {
180            client = _client;
181            inputContext = _inputContext;
182            uid = _uid;
183            pid = _pid;
184            binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
185        }
186    }
187
188    final HashMap<IBinder, ClientState> mClients
189            = new HashMap<IBinder, ClientState>();
190
191    /**
192     * Set once the system is ready to run third party code.
193     */
194    boolean mSystemReady;
195
196    /**
197     * Id of the currently selected input method.
198     */
199    String mCurMethodId;
200
201    /**
202     * The current binding sequence number, incremented every time there is
203     * a new bind performed.
204     */
205    int mCurSeq;
206
207    /**
208     * The client that is currently bound to an input method.
209     */
210    ClientState mCurClient;
211
212    /**
213     * The last window token that gained focus.
214     */
215    IBinder mCurFocusedWindow;
216
217    /**
218     * The input context last provided by the current client.
219     */
220    IInputContext mCurInputContext;
221
222    /**
223     * The attributes last provided by the current client.
224     */
225    EditorInfo mCurAttribute;
226
227    /**
228     * The input method ID of the input method service that we are currently
229     * connected to or in the process of connecting to.
230     */
231    String mCurId;
232
233    /**
234     * The current subtype of the current input method.
235     */
236    private InputMethodSubtype mCurrentSubtype;
237
238
239    /**
240     * Set to true if our ServiceConnection is currently actively bound to
241     * a service (whether or not we have gotten its IBinder back yet).
242     */
243    boolean mHaveConnection;
244
245    /**
246     * Set if the client has asked for the input method to be shown.
247     */
248    boolean mShowRequested;
249
250    /**
251     * Set if we were explicitly told to show the input method.
252     */
253    boolean mShowExplicitlyRequested;
254
255    /**
256     * Set if we were forced to be shown.
257     */
258    boolean mShowForced;
259
260    /**
261     * Set if we last told the input method to show itself.
262     */
263    boolean mInputShown;
264
265    /**
266     * The Intent used to connect to the current input method.
267     */
268    Intent mCurIntent;
269
270    /**
271     * The token we have made for the currently active input method, to
272     * identify it in the future.
273     */
274    IBinder mCurToken;
275
276    /**
277     * If non-null, this is the input method service we are currently connected
278     * to.
279     */
280    IInputMethod mCurMethod;
281
282    /**
283     * Time that we last initiated a bind to the input method, to determine
284     * if we should try to disconnect and reconnect to it.
285     */
286    long mLastBindTime;
287
288    /**
289     * Have we called mCurMethod.bindInput()?
290     */
291    boolean mBoundToMethod;
292
293    /**
294     * Currently enabled session.  Only touched by service thread, not
295     * protected by a lock.
296     */
297    SessionState mEnabledSession;
298
299    /**
300     * True if the screen is on.  The value is true initially.
301     */
302    boolean mScreenOn = true;
303
304    AlertDialog.Builder mDialogBuilder;
305    AlertDialog mSwitchingDialog;
306    InputMethodInfo[] mIms;
307    CharSequence[] mItems;
308    int[] mSubtypeIds;
309
310    class SettingsObserver extends ContentObserver {
311        SettingsObserver(Handler handler) {
312            super(handler);
313            ContentResolver resolver = mContext.getContentResolver();
314            resolver.registerContentObserver(Settings.Secure.getUriFor(
315                    Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
316            resolver.registerContentObserver(Settings.Secure.getUriFor(
317                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
318        }
319
320        @Override public void onChange(boolean selfChange) {
321            synchronized (mMethodMap) {
322                updateFromSettingsLocked();
323            }
324        }
325    }
326
327    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
328        @Override
329        public void onReceive(Context context, Intent intent) {
330            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
331                mScreenOn = true;
332            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
333                mScreenOn = false;
334            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
335                hideInputMethodMenu();
336                return;
337            } else {
338                Slog.w(TAG, "Unexpected intent " + intent);
339            }
340
341            // Inform the current client of the change in active status
342            try {
343                if (mCurClient != null && mCurClient.client != null) {
344                    mCurClient.client.setActive(mScreenOn);
345                }
346            } catch (RemoteException e) {
347                Slog.w(TAG, "Got RemoteException sending 'screen on/off' notification to pid "
348                        + mCurClient.pid + " uid " + mCurClient.uid);
349            }
350        }
351    }
352
353    class MyPackageMonitor extends PackageMonitor {
354
355        @Override
356        public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
357            synchronized (mMethodMap) {
358                String curInputMethodId = Settings.Secure.getString(mContext
359                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
360                final int N = mMethodList.size();
361                if (curInputMethodId != null) {
362                    for (int i=0; i<N; i++) {
363                        InputMethodInfo imi = mMethodList.get(i);
364                        if (imi.getId().equals(curInputMethodId)) {
365                            for (String pkg : packages) {
366                                if (imi.getPackageName().equals(pkg)) {
367                                    if (!doit) {
368                                        return true;
369                                    }
370                                    resetSelectedInputMethodAndSubtypeLocked("");
371                                    chooseNewDefaultIMELocked();
372                                    return true;
373                                }
374                            }
375                        }
376                    }
377                }
378            }
379            return false;
380        }
381
382        @Override
383        public void onSomePackagesChanged() {
384            synchronized (mMethodMap) {
385                InputMethodInfo curIm = null;
386                String curInputMethodId = Settings.Secure.getString(mContext
387                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
388                final int N = mMethodList.size();
389                if (curInputMethodId != null) {
390                    for (int i=0; i<N; i++) {
391                        InputMethodInfo imi = mMethodList.get(i);
392                        if (imi.getId().equals(curInputMethodId)) {
393                            curIm = imi;
394                        }
395                        int change = isPackageDisappearing(imi.getPackageName());
396                        if (change == PACKAGE_TEMPORARY_CHANGE
397                                || change == PACKAGE_PERMANENT_CHANGE) {
398                            Slog.i(TAG, "Input method uninstalled, disabling: "
399                                    + imi.getComponent());
400                            setInputMethodEnabledLocked(imi.getId(), false);
401                        }
402                    }
403                }
404
405                buildInputMethodListLocked(mMethodList, mMethodMap);
406
407                boolean changed = false;
408
409                if (curIm != null) {
410                    int change = isPackageDisappearing(curIm.getPackageName());
411                    if (change == PACKAGE_TEMPORARY_CHANGE
412                            || change == PACKAGE_PERMANENT_CHANGE) {
413                        ServiceInfo si = null;
414                        try {
415                            si = mContext.getPackageManager().getServiceInfo(
416                                    curIm.getComponent(), 0);
417                        } catch (PackageManager.NameNotFoundException ex) {
418                        }
419                        if (si == null) {
420                            // Uh oh, current input method is no longer around!
421                            // Pick another one...
422                            Slog.i(TAG, "Current input method removed: " + curInputMethodId);
423                            if (!chooseNewDefaultIMELocked()) {
424                                changed = true;
425                                curIm = null;
426                                Slog.i(TAG, "Unsetting current input method");
427                                resetSelectedInputMethodAndSubtypeLocked("");
428                            }
429                        }
430                    }
431                }
432
433                if (curIm == null) {
434                    // We currently don't have a default input method... is
435                    // one now available?
436                    changed = chooseNewDefaultIMELocked();
437                }
438
439                if (changed) {
440                    updateFromSettingsLocked();
441                }
442            }
443        }
444    }
445
446    class MethodCallback extends IInputMethodCallback.Stub {
447        final IInputMethod mMethod;
448
449        MethodCallback(IInputMethod method) {
450            mMethod = method;
451        }
452
453        public void finishedEvent(int seq, boolean handled) throws RemoteException {
454        }
455
456        public void sessionCreated(IInputMethodSession session) throws RemoteException {
457            onSessionCreated(mMethod, session);
458        }
459    }
460
461    public InputMethodManagerService(Context context, StatusBarManagerService statusBar) {
462        mContext = context;
463        mHandler = new Handler(this);
464        mIWindowManager = IWindowManager.Stub.asInterface(
465                ServiceManager.getService(Context.WINDOW_SERVICE));
466        mCaller = new HandlerCaller(context, new HandlerCaller.Callback() {
467            public void executeMessage(Message msg) {
468                handleMessage(msg);
469            }
470        });
471
472        (new MyPackageMonitor()).register(mContext, true);
473
474        IntentFilter screenOnOffFilt = new IntentFilter();
475        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
476        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
477        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
478        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
479
480        mStatusBar = statusBar;
481        statusBar.setIconVisibility("ime", false);
482
483        // mSettings should be created before buildInputMethodListLocked
484        mSettings = new InputMethodSettings(context.getContentResolver(), mMethodMap, mMethodList);
485        buildInputMethodListLocked(mMethodList, mMethodMap);
486        mSettings.enableAllIMEsIfThereIsNoEnabledIME();
487
488        if (TextUtils.isEmpty(Settings.Secure.getString(
489                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD))) {
490            InputMethodInfo defIm = null;
491            for (InputMethodInfo imi: mMethodList) {
492                if (defIm == null && imi.getIsDefaultResourceId() != 0) {
493                    try {
494                        Resources res = context.createPackageContext(
495                                imi.getPackageName(), 0).getResources();
496                        if (res.getBoolean(imi.getIsDefaultResourceId())) {
497                            defIm = imi;
498                            Slog.i(TAG, "Selected default: " + imi.getId());
499                        }
500                    } catch (PackageManager.NameNotFoundException ex) {
501                    } catch (Resources.NotFoundException ex) {
502                    }
503                }
504            }
505            if (defIm == null && mMethodList.size() > 0) {
506                defIm = mMethodList.get(0);
507                Slog.i(TAG, "No default found, using " + defIm.getId());
508            }
509            if (defIm != null) {
510                setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
511            }
512        }
513
514        mSettingsObserver = new SettingsObserver(mHandler);
515        updateFromSettingsLocked();
516    }
517
518    @Override
519    public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
520            throws RemoteException {
521        try {
522            return super.onTransact(code, data, reply, flags);
523        } catch (RuntimeException e) {
524            // The input method manager only throws security exceptions, so let's
525            // log all others.
526            if (!(e instanceof SecurityException)) {
527                Slog.e(TAG, "Input Method Manager Crash", e);
528            }
529            throw e;
530        }
531    }
532
533    public void systemReady() {
534        synchronized (mMethodMap) {
535            if (!mSystemReady) {
536                mSystemReady = true;
537                try {
538                    startInputInnerLocked();
539                } catch (RuntimeException e) {
540                    Slog.w(TAG, "Unexpected exception", e);
541                }
542            }
543        }
544    }
545
546    public List<InputMethodInfo> getInputMethodList() {
547        synchronized (mMethodMap) {
548            return new ArrayList<InputMethodInfo>(mMethodList);
549        }
550    }
551
552    public List<InputMethodInfo> getEnabledInputMethodList() {
553        synchronized (mMethodMap) {
554            return mSettings.getEnabledInputMethodListLocked();
555        }
556    }
557
558    public void addClient(IInputMethodClient client,
559            IInputContext inputContext, int uid, int pid) {
560        synchronized (mMethodMap) {
561            mClients.put(client.asBinder(), new ClientState(client,
562                    inputContext, uid, pid));
563        }
564    }
565
566    public void removeClient(IInputMethodClient client) {
567        synchronized (mMethodMap) {
568            mClients.remove(client.asBinder());
569        }
570    }
571
572    void executeOrSendMessage(IInterface target, Message msg) {
573         if (target.asBinder() instanceof Binder) {
574             mCaller.sendMessage(msg);
575         } else {
576             handleMessage(msg);
577             msg.recycle();
578         }
579    }
580
581    void unbindCurrentClientLocked() {
582        if (mCurClient != null) {
583            if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
584                    + mCurClient.client.asBinder());
585            if (mBoundToMethod) {
586                mBoundToMethod = false;
587                if (mCurMethod != null) {
588                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
589                            MSG_UNBIND_INPUT, mCurMethod));
590                }
591            }
592            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
593                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
594            mCurClient.sessionRequested = false;
595
596            // Call setActive(false) on the old client
597            try {
598                mCurClient.client.setActive(false);
599            } catch (RemoteException e) {
600                Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
601                        + mCurClient.pid + " uid " + mCurClient.uid);
602            }
603            mCurClient = null;
604
605            hideInputMethodMenuLocked();
606        }
607    }
608
609    private int getImeShowFlags() {
610        int flags = 0;
611        if (mShowForced) {
612            flags |= InputMethod.SHOW_FORCED
613                    | InputMethod.SHOW_EXPLICIT;
614        } else if (mShowExplicitlyRequested) {
615            flags |= InputMethod.SHOW_EXPLICIT;
616        }
617        return flags;
618    }
619
620    private int getAppShowFlags() {
621        int flags = 0;
622        if (mShowForced) {
623            flags |= InputMethodManager.SHOW_FORCED;
624        } else if (!mShowExplicitlyRequested) {
625            flags |= InputMethodManager.SHOW_IMPLICIT;
626        }
627        return flags;
628    }
629
630    InputBindResult attachNewInputLocked(boolean initial, boolean needResult) {
631        if (!mBoundToMethod) {
632            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
633                    MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
634            mBoundToMethod = true;
635        }
636        final SessionState session = mCurClient.curSession;
637        if (initial) {
638            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
639                    MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
640        } else {
641            executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
642                    MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
643        }
644        if (mShowRequested) {
645            if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
646            showCurrentInputLocked(getAppShowFlags(), null);
647        }
648        return needResult
649                ? new InputBindResult(session.session, mCurId, mCurSeq)
650                : null;
651    }
652
653    InputBindResult startInputLocked(IInputMethodClient client,
654            IInputContext inputContext, EditorInfo attribute,
655            boolean initial, boolean needResult) {
656        // If no method is currently selected, do nothing.
657        if (mCurMethodId == null) {
658            return mNoBinding;
659        }
660
661        ClientState cs = mClients.get(client.asBinder());
662        if (cs == null) {
663            throw new IllegalArgumentException("unknown client "
664                    + client.asBinder());
665        }
666
667        try {
668            if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
669                // Check with the window manager to make sure this client actually
670                // has a window with focus.  If not, reject.  This is thread safe
671                // because if the focus changes some time before or after, the
672                // next client receiving focus that has any interest in input will
673                // be calling through here after that change happens.
674                Slog.w(TAG, "Starting input on non-focused client " + cs.client
675                        + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
676                return null;
677            }
678        } catch (RemoteException e) {
679        }
680
681        if (mCurClient != cs) {
682            // If the client is changing, we need to switch over to the new
683            // one.
684            unbindCurrentClientLocked();
685            if (DEBUG) Slog.v(TAG, "switching to client: client = "
686                    + cs.client.asBinder());
687
688            // If the screen is on, inform the new client it is active
689            if (mScreenOn) {
690                try {
691                    cs.client.setActive(mScreenOn);
692                } catch (RemoteException e) {
693                    Slog.w(TAG, "Got RemoteException sending setActive notification to pid "
694                            + cs.pid + " uid " + cs.uid);
695                }
696            }
697        }
698
699        // Bump up the sequence for this client and attach it.
700        mCurSeq++;
701        if (mCurSeq <= 0) mCurSeq = 1;
702        mCurClient = cs;
703        mCurInputContext = inputContext;
704        mCurAttribute = attribute;
705
706        // Check if the input method is changing.
707        if (mCurId != null && mCurId.equals(mCurMethodId)) {
708            if (cs.curSession != null) {
709                // Fast case: if we are already connected to the input method,
710                // then just return it.
711                return attachNewInputLocked(initial, needResult);
712            }
713            if (mHaveConnection) {
714                if (mCurMethod != null) {
715                    if (!cs.sessionRequested) {
716                        cs.sessionRequested = true;
717                        if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
718                        executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
719                                MSG_CREATE_SESSION, mCurMethod,
720                                new MethodCallback(mCurMethod)));
721                    }
722                    // Return to client, and we will get back with it when
723                    // we have had a session made for it.
724                    return new InputBindResult(null, mCurId, mCurSeq);
725                } else if (SystemClock.uptimeMillis()
726                        < (mLastBindTime+TIME_TO_RECONNECT)) {
727                    // In this case we have connected to the service, but
728                    // don't yet have its interface.  If it hasn't been too
729                    // long since we did the connection, we'll return to
730                    // the client and wait to get the service interface so
731                    // we can report back.  If it has been too long, we want
732                    // to fall through so we can try a disconnect/reconnect
733                    // to see if we can get back in touch with the service.
734                    return new InputBindResult(null, mCurId, mCurSeq);
735                } else {
736                    EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
737                            mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
738                }
739            }
740        }
741
742        return startInputInnerLocked();
743    }
744
745    InputBindResult startInputInnerLocked() {
746        if (mCurMethodId == null) {
747            return mNoBinding;
748        }
749
750        if (!mSystemReady) {
751            // If the system is not yet ready, we shouldn't be running third
752            // party code.
753            return new InputBindResult(null, mCurMethodId, mCurSeq);
754        }
755
756        InputMethodInfo info = mMethodMap.get(mCurMethodId);
757        if (info == null) {
758            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
759        }
760
761        unbindCurrentMethodLocked(false);
762
763        mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
764        mCurIntent.setComponent(info.getComponent());
765        mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
766                com.android.internal.R.string.input_method_binding_label);
767        mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
768                mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
769        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE)) {
770            mLastBindTime = SystemClock.uptimeMillis();
771            mHaveConnection = true;
772            mCurId = info.getId();
773            mCurToken = new Binder();
774            try {
775                if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
776                mIWindowManager.addWindowToken(mCurToken,
777                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
778            } catch (RemoteException e) {
779            }
780            return new InputBindResult(null, mCurId, mCurSeq);
781        } else {
782            mCurIntent = null;
783            Slog.w(TAG, "Failure connecting to input method service: "
784                    + mCurIntent);
785        }
786        return null;
787    }
788
789    public InputBindResult startInput(IInputMethodClient client,
790            IInputContext inputContext, EditorInfo attribute,
791            boolean initial, boolean needResult) {
792        synchronized (mMethodMap) {
793            final long ident = Binder.clearCallingIdentity();
794            try {
795                return startInputLocked(client, inputContext, attribute,
796                        initial, needResult);
797            } finally {
798                Binder.restoreCallingIdentity(ident);
799            }
800        }
801    }
802
803    public void finishInput(IInputMethodClient client) {
804    }
805
806    public void onServiceConnected(ComponentName name, IBinder service) {
807        synchronized (mMethodMap) {
808            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
809                mCurMethod = IInputMethod.Stub.asInterface(service);
810                if (mCurToken == null) {
811                    Slog.w(TAG, "Service connected without a token!");
812                    unbindCurrentMethodLocked(false);
813                    return;
814                }
815                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
816                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
817                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
818                if (mCurClient != null) {
819                    if (DEBUG) Slog.v(TAG, "Creating first session while with client "
820                            + mCurClient);
821                    executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
822                            MSG_CREATE_SESSION, mCurMethod,
823                            new MethodCallback(mCurMethod)));
824                }
825            }
826        }
827    }
828
829    void onSessionCreated(IInputMethod method, IInputMethodSession session) {
830        synchronized (mMethodMap) {
831            if (mCurMethod != null && method != null
832                    && mCurMethod.asBinder() == method.asBinder()) {
833                if (mCurClient != null) {
834                    mCurClient.curSession = new SessionState(mCurClient,
835                            method, session);
836                    mCurClient.sessionRequested = false;
837                    InputBindResult res = attachNewInputLocked(true, true);
838                    if (res.method != null) {
839                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
840                                MSG_BIND_METHOD, mCurClient.client, res));
841                    }
842                }
843            }
844        }
845    }
846
847    void unbindCurrentMethodLocked(boolean reportToClient) {
848        if (mHaveConnection) {
849            mContext.unbindService(this);
850            mHaveConnection = false;
851        }
852
853        if (mCurToken != null) {
854            try {
855                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
856                mIWindowManager.removeWindowToken(mCurToken);
857            } catch (RemoteException e) {
858            }
859            mCurToken = null;
860        }
861
862        mCurId = null;
863        clearCurMethodLocked();
864
865        if (reportToClient && mCurClient != null) {
866            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
867                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
868        }
869    }
870
871    private void finishSession(SessionState sessionState) {
872        if (sessionState != null && sessionState.session != null) {
873            try {
874                sessionState.session.finishSession();
875            } catch (RemoteException e) {
876                Slog.w(TAG, "Session failed to close due to remote exception", e);
877            }
878        }
879    }
880
881    void clearCurMethodLocked() {
882        if (mCurMethod != null) {
883            for (ClientState cs : mClients.values()) {
884                cs.sessionRequested = false;
885                finishSession(cs.curSession);
886                cs.curSession = null;
887            }
888
889            finishSession(mEnabledSession);
890            mEnabledSession = null;
891            mCurMethod = null;
892        }
893        mStatusBar.setIconVisibility("ime", false);
894    }
895
896    public void onServiceDisconnected(ComponentName name) {
897        synchronized (mMethodMap) {
898            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
899                    + " mCurIntent=" + mCurIntent);
900            if (mCurMethod != null && mCurIntent != null
901                    && name.equals(mCurIntent.getComponent())) {
902                clearCurMethodLocked();
903                // We consider this to be a new bind attempt, since the system
904                // should now try to restart the service for us.
905                mLastBindTime = SystemClock.uptimeMillis();
906                mShowRequested = mInputShown;
907                mInputShown = false;
908                if (mCurClient != null) {
909                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
910                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
911                }
912            }
913        }
914    }
915
916    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
917        int uid = Binder.getCallingUid();
918        long ident = Binder.clearCallingIdentity();
919        try {
920            if (token == null || mCurToken != token) {
921                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
922                return;
923            }
924
925            synchronized (mMethodMap) {
926                if (iconId == 0) {
927                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
928                    mStatusBar.setIconVisibility("ime", false);
929                } else if (packageName != null) {
930                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
931                    mStatusBar.setIcon("ime", packageName, iconId, 0);
932                    mStatusBar.setIconVisibility("ime", true);
933                }
934            }
935        } finally {
936            Binder.restoreCallingIdentity(ident);
937        }
938    }
939
940    public void setIMEButtonVisible(IBinder token, boolean visible) {
941        int uid = Binder.getCallingUid();
942        long ident = Binder.clearCallingIdentity();
943        try {
944            if (token == null || mCurToken != token) {
945                Slog.w(TAG, "Ignoring setIMEButtonVisible of uid " + uid + " token: " + token);
946                return;
947            }
948
949            synchronized (mMethodMap) {
950                mStatusBar.setIMEButtonVisible(visible);
951            }
952        } finally {
953            Binder.restoreCallingIdentity(ident);
954        }
955    }
956
957    void updateFromSettingsLocked() {
958        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
959        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
960        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
961        // enabled.
962        String id = Settings.Secure.getString(mContext.getContentResolver(),
963                Settings.Secure.DEFAULT_INPUT_METHOD);
964        // There is no input method selected, try to choose new applicable input method.
965        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
966            id = Settings.Secure.getString(mContext.getContentResolver(),
967                    Settings.Secure.DEFAULT_INPUT_METHOD);
968        }
969        if (!TextUtils.isEmpty(id)) {
970            try {
971                setInputMethodLocked(id, getSelectedInputMethodSubtypeId(id));
972            } catch (IllegalArgumentException e) {
973                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
974                mCurMethodId = null;
975                unbindCurrentMethodLocked(true);
976            }
977        } else {
978            // There is no longer an input method set, so stop any current one.
979            mCurMethodId = null;
980            unbindCurrentMethodLocked(true);
981        }
982    }
983
984    /* package */ void setInputMethodLocked(String id, int subtypeId) {
985        InputMethodInfo info = mMethodMap.get(id);
986        if (info == null) {
987            throw new IllegalArgumentException("Unknown id: " + id);
988        }
989
990        if (id.equals(mCurMethodId)) {
991            if (subtypeId != NOT_A_SUBTYPE_ID) {
992                InputMethodSubtype subtype = info.getSubtypes().get(subtypeId);
993                if (subtype != mCurrentSubtype) {
994                    synchronized (mMethodMap) {
995                        if (mCurMethod != null) {
996                            try {
997                                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
998                                if (mInputShown) {
999                                    // If mInputShown is false, there is no IME button on the
1000                                    // system bar.
1001                                    // Thus there is no need to make it invisible explicitly.
1002                                    mStatusBar.setIMEButtonVisible(true);
1003                                }
1004                                mCurMethod.changeInputMethodSubtype(subtype);
1005                            } catch (RemoteException e) {
1006                                return;
1007                            }
1008                        }
1009                    }
1010                }
1011            }
1012            return;
1013        }
1014
1015        final long ident = Binder.clearCallingIdentity();
1016        try {
1017            // Set a subtype to this input method.
1018            // subtypeId the name of a subtype which will be set.
1019            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1020            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1021            // because mCurMethodId is stored as a history in
1022            // setSelectedInputMethodAndSubtypeLocked().
1023            mCurMethodId = id;
1024
1025            if (ActivityManagerNative.isSystemReady()) {
1026                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1027                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1028                intent.putExtra("input_method_id", id);
1029                mContext.sendBroadcast(intent);
1030            }
1031            unbindCurrentClientLocked();
1032        } finally {
1033            Binder.restoreCallingIdentity(ident);
1034        }
1035    }
1036
1037    public boolean showSoftInput(IInputMethodClient client, int flags,
1038            ResultReceiver resultReceiver) {
1039        int uid = Binder.getCallingUid();
1040        long ident = Binder.clearCallingIdentity();
1041        try {
1042            synchronized (mMethodMap) {
1043                if (mCurClient == null || client == null
1044                        || mCurClient.client.asBinder() != client.asBinder()) {
1045                    try {
1046                        // We need to check if this is the current client with
1047                        // focus in the window manager, to allow this call to
1048                        // be made before input is started in it.
1049                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1050                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1051                            return false;
1052                        }
1053                    } catch (RemoteException e) {
1054                        return false;
1055                    }
1056                }
1057
1058                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1059                return showCurrentInputLocked(flags, resultReceiver);
1060            }
1061        } finally {
1062            Binder.restoreCallingIdentity(ident);
1063        }
1064    }
1065
1066    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1067        mShowRequested = true;
1068        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1069            mShowExplicitlyRequested = true;
1070        }
1071        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1072            mShowExplicitlyRequested = true;
1073            mShowForced = true;
1074        }
1075
1076        if (!mSystemReady) {
1077            return false;
1078        }
1079
1080        boolean res = false;
1081        if (mCurMethod != null) {
1082            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1083                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1084                    resultReceiver));
1085            mInputShown = true;
1086            res = true;
1087        } else if (mHaveConnection && SystemClock.uptimeMillis()
1088                < (mLastBindTime+TIME_TO_RECONNECT)) {
1089            // The client has asked to have the input method shown, but
1090            // we have been sitting here too long with a connection to the
1091            // service and no interface received, so let's disconnect/connect
1092            // to try to prod things along.
1093            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1094                    SystemClock.uptimeMillis()-mLastBindTime,1);
1095            mContext.unbindService(this);
1096            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE);
1097        }
1098
1099        return res;
1100    }
1101
1102    public boolean hideSoftInput(IInputMethodClient client, int flags,
1103            ResultReceiver resultReceiver) {
1104        int uid = Binder.getCallingUid();
1105        long ident = Binder.clearCallingIdentity();
1106        try {
1107            synchronized (mMethodMap) {
1108                if (mCurClient == null || client == null
1109                        || mCurClient.client.asBinder() != client.asBinder()) {
1110                    try {
1111                        // We need to check if this is the current client with
1112                        // focus in the window manager, to allow this call to
1113                        // be made before input is started in it.
1114                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1115                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1116                                    + uid + ": " + client);
1117                            return false;
1118                        }
1119                    } catch (RemoteException e) {
1120                        return false;
1121                    }
1122                }
1123
1124                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1125                return hideCurrentInputLocked(flags, resultReceiver);
1126            }
1127        } finally {
1128            Binder.restoreCallingIdentity(ident);
1129        }
1130    }
1131
1132    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1133        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1134                && (mShowExplicitlyRequested || mShowForced)) {
1135            if (DEBUG) Slog.v(TAG,
1136                    "Not hiding: explicit show not cancelled by non-explicit hide");
1137            return false;
1138        }
1139        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1140            if (DEBUG) Slog.v(TAG,
1141                    "Not hiding: forced show not cancelled by not-always hide");
1142            return false;
1143        }
1144        boolean res;
1145        if (mInputShown && mCurMethod != null) {
1146            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1147                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1148            res = true;
1149        } else {
1150            res = false;
1151        }
1152        mInputShown = false;
1153        mShowRequested = false;
1154        mShowExplicitlyRequested = false;
1155        mShowForced = false;
1156        return res;
1157    }
1158
1159    public void windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1160            boolean viewHasFocus, boolean isTextEditor, int softInputMode,
1161            boolean first, int windowFlags) {
1162        long ident = Binder.clearCallingIdentity();
1163        try {
1164            synchronized (mMethodMap) {
1165                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1166                        + " viewHasFocus=" + viewHasFocus
1167                        + " isTextEditor=" + isTextEditor
1168                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1169                        + " first=" + first + " flags=#"
1170                        + Integer.toHexString(windowFlags));
1171
1172                if (mCurClient == null || client == null
1173                        || mCurClient.client.asBinder() != client.asBinder()) {
1174                    try {
1175                        // We need to check if this is the current client with
1176                        // focus in the window manager, to allow this call to
1177                        // be made before input is started in it.
1178                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1179                            Slog.w(TAG, "Client not active, ignoring focus gain of: " + client);
1180                            return;
1181                        }
1182                    } catch (RemoteException e) {
1183                    }
1184                }
1185
1186                if (mCurFocusedWindow == windowToken) {
1187                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client);
1188                    return;
1189                }
1190                mCurFocusedWindow = windowToken;
1191
1192                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1193                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1194                        if (!isTextEditor || (softInputMode &
1195                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1196                                != WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE) {
1197                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1198                                // There is no focus view, and this window will
1199                                // be behind any soft input window, so hide the
1200                                // soft input window if it is shown.
1201                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1202                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1203                            }
1204                        } else if (isTextEditor && (softInputMode &
1205                                WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1206                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1207                                && (softInputMode &
1208                                        WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1209                            // There is a focus view, and we are navigating forward
1210                            // into the window, so show the input window for the user.
1211                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1212                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1213                        }
1214                        break;
1215                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1216                        // Do nothing.
1217                        break;
1218                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1219                        if ((softInputMode &
1220                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1221                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1222                            hideCurrentInputLocked(0, null);
1223                        }
1224                        break;
1225                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1226                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1227                        hideCurrentInputLocked(0, null);
1228                        break;
1229                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1230                        if ((softInputMode &
1231                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1232                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
1233                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1234                        }
1235                        break;
1236                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
1237                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
1238                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1239                        break;
1240                }
1241            }
1242        } finally {
1243            Binder.restoreCallingIdentity(ident);
1244        }
1245    }
1246
1247    public void showInputMethodPickerFromClient(IInputMethodClient client) {
1248        synchronized (mMethodMap) {
1249            if (mCurClient == null || client == null
1250                    || mCurClient.client.asBinder() != client.asBinder()) {
1251                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
1252                        + Binder.getCallingUid() + ": " + client);
1253            }
1254
1255            mHandler.sendEmptyMessage(MSG_SHOW_IM_PICKER);
1256        }
1257    }
1258
1259    public void showInputMethodSubtypePickerFromClient(IInputMethodClient client) {
1260        synchronized (mMethodMap) {
1261            if (mCurClient == null || client == null
1262                    || mCurClient.client.asBinder() != client.asBinder()) {
1263                Slog.w(TAG, "Ignoring showInputMethodSubtypePickerFromClient of: " + client);
1264            }
1265
1266            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
1267        }
1268    }
1269
1270    public void showInputMethodAndSubtypeEnablerFromClient(
1271            IInputMethodClient client, String topId) {
1272        // TODO: Handle topId for setting the top position of the list activity
1273        synchronized (mMethodMap) {
1274            if (mCurClient == null || client == null
1275                    || mCurClient.client.asBinder() != client.asBinder()) {
1276                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
1277            }
1278
1279            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_ENABLER);
1280        }
1281    }
1282
1283    public void setInputMethod(IBinder token, String id) {
1284        setInputMethodWithSubtype(token, id, NOT_A_SUBTYPE_ID);
1285    }
1286
1287    public boolean switchToLastInputMethod(IBinder token) {
1288        synchronized (mMethodMap) {
1289            Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
1290            if (lastIme != null) {
1291                InputMethodInfo imi = mMethodMap.get(lastIme.first);
1292                if (imi != null) {
1293                    setInputMethodWithSubtype(token, lastIme.first, getSubtypeIdFromHashCode(
1294                            imi, Integer.valueOf(lastIme.second)));
1295                    return true;
1296                }
1297            }
1298            return false;
1299        }
1300    }
1301
1302    private void setInputMethodWithSubtype(IBinder token, String id, int subtypeId) {
1303        synchronized (mMethodMap) {
1304            if (token == null) {
1305                if (mContext.checkCallingOrSelfPermission(
1306                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
1307                        != PackageManager.PERMISSION_GRANTED) {
1308                    throw new SecurityException(
1309                            "Using null token requires permission "
1310                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1311                }
1312            } else if (mCurToken != token) {
1313                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
1314                        + " token: " + token);
1315                return;
1316            }
1317
1318            long ident = Binder.clearCallingIdentity();
1319            try {
1320                setInputMethodLocked(id, subtypeId);
1321            } finally {
1322                Binder.restoreCallingIdentity(ident);
1323            }
1324        }
1325    }
1326
1327    public void hideMySoftInput(IBinder token, int flags) {
1328        synchronized (mMethodMap) {
1329            if (token == null || mCurToken != token) {
1330                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
1331                        + Binder.getCallingUid() + " token: " + token);
1332                return;
1333            }
1334            long ident = Binder.clearCallingIdentity();
1335            try {
1336                hideCurrentInputLocked(flags, null);
1337            } finally {
1338                Binder.restoreCallingIdentity(ident);
1339            }
1340        }
1341    }
1342
1343    public void showMySoftInput(IBinder token, int flags) {
1344        synchronized (mMethodMap) {
1345            if (token == null || mCurToken != token) {
1346                Slog.w(TAG, "Ignoring showMySoftInput of uid "
1347                        + Binder.getCallingUid() + " token: " + token);
1348                return;
1349            }
1350            long ident = Binder.clearCallingIdentity();
1351            try {
1352                showCurrentInputLocked(flags, null);
1353            } finally {
1354                Binder.restoreCallingIdentity(ident);
1355            }
1356        }
1357    }
1358
1359    void setEnabledSessionInMainThread(SessionState session) {
1360        if (mEnabledSession != session) {
1361            if (mEnabledSession != null) {
1362                try {
1363                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
1364                    mEnabledSession.method.setSessionEnabled(
1365                            mEnabledSession.session, false);
1366                } catch (RemoteException e) {
1367                }
1368            }
1369            mEnabledSession = session;
1370            try {
1371                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
1372                session.method.setSessionEnabled(
1373                        session.session, true);
1374            } catch (RemoteException e) {
1375            }
1376        }
1377    }
1378
1379    public boolean handleMessage(Message msg) {
1380        HandlerCaller.SomeArgs args;
1381        switch (msg.what) {
1382            case MSG_SHOW_IM_PICKER:
1383                showInputMethodMenu();
1384                return true;
1385
1386            case MSG_SHOW_IM_SUBTYPE_PICKER:
1387                showInputMethodSubtypeMenu();
1388                return true;
1389
1390            case MSG_SHOW_IM_SUBTYPE_ENABLER:
1391                showInputMethodAndSubtypeEnabler();
1392                return true;
1393
1394            // ---------------------------------------------------------
1395
1396            case MSG_UNBIND_INPUT:
1397                try {
1398                    ((IInputMethod)msg.obj).unbindInput();
1399                } catch (RemoteException e) {
1400                    // There is nothing interesting about the method dying.
1401                }
1402                return true;
1403            case MSG_BIND_INPUT:
1404                args = (HandlerCaller.SomeArgs)msg.obj;
1405                try {
1406                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
1407                } catch (RemoteException e) {
1408                }
1409                return true;
1410            case MSG_SHOW_SOFT_INPUT:
1411                args = (HandlerCaller.SomeArgs)msg.obj;
1412                try {
1413                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1,
1414                            (ResultReceiver)args.arg2);
1415                } catch (RemoteException e) {
1416                }
1417                return true;
1418            case MSG_HIDE_SOFT_INPUT:
1419                args = (HandlerCaller.SomeArgs)msg.obj;
1420                try {
1421                    ((IInputMethod)args.arg1).hideSoftInput(0,
1422                            (ResultReceiver)args.arg2);
1423                } catch (RemoteException e) {
1424                }
1425                return true;
1426            case MSG_ATTACH_TOKEN:
1427                args = (HandlerCaller.SomeArgs)msg.obj;
1428                try {
1429                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
1430                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
1431                } catch (RemoteException e) {
1432                }
1433                return true;
1434            case MSG_CREATE_SESSION:
1435                args = (HandlerCaller.SomeArgs)msg.obj;
1436                try {
1437                    ((IInputMethod)args.arg1).createSession(
1438                            (IInputMethodCallback)args.arg2);
1439                } catch (RemoteException e) {
1440                }
1441                return true;
1442            // ---------------------------------------------------------
1443
1444            case MSG_START_INPUT:
1445                args = (HandlerCaller.SomeArgs)msg.obj;
1446                try {
1447                    SessionState session = (SessionState)args.arg1;
1448                    setEnabledSessionInMainThread(session);
1449                    session.method.startInput((IInputContext)args.arg2,
1450                            (EditorInfo)args.arg3);
1451                } catch (RemoteException e) {
1452                }
1453                return true;
1454            case MSG_RESTART_INPUT:
1455                args = (HandlerCaller.SomeArgs)msg.obj;
1456                try {
1457                    SessionState session = (SessionState)args.arg1;
1458                    setEnabledSessionInMainThread(session);
1459                    session.method.restartInput((IInputContext)args.arg2,
1460                            (EditorInfo)args.arg3);
1461                } catch (RemoteException e) {
1462                }
1463                return true;
1464
1465            // ---------------------------------------------------------
1466
1467            case MSG_UNBIND_METHOD:
1468                try {
1469                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
1470                } catch (RemoteException e) {
1471                    // There is nothing interesting about the last client dying.
1472                }
1473                return true;
1474            case MSG_BIND_METHOD:
1475                args = (HandlerCaller.SomeArgs)msg.obj;
1476                try {
1477                    ((IInputMethodClient)args.arg1).onBindMethod(
1478                            (InputBindResult)args.arg2);
1479                } catch (RemoteException e) {
1480                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
1481                }
1482                return true;
1483        }
1484        return false;
1485    }
1486
1487    private boolean isSystemIme(InputMethodInfo inputMethod) {
1488        return (inputMethod.getServiceInfo().applicationInfo.flags
1489                & ApplicationInfo.FLAG_SYSTEM) != 0;
1490    }
1491
1492    private boolean chooseNewDefaultIMELocked() {
1493        List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1494        if (enabled != null && enabled.size() > 0) {
1495            // We'd prefer to fall back on a system IME, since that is safer.
1496            int i=enabled.size();
1497            while (i > 0) {
1498                i--;
1499                if ((enabled.get(i).getServiceInfo().applicationInfo.flags
1500                        & ApplicationInfo.FLAG_SYSTEM) != 0) {
1501                    break;
1502                }
1503            }
1504            InputMethodInfo imi = enabled.get(i);
1505            if (DEBUG) {
1506                Slog.d(TAG, "New default IME was selected: " + imi.getId());
1507            }
1508            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
1509            return true;
1510        }
1511
1512        return false;
1513    }
1514
1515    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
1516            HashMap<String, InputMethodInfo> map) {
1517        list.clear();
1518        map.clear();
1519
1520        PackageManager pm = mContext.getPackageManager();
1521        final Configuration config = mContext.getResources().getConfiguration();
1522        final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
1523        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
1524                Secure.DISABLED_SYSTEM_INPUT_METHODS);
1525        if (disabledSysImes == null) disabledSysImes = "";
1526
1527        List<ResolveInfo> services = pm.queryIntentServices(
1528                new Intent(InputMethod.SERVICE_INTERFACE),
1529                PackageManager.GET_META_DATA);
1530
1531        for (int i = 0; i < services.size(); ++i) {
1532            ResolveInfo ri = services.get(i);
1533            ServiceInfo si = ri.serviceInfo;
1534            ComponentName compName = new ComponentName(si.packageName, si.name);
1535            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
1536                    si.permission)) {
1537                Slog.w(TAG, "Skipping input method " + compName
1538                        + ": it does not require the permission "
1539                        + android.Manifest.permission.BIND_INPUT_METHOD);
1540                continue;
1541            }
1542
1543            if (DEBUG) Slog.d(TAG, "Checking " + compName);
1544
1545            try {
1546                InputMethodInfo p = new InputMethodInfo(mContext, ri);
1547                list.add(p);
1548                final String id = p.getId();
1549                map.put(id, p);
1550
1551                // System IMEs are enabled by default, unless there's a hard keyboard
1552                // and the system IME was explicitly disabled
1553                if (isSystemIme(p) && (!haveHardKeyboard || disabledSysImes.indexOf(id) < 0)) {
1554                    setInputMethodEnabledLocked(id, true);
1555                }
1556
1557                if (DEBUG) {
1558                    Slog.d(TAG, "Found a third-party input method " + p);
1559                }
1560
1561            } catch (XmlPullParserException e) {
1562                Slog.w(TAG, "Unable to load input method " + compName, e);
1563            } catch (IOException e) {
1564                Slog.w(TAG, "Unable to load input method " + compName, e);
1565            }
1566        }
1567
1568        String defaultIme = Settings.Secure.getString(mContext
1569                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1570        if (!TextUtils.isEmpty(defaultIme) && !map.containsKey(defaultIme)) {
1571            if (chooseNewDefaultIMELocked()) {
1572                updateFromSettingsLocked();
1573            }
1574        }
1575    }
1576
1577    // ----------------------------------------------------------------------
1578
1579    private void showInputMethodMenu() {
1580        showInputMethodMenuInternal(false);
1581    }
1582
1583    private void showInputMethodSubtypeMenu() {
1584        showInputMethodMenuInternal(true);
1585    }
1586
1587    private void showInputMethodAndSubtypeEnabler() {
1588        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_AND_SUBTYPE_ENABLER);
1589        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
1590                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
1591                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
1592        mContext.startActivity(intent);
1593    }
1594
1595    private void showInputMethodMenuInternal(boolean showSubtypes) {
1596        if (DEBUG) Slog.v(TAG, "Show switching menu");
1597
1598        final Context context = mContext;
1599
1600        final PackageManager pm = context.getPackageManager();
1601
1602        String lastInputMethodId = Settings.Secure.getString(context
1603                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1604        int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
1605        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
1606
1607        synchronized (mMethodMap) {
1608            final List<Pair<InputMethodInfo, ArrayList<String>>> immis =
1609                    mSettings.getEnabledInputMethodAndSubtypeListLocked();
1610            ArrayList<Integer> subtypeIds = new ArrayList<Integer>();
1611
1612            if (immis == null || immis.size() == 0) {
1613                return;
1614            }
1615
1616            hideInputMethodMenuLocked();
1617
1618            int N = immis.size();
1619
1620            final Map<CharSequence, Pair<InputMethodInfo, Integer>> imMap =
1621                new TreeMap<CharSequence, Pair<InputMethodInfo, Integer>>(Collator.getInstance());
1622
1623            for (int i = 0; i < N; ++i) {
1624                InputMethodInfo property = immis.get(i).first;
1625                final ArrayList<String> enabledSubtypeIds = immis.get(i).second;
1626                HashSet<String> enabledSubtypeSet = new HashSet<String>();
1627                for (String s : enabledSubtypeIds) {
1628                    enabledSubtypeSet.add(s);
1629                }
1630                if (property == null) {
1631                    continue;
1632                }
1633                ArrayList<InputMethodSubtype> subtypes = property.getSubtypes();
1634                CharSequence label = property.loadLabel(pm);
1635                if (showSubtypes && enabledSubtypeSet.size() > 0) {
1636                    for (int j = 0; j < subtypes.size(); ++j) {
1637                        InputMethodSubtype subtype = subtypes.get(j);
1638                        if (enabledSubtypeSet.contains(String.valueOf(subtype.hashCode()))) {
1639                            CharSequence title;
1640                            int nameResId = subtype.getNameResId();
1641                            String mode = subtype.getMode();
1642                            if (nameResId != 0) {
1643                                title = pm.getText(property.getPackageName(), nameResId,
1644                                        property.getServiceInfo().applicationInfo);
1645                            } else {
1646                                CharSequence language = subtype.getLocale();
1647                                // TODO: Use more friendly Title and UI
1648                                title = label + "," + (mode == null ? "" : mode) + ","
1649                                        + (language == null ? "" : language);
1650                            }
1651                            imMap.put(title, new Pair<InputMethodInfo, Integer>(property, j));
1652                        }
1653                    }
1654                } else {
1655                    imMap.put(label,
1656                            new Pair<InputMethodInfo, Integer>(property, NOT_A_SUBTYPE_ID));
1657                    subtypeIds.add(0);
1658                }
1659            }
1660
1661            N = imMap.size();
1662            mItems = imMap.keySet().toArray(new CharSequence[N]);
1663            mIms = new InputMethodInfo[N];
1664            mSubtypeIds = new int[N];
1665            int checkedItem = 0;
1666            for (int i = 0; i < N; ++i) {
1667                Pair<InputMethodInfo, Integer> value = imMap.get(mItems[i]);
1668                mIms[i] = value.first;
1669                mSubtypeIds[i] = value.second;
1670                if (mIms[i].getId().equals(lastInputMethodId)) {
1671                    int subtypeId = mSubtypeIds[i];
1672                    if ((subtypeId == NOT_A_SUBTYPE_ID)
1673                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
1674                            || (subtypeId == lastInputMethodSubtypeId)) {
1675                        checkedItem = i;
1676                    }
1677                }
1678            }
1679
1680            AlertDialog.OnClickListener adocl = new AlertDialog.OnClickListener() {
1681                public void onClick(DialogInterface dialog, int which) {
1682                    hideInputMethodMenu();
1683                }
1684            };
1685
1686            TypedArray a = context.obtainStyledAttributes(null,
1687                    com.android.internal.R.styleable.DialogPreference,
1688                    com.android.internal.R.attr.alertDialogStyle, 0);
1689            mDialogBuilder = new AlertDialog.Builder(context)
1690                    .setTitle(com.android.internal.R.string.select_input_method)
1691                    .setOnCancelListener(new OnCancelListener() {
1692                        public void onCancel(DialogInterface dialog) {
1693                            hideInputMethodMenu();
1694                        }
1695                    })
1696                    .setIcon(a.getDrawable(
1697                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
1698            a.recycle();
1699
1700            mDialogBuilder.setSingleChoiceItems(mItems, checkedItem,
1701                    new AlertDialog.OnClickListener() {
1702                        public void onClick(DialogInterface dialog, int which) {
1703                            synchronized (mMethodMap) {
1704                                if (mIms == null || mIms.length <= which
1705                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
1706                                    return;
1707                                }
1708                                InputMethodInfo im = mIms[which];
1709                                int subtypeId = mSubtypeIds[which];
1710                                hideInputMethodMenu();
1711                                if (im != null) {
1712                                    if ((subtypeId < 0)
1713                                            || (subtypeId >= im.getSubtypes().size())) {
1714                                        subtypeId = NOT_A_SUBTYPE_ID;
1715                                    }
1716                                    setInputMethodLocked(im.getId(), subtypeId);
1717                                }
1718                            }
1719                        }
1720                    });
1721
1722            if (showSubtypes) {
1723                mDialogBuilder.setPositiveButton(com.android.internal.R.string.more_item_label,
1724                        new DialogInterface.OnClickListener() {
1725                            public void onClick(DialogInterface dialog, int whichButton) {
1726                                showInputMethodAndSubtypeEnabler();
1727                            }
1728                        });
1729            }
1730            mDialogBuilder.setNegativeButton(com.android.internal.R.string.cancel,
1731                    new DialogInterface.OnClickListener() {
1732                        public void onClick(DialogInterface dialog, int whichButton) {
1733                            hideInputMethodMenu();
1734                        }
1735                    });
1736            mSwitchingDialog = mDialogBuilder.create();
1737            mSwitchingDialog.getWindow().setType(
1738                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
1739            mSwitchingDialog.show();
1740        }
1741    }
1742
1743    void hideInputMethodMenu() {
1744        synchronized (mMethodMap) {
1745            hideInputMethodMenuLocked();
1746        }
1747    }
1748
1749    void hideInputMethodMenuLocked() {
1750        if (DEBUG) Slog.v(TAG, "Hide switching menu");
1751
1752        if (mSwitchingDialog != null) {
1753            mSwitchingDialog.dismiss();
1754            mSwitchingDialog = null;
1755        }
1756
1757        mDialogBuilder = null;
1758        mItems = null;
1759        mIms = null;
1760    }
1761
1762    // ----------------------------------------------------------------------
1763
1764    public boolean setInputMethodEnabled(String id, boolean enabled) {
1765        synchronized (mMethodMap) {
1766            if (mContext.checkCallingOrSelfPermission(
1767                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
1768                    != PackageManager.PERMISSION_GRANTED) {
1769                throw new SecurityException(
1770                        "Requires permission "
1771                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
1772            }
1773
1774            long ident = Binder.clearCallingIdentity();
1775            try {
1776                return setInputMethodEnabledLocked(id, enabled);
1777            } finally {
1778                Binder.restoreCallingIdentity(ident);
1779            }
1780        }
1781    }
1782
1783    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
1784        // Make sure this is a valid input method.
1785        InputMethodInfo imm = mMethodMap.get(id);
1786        if (imm == null) {
1787            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1788        }
1789
1790        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
1791                .getEnabledInputMethodsAndSubtypeListLocked();
1792
1793        if (enabled) {
1794            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
1795                if (pair.first.equals(id)) {
1796                    // We are enabling this input method, but it is already enabled.
1797                    // Nothing to do. The previous state was enabled.
1798                    return true;
1799                }
1800            }
1801            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
1802            // Previous state was disabled.
1803            return false;
1804        } else {
1805            StringBuilder builder = new StringBuilder();
1806            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
1807                    builder, enabledInputMethodsList, id)) {
1808                // Disabled input method is currently selected, switch to another one.
1809                String selId = Settings.Secure.getString(mContext.getContentResolver(),
1810                        Settings.Secure.DEFAULT_INPUT_METHOD);
1811                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
1812                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
1813                    resetSelectedInputMethodAndSubtypeLocked("");
1814                }
1815                // Previous state was enabled.
1816                return true;
1817            } else {
1818                // We are disabling the input method but it is already disabled.
1819                // Nothing to do.  The previous state was disabled.
1820                return false;
1821            }
1822        }
1823    }
1824
1825    private void saveCurrentInputMethodAndSubtypeToHistory() {
1826        String subtypeId = NOT_A_SUBTYPE_ID_STR;
1827        if (mCurrentSubtype != null) {
1828            subtypeId = String.valueOf(mCurrentSubtype.hashCode());
1829        }
1830        mSettings.addSubtypeToHistory(mCurMethodId, subtypeId);
1831    }
1832
1833    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
1834            boolean setSubtypeOnly) {
1835        // Update the history of InputMethod and Subtype
1836        saveCurrentInputMethodAndSubtypeToHistory();
1837
1838        // Set Subtype here
1839        if (imi == null || subtypeId < 0) {
1840            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1841            mCurrentSubtype = null;
1842        } else {
1843            final ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
1844            if (subtypeId < subtypes.size()) {
1845                mSettings.putSelectedSubtype(subtypes.get(subtypeId).hashCode());
1846                mCurrentSubtype = subtypes.get(subtypeId);
1847            } else {
1848                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
1849                mCurrentSubtype = null;
1850            }
1851        }
1852
1853        if (!setSubtypeOnly) {
1854            // Set InputMethod here
1855            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
1856        }
1857    }
1858
1859    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
1860        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
1861        int lastSubtypeId = NOT_A_SUBTYPE_ID;
1862        // newDefaultIme is empty when there is no candidate for the selected IME.
1863        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
1864            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
1865            if (subtypeHashCode != null) {
1866                try {
1867                    lastSubtypeId = getSubtypeIdFromHashCode(
1868                            imi, Integer.valueOf(subtypeHashCode));
1869                } catch (NumberFormatException e) {
1870                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
1871                }
1872            }
1873        }
1874        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
1875    }
1876
1877    private int getSelectedInputMethodSubtypeId(String id) {
1878        InputMethodInfo imi = mMethodMap.get(id);
1879        if (imi == null) {
1880            return NOT_A_SUBTYPE_ID;
1881        }
1882        int subtypeId;
1883        try {
1884            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
1885                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
1886        } catch (SettingNotFoundException e) {
1887            return NOT_A_SUBTYPE_ID;
1888        }
1889        return getSubtypeIdFromHashCode(imi, subtypeId);
1890    }
1891
1892    private int getSubtypeIdFromHashCode(InputMethodInfo imi, int subtypeHashCode) {
1893        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
1894        for (int i = 0; i < subtypes.size(); ++i) {
1895            InputMethodSubtype ims = subtypes.get(i);
1896            if (subtypeHashCode == ims.hashCode()) {
1897                return i;
1898            }
1899        }
1900        return NOT_A_SUBTYPE_ID;
1901    }
1902
1903    // If there are no selected subtypes, tries finding the most applicable one according to the
1904    // current system locale
1905    private int findApplicableSubtype(String id) {
1906        InputMethodInfo imi = mMethodMap.get(id);
1907        if (imi == null) {
1908            return NOT_A_SUBTYPE_ID;
1909        }
1910        ArrayList<InputMethodSubtype> subtypes = imi.getSubtypes();
1911        if (subtypes == null || subtypes.size() == 0) {
1912            return NOT_A_SUBTYPE_ID;
1913        }
1914        final String locale = mContext.getResources().getConfiguration().locale.toString();
1915        final String language = locale.substring(0, 2);
1916        boolean partialMatchFound = false;
1917        int applicableSubtypeId = DEFAULT_SUBTYPE_ID;
1918        for (int i = 0; i < subtypes.size(); ++i) {
1919            final String subtypeLocale = subtypes.get(i).getLocale();
1920            // An applicable subtype should be a keyboard subtype
1921            if (subtypes.get(i).getMode().equalsIgnoreCase("keyboard")) {
1922                if (locale.equals(subtypeLocale)) {
1923                    // Exact match (e.g. system locale is "en_US" and subtype locale is "en_US")
1924                    applicableSubtypeId = i;
1925                    break;
1926                } else if (!partialMatchFound && subtypeLocale.startsWith(language)) {
1927                    // Partial match (e.g. system locale is "en_US" and subtype locale is "en")
1928                    applicableSubtypeId = i;
1929                    partialMatchFound = true;
1930                }
1931            }
1932        }
1933
1934        // The first subtype applicable to the system locale will be defined as the most applicable
1935        // subtype.
1936        if (DEBUG) {
1937            Slog.d(TAG, "Applicable InputMethodSubtype was found: " + applicableSubtypeId + ","
1938                    + subtypes.get(applicableSubtypeId).getLocale());
1939        }
1940        return applicableSubtypeId;
1941    }
1942
1943    /**
1944     * @return Return the current subtype of this input method.
1945     */
1946    public InputMethodSubtype getCurrentInputMethodSubtype() {
1947        boolean subtypeIsSelected = false;
1948        try {
1949            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
1950                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
1951        } catch (SettingNotFoundException e) {
1952        }
1953        if (!subtypeIsSelected || mCurrentSubtype == null) {
1954            String lastInputMethodId = Settings.Secure.getString(mContext
1955                    .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
1956            int subtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
1957            if (subtypeId == NOT_A_SUBTYPE_ID) {
1958                subtypeId = findApplicableSubtype(lastInputMethodId);
1959            }
1960            if (subtypeId != NOT_A_SUBTYPE_ID) {
1961                mCurrentSubtype = mMethodMap.get(lastInputMethodId).getSubtypes().get(subtypeId);
1962            }
1963        }
1964        return mCurrentSubtype;
1965    }
1966
1967    /**
1968     * Utility class for putting and getting settings for InputMethod
1969     * TODO: Move all putters and getters of settings to this class.
1970     */
1971    private static class InputMethodSettings {
1972        // The string for enabled input method is saved as follows:
1973        // example: ("ime0;subtype0;subtype1;subtype2:ime1:ime2;subtype0")
1974        private static final char INPUT_METHOD_SEPARATER = ':';
1975        private static final char INPUT_METHOD_SUBTYPE_SEPARATER = ';';
1976        private final TextUtils.SimpleStringSplitter mInputMethodSplitter =
1977                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SEPARATER);
1978
1979        private final TextUtils.SimpleStringSplitter mSubtypeSplitter =
1980                new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATER);
1981
1982        private final ContentResolver mResolver;
1983        private final HashMap<String, InputMethodInfo> mMethodMap;
1984        private final ArrayList<InputMethodInfo> mMethodList;
1985
1986        private String mEnabledInputMethodsStrCache;
1987
1988        private static void buildEnabledInputMethodsSettingString(
1989                StringBuilder builder, Pair<String, ArrayList<String>> pair) {
1990            String id = pair.first;
1991            ArrayList<String> subtypes = pair.second;
1992            builder.append(id);
1993            // Inputmethod and subtypes are saved in the settings as follows:
1994            // ime0;subtype0;subtype1:ime1;subtype0:ime2:ime3;subtype0;subtype1
1995            for (String subtypeId: subtypes) {
1996                builder.append(INPUT_METHOD_SUBTYPE_SEPARATER).append(subtypeId);
1997            }
1998        }
1999
2000        public InputMethodSettings(
2001                ContentResolver resolver, HashMap<String, InputMethodInfo> methodMap,
2002                ArrayList<InputMethodInfo> methodList) {
2003            mResolver = resolver;
2004            mMethodMap = methodMap;
2005            mMethodList = methodList;
2006        }
2007
2008        public List<InputMethodInfo> getEnabledInputMethodListLocked() {
2009            return createEnabledInputMethodListLocked(
2010                    getEnabledInputMethodsAndSubtypeListLocked());
2011        }
2012
2013        public List<Pair<InputMethodInfo, ArrayList<String>>>
2014                getEnabledInputMethodAndSubtypeListLocked() {
2015            return createEnabledInputMethodAndSubtypeListLocked(
2016                    getEnabledInputMethodsAndSubtypeListLocked());
2017        }
2018
2019        // At the initial boot, the settings for input methods are not set,
2020        // so we need to enable IME in that case.
2021        public void enableAllIMEsIfThereIsNoEnabledIME() {
2022            if (TextUtils.isEmpty(getEnabledInputMethodsStr())) {
2023                StringBuilder sb = new StringBuilder();
2024                final int N = mMethodList.size();
2025                for (int i = 0; i < N; i++) {
2026                    InputMethodInfo imi = mMethodList.get(i);
2027                    Slog.i(TAG, "Adding: " + imi.getId());
2028                    if (i > 0) sb.append(':');
2029                    sb.append(imi.getId());
2030                }
2031                putEnabledInputMethodsStr(sb.toString());
2032            }
2033        }
2034
2035        public List<Pair<String, ArrayList<String>>> getEnabledInputMethodsAndSubtypeListLocked() {
2036            ArrayList<Pair<String, ArrayList<String>>> imsList
2037                    = new ArrayList<Pair<String, ArrayList<String>>>();
2038            final String enabledInputMethodsStr = getEnabledInputMethodsStr();
2039            if (TextUtils.isEmpty(enabledInputMethodsStr)) {
2040                return imsList;
2041            }
2042            mInputMethodSplitter.setString(enabledInputMethodsStr);
2043            while (mInputMethodSplitter.hasNext()) {
2044                String nextImsStr = mInputMethodSplitter.next();
2045                mSubtypeSplitter.setString(nextImsStr);
2046                if (mSubtypeSplitter.hasNext()) {
2047                    ArrayList<String> subtypeHashes = new ArrayList<String>();
2048                    // The first element is ime id.
2049                    String imeId = mSubtypeSplitter.next();
2050                    while (mSubtypeSplitter.hasNext()) {
2051                        subtypeHashes.add(mSubtypeSplitter.next());
2052                    }
2053                    imsList.add(new Pair<String, ArrayList<String>>(imeId, subtypeHashes));
2054                }
2055            }
2056            return imsList;
2057        }
2058
2059        public void appendAndPutEnabledInputMethodLocked(String id, boolean reloadInputMethodStr) {
2060            if (reloadInputMethodStr) {
2061                getEnabledInputMethodsStr();
2062            }
2063            if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
2064                // Add in the newly enabled input method.
2065                putEnabledInputMethodsStr(id);
2066            } else {
2067                putEnabledInputMethodsStr(
2068                        mEnabledInputMethodsStrCache + INPUT_METHOD_SEPARATER + id);
2069            }
2070        }
2071
2072        /**
2073         * Build and put a string of EnabledInputMethods with removing specified Id.
2074         * @return the specified id was removed or not.
2075         */
2076        public boolean buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2077                StringBuilder builder, List<Pair<String, ArrayList<String>>> imsList, String id) {
2078            boolean isRemoved = false;
2079            boolean needsAppendSeparator = false;
2080            for (Pair<String, ArrayList<String>> ims: imsList) {
2081                String curId = ims.first;
2082                if (curId.equals(id)) {
2083                    // We are disabling this input method, and it is
2084                    // currently enabled.  Skip it to remove from the
2085                    // new list.
2086                    isRemoved = true;
2087                } else {
2088                    if (needsAppendSeparator) {
2089                        builder.append(INPUT_METHOD_SEPARATER);
2090                    } else {
2091                        needsAppendSeparator = true;
2092                    }
2093                    buildEnabledInputMethodsSettingString(builder, ims);
2094                }
2095            }
2096            if (isRemoved) {
2097                // Update the setting with the new list of input methods.
2098                putEnabledInputMethodsStr(builder.toString());
2099            }
2100            return isRemoved;
2101        }
2102
2103        private List<InputMethodInfo> createEnabledInputMethodListLocked(
2104                List<Pair<String, ArrayList<String>>> imsList) {
2105            final ArrayList<InputMethodInfo> res = new ArrayList<InputMethodInfo>();
2106            for (Pair<String, ArrayList<String>> ims: imsList) {
2107                InputMethodInfo info = mMethodMap.get(ims.first);
2108                if (info != null) {
2109                    res.add(info);
2110                }
2111            }
2112            return res;
2113        }
2114
2115        private List<Pair<InputMethodInfo, ArrayList<String>>>
2116                createEnabledInputMethodAndSubtypeListLocked(
2117                        List<Pair<String, ArrayList<String>>> imsList) {
2118            final ArrayList<Pair<InputMethodInfo, ArrayList<String>>> res
2119                    = new ArrayList<Pair<InputMethodInfo, ArrayList<String>>>();
2120            for (Pair<String, ArrayList<String>> ims : imsList) {
2121                InputMethodInfo info = mMethodMap.get(ims.first);
2122                if (info != null) {
2123                    res.add(new Pair<InputMethodInfo, ArrayList<String>>(info, ims.second));
2124                }
2125            }
2126            return res;
2127        }
2128
2129        private void putEnabledInputMethodsStr(String str) {
2130            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
2131            mEnabledInputMethodsStrCache = str;
2132        }
2133
2134        private String getEnabledInputMethodsStr() {
2135            mEnabledInputMethodsStrCache = Settings.Secure.getString(
2136                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
2137            if (DEBUG) {
2138                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
2139            }
2140            return mEnabledInputMethodsStrCache;
2141        }
2142
2143        private void saveSubtypeHistory(
2144                List<Pair<String, String>> savedImes, String newImeId, String newSubtypeId) {
2145            StringBuilder builder = new StringBuilder();
2146            boolean isImeAdded = false;
2147            if (!TextUtils.isEmpty(newImeId) && !TextUtils.isEmpty(newSubtypeId)) {
2148                builder.append(newImeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2149                        newSubtypeId);
2150                isImeAdded = true;
2151            }
2152            for (Pair<String, String> ime: savedImes) {
2153                String imeId = ime.first;
2154                String subtypeId = ime.second;
2155                if (TextUtils.isEmpty(subtypeId)) {
2156                    subtypeId = NOT_A_SUBTYPE_ID_STR;
2157                }
2158                if (isImeAdded) {
2159                    builder.append(INPUT_METHOD_SEPARATER);
2160                } else {
2161                    isImeAdded = true;
2162                }
2163                builder.append(imeId).append(INPUT_METHOD_SUBTYPE_SEPARATER).append(
2164                        subtypeId);
2165            }
2166            // Remove the last INPUT_METHOD_SEPARATER
2167            putSubtypeHistoryStr(builder.toString());
2168        }
2169
2170        public void addSubtypeToHistory(String imeId, String subtypeId) {
2171            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2172            for (Pair<String, String> ime: subtypeHistory) {
2173                if (ime.first.equals(imeId)) {
2174                    if (DEBUG) {
2175                        Slog.v(TAG, "Subtype found in the history: " + imeId
2176                                + ime.second);
2177                    }
2178                    // We should break here
2179                    subtypeHistory.remove(ime);
2180                    break;
2181                }
2182            }
2183            saveSubtypeHistory(subtypeHistory, imeId, subtypeId);
2184        }
2185
2186        private void putSubtypeHistoryStr(String str) {
2187            if (DEBUG) {
2188                Slog.d(TAG, "putSubtypeHistoryStr: " + str);
2189            }
2190            Settings.Secure.putString(
2191                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
2192        }
2193
2194        public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
2195            // Gets the first one from the history
2196            return getLastSubtypeForInputMethodLockedInternal(null);
2197        }
2198
2199        public String getLastSubtypeForInputMethodLocked(String imeId) {
2200            Pair<String, String> ime = getLastSubtypeForInputMethodLockedInternal(imeId);
2201            if (ime != null) {
2202                return ime.second;
2203            } else {
2204                return null;
2205            }
2206        }
2207
2208        private Pair<String, String> getLastSubtypeForInputMethodLockedInternal(String imeId) {
2209            List<Pair<String, ArrayList<String>>> enabledImes =
2210                    getEnabledInputMethodsAndSubtypeListLocked();
2211            List<Pair<String, String>> subtypeHistory = loadInputMethodAndSubtypeHistoryLocked();
2212            for (Pair<String, String> imeAndSubtype: subtypeHistory) {
2213                final String imeInTheHistory = imeAndSubtype.first;
2214                // If imeId is empty, returns the first IME and subtype in the history
2215                if (TextUtils.isEmpty(imeId) || imeInTheHistory.equals(imeId)) {
2216                    final String subtypeInTheHistory = imeAndSubtype.second;
2217                    final String subtypeHashCode = getEnabledSubtypeForInputMethodAndSubtypeLocked(
2218                            enabledImes, imeInTheHistory, subtypeInTheHistory);
2219                    if (!TextUtils.isEmpty(subtypeHashCode)) {
2220                        if (DEBUG) {
2221                            Slog.d(TAG, "Enabled subtype found in the history:" + subtypeHashCode);
2222                        }
2223                        return new Pair<String, String>(imeInTheHistory, subtypeHashCode);
2224                    }
2225                }
2226            }
2227            if (DEBUG) {
2228                Slog.d(TAG, "No enabled IME found in the history");
2229            }
2230            return null;
2231        }
2232
2233        private String getEnabledSubtypeForInputMethodAndSubtypeLocked(List<Pair<String,
2234                ArrayList<String>>> enabledImes, String imeId, String subtypeHashCode) {
2235            for (Pair<String, ArrayList<String>> enabledIme: enabledImes) {
2236                if (enabledIme.first.equals(imeId)) {
2237                    for (String s: enabledIme.second) {
2238                        if (s.equals(subtypeHashCode)) {
2239                            // If both imeId and subtypeId are enabled, return subtypeId.
2240                            return s;
2241                        }
2242                    }
2243                    // If imeId was enabled but subtypeId was disabled.
2244                    return NOT_A_SUBTYPE_ID_STR;
2245                }
2246            }
2247            // If both imeId and subtypeId are disabled, return null
2248            return null;
2249        }
2250
2251        private List<Pair<String, String>> loadInputMethodAndSubtypeHistoryLocked() {
2252            ArrayList<Pair<String, String>> imsList = new ArrayList<Pair<String, String>>();
2253            final String subtypeHistoryStr = getSubtypeHistoryStr();
2254            if (TextUtils.isEmpty(subtypeHistoryStr)) {
2255                return imsList;
2256            }
2257            mInputMethodSplitter.setString(subtypeHistoryStr);
2258            while (mInputMethodSplitter.hasNext()) {
2259                String nextImsStr = mInputMethodSplitter.next();
2260                mSubtypeSplitter.setString(nextImsStr);
2261                if (mSubtypeSplitter.hasNext()) {
2262                    String subtypeId = NOT_A_SUBTYPE_ID_STR;
2263                    // The first element is ime id.
2264                    String imeId = mSubtypeSplitter.next();
2265                    while (mSubtypeSplitter.hasNext()) {
2266                        subtypeId = mSubtypeSplitter.next();
2267                        break;
2268                    }
2269                    imsList.add(new Pair<String, String>(imeId, subtypeId));
2270                }
2271            }
2272            return imsList;
2273        }
2274
2275        private String getSubtypeHistoryStr() {
2276            if (DEBUG) {
2277                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
2278                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
2279            }
2280            return Settings.Secure.getString(
2281                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
2282        }
2283
2284        public void putSelectedInputMethod(String imeId) {
2285            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
2286        }
2287
2288        public void putSelectedSubtype(int subtypeId) {
2289            Settings.Secure.putInt(
2290                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
2291        }
2292    }
2293
2294    // ----------------------------------------------------------------------
2295
2296    @Override
2297    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
2298        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
2299                != PackageManager.PERMISSION_GRANTED) {
2300
2301            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
2302                    + Binder.getCallingPid()
2303                    + ", uid=" + Binder.getCallingUid());
2304            return;
2305        }
2306
2307        IInputMethod method;
2308        ClientState client;
2309
2310        final Printer p = new PrintWriterPrinter(pw);
2311
2312        synchronized (mMethodMap) {
2313            p.println("Current Input Method Manager state:");
2314            int N = mMethodList.size();
2315            p.println("  Input Methods:");
2316            for (int i=0; i<N; i++) {
2317                InputMethodInfo info = mMethodList.get(i);
2318                p.println("  InputMethod #" + i + ":");
2319                info.dump(p, "    ");
2320            }
2321            p.println("  Clients:");
2322            for (ClientState ci : mClients.values()) {
2323                p.println("  Client " + ci + ":");
2324                p.println("    client=" + ci.client);
2325                p.println("    inputContext=" + ci.inputContext);
2326                p.println("    sessionRequested=" + ci.sessionRequested);
2327                p.println("    curSession=" + ci.curSession);
2328            }
2329            p.println("  mCurMethodId=" + mCurMethodId);
2330            client = mCurClient;
2331            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
2332            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
2333            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
2334                    + " mBoundToMethod=" + mBoundToMethod);
2335            p.println("  mCurToken=" + mCurToken);
2336            p.println("  mCurIntent=" + mCurIntent);
2337            method = mCurMethod;
2338            p.println("  mCurMethod=" + mCurMethod);
2339            p.println("  mEnabledSession=" + mEnabledSession);
2340            p.println("  mShowRequested=" + mShowRequested
2341                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
2342                    + " mShowForced=" + mShowForced
2343                    + " mInputShown=" + mInputShown);
2344            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
2345        }
2346
2347        p.println(" ");
2348        if (client != null) {
2349            pw.flush();
2350            try {
2351                client.client.asBinder().dump(fd, args);
2352            } catch (RemoteException e) {
2353                p.println("Input method client dead: " + e);
2354            }
2355        } else {
2356            p.println("No input method client.");
2357        }
2358
2359        p.println(" ");
2360        if (method != null) {
2361            pw.flush();
2362            try {
2363                method.asBinder().dump(fd, args);
2364            } catch (RemoteException e) {
2365                p.println("Input method service dead: " + e);
2366            }
2367        } else {
2368            p.println("No input method service.");
2369        }
2370    }
2371}
2372