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