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