InputMethodManagerService.java revision f0f94d129b6eb3c48624e915898d86d4f2de59ff
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_NOT_FOREGROUND
1229                | Context.BIND_SHOWING_UI)) {
1230            mLastBindTime = SystemClock.uptimeMillis();
1231            mHaveConnection = true;
1232            mCurId = info.getId();
1233            mCurToken = new Binder();
1234            try {
1235                if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1236                mIWindowManager.addWindowToken(mCurToken,
1237                        WindowManager.LayoutParams.TYPE_INPUT_METHOD);
1238            } catch (RemoteException e) {
1239            }
1240            return new InputBindResult(null, null, mCurId, mCurSeq);
1241        } else {
1242            mCurIntent = null;
1243            Slog.w(TAG, "Failure connecting to input method service: "
1244                    + mCurIntent);
1245        }
1246        return null;
1247    }
1248
1249    @Override
1250    public InputBindResult startInput(IInputMethodClient client,
1251            IInputContext inputContext, EditorInfo attribute, int controlFlags) {
1252        if (!calledFromValidUser()) {
1253            return null;
1254        }
1255        synchronized (mMethodMap) {
1256            final long ident = Binder.clearCallingIdentity();
1257            try {
1258                return startInputLocked(client, inputContext, attribute, controlFlags);
1259            } finally {
1260                Binder.restoreCallingIdentity(ident);
1261            }
1262        }
1263    }
1264
1265    @Override
1266    public void finishInput(IInputMethodClient client) {
1267    }
1268
1269    @Override
1270    public void onServiceConnected(ComponentName name, IBinder service) {
1271        synchronized (mMethodMap) {
1272            if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
1273                mCurMethod = IInputMethod.Stub.asInterface(service);
1274                if (mCurToken == null) {
1275                    Slog.w(TAG, "Service connected without a token!");
1276                    unbindCurrentMethodLocked(false, false);
1277                    return;
1278                }
1279                if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
1280                executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1281                        MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
1282                if (mCurClient != null) {
1283                    clearClientSessionLocked(mCurClient);
1284                    requestClientSessionLocked(mCurClient);
1285                }
1286            }
1287        }
1288    }
1289
1290    void onSessionCreated(IInputMethod method, IInputMethodSession session,
1291            InputChannel channel) {
1292        synchronized (mMethodMap) {
1293            if (mCurMethod != null && method != null
1294                    && mCurMethod.asBinder() == method.asBinder()) {
1295                if (mCurClient != null) {
1296                    clearClientSessionLocked(mCurClient);
1297                    mCurClient.curSession = new SessionState(mCurClient,
1298                            method, session, channel);
1299                    InputBindResult res = attachNewInputLocked(true);
1300                    if (res.method != null) {
1301                        executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
1302                                MSG_BIND_METHOD, mCurClient.client, res));
1303                    }
1304                    return;
1305                }
1306            }
1307        }
1308
1309        // Session abandoned.  Close its associated input channel.
1310        channel.dispose();
1311    }
1312
1313    void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
1314        if (mVisibleBound) {
1315            mContext.unbindService(mVisibleConnection);
1316            mVisibleBound = false;
1317        }
1318
1319        if (mHaveConnection) {
1320            mContext.unbindService(this);
1321            mHaveConnection = false;
1322        }
1323
1324        if (mCurToken != null) {
1325            try {
1326                if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
1327                if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
1328                    // The current IME is shown. Hence an IME switch (transition) is happening.
1329                    mWindowManagerService.saveLastInputMethodWindowForTransition();
1330                }
1331                mIWindowManager.removeWindowToken(mCurToken);
1332            } catch (RemoteException e) {
1333            }
1334            mCurToken = null;
1335        }
1336
1337        mCurId = null;
1338        clearCurMethodLocked();
1339
1340        if (reportToClient && mCurClient != null) {
1341            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1342                    MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1343        }
1344    }
1345
1346    void requestClientSessionLocked(ClientState cs) {
1347        if (!cs.sessionRequested) {
1348            if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
1349            InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
1350            cs.sessionRequested = true;
1351            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
1352                    MSG_CREATE_SESSION, mCurMethod, channels[1],
1353                    new MethodCallback(this, mCurMethod, channels[0])));
1354        }
1355    }
1356
1357    void clearClientSessionLocked(ClientState cs) {
1358        finishSessionLocked(cs.curSession);
1359        cs.curSession = null;
1360        cs.sessionRequested = false;
1361    }
1362
1363    private void finishSessionLocked(SessionState sessionState) {
1364        if (sessionState != null) {
1365            if (sessionState.session != null) {
1366                try {
1367                    sessionState.session.finishSession();
1368                } catch (RemoteException e) {
1369                    Slog.w(TAG, "Session failed to close due to remote exception", e);
1370                    setImeWindowVisibilityStatusHiddenLocked();
1371                }
1372                sessionState.session = null;
1373            }
1374            if (sessionState.channel != null) {
1375                sessionState.channel.dispose();
1376                sessionState.channel = null;
1377            }
1378        }
1379    }
1380
1381    void clearCurMethodLocked() {
1382        if (mCurMethod != null) {
1383            for (ClientState cs : mClients.values()) {
1384                clearClientSessionLocked(cs);
1385            }
1386
1387            finishSessionLocked(mEnabledSession);
1388            mEnabledSession = null;
1389            mCurMethod = null;
1390        }
1391        if (mStatusBar != null) {
1392            mStatusBar.setIconVisibility("ime", false);
1393        }
1394    }
1395
1396    @Override
1397    public void onServiceDisconnected(ComponentName name) {
1398        synchronized (mMethodMap) {
1399            if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1400                    + " mCurIntent=" + mCurIntent);
1401            if (mCurMethod != null && mCurIntent != null
1402                    && name.equals(mCurIntent.getComponent())) {
1403                clearCurMethodLocked();
1404                // We consider this to be a new bind attempt, since the system
1405                // should now try to restart the service for us.
1406                mLastBindTime = SystemClock.uptimeMillis();
1407                mShowRequested = mInputShown;
1408                mInputShown = false;
1409                if (mCurClient != null) {
1410                    executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
1411                            MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
1412                }
1413            }
1414        }
1415    }
1416
1417    @Override
1418    public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1419        int uid = Binder.getCallingUid();
1420        long ident = Binder.clearCallingIdentity();
1421        try {
1422            if (token == null || mCurToken != token) {
1423                Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
1424                return;
1425            }
1426
1427            synchronized (mMethodMap) {
1428                if (iconId == 0) {
1429                    if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1430                    if (mStatusBar != null) {
1431                        mStatusBar.setIconVisibility("ime", false);
1432                    }
1433                } else if (packageName != null) {
1434                    if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1435                    CharSequence contentDescription = null;
1436                    try {
1437                        // Use PackageManager to load label
1438                        final PackageManager packageManager = mContext.getPackageManager();
1439                        contentDescription = packageManager.getApplicationLabel(
1440                                mIPackageManager.getApplicationInfo(packageName, 0,
1441                                        mSettings.getCurrentUserId()));
1442                    } catch (RemoteException e) {
1443                        /* ignore */
1444                    }
1445                    if (mStatusBar != null) {
1446                        mStatusBar.setIcon("ime", packageName, iconId, 0,
1447                                contentDescription  != null
1448                                        ? contentDescription.toString() : null);
1449                        mStatusBar.setIconVisibility("ime", true);
1450                    }
1451                }
1452            }
1453        } finally {
1454            Binder.restoreCallingIdentity(ident);
1455        }
1456    }
1457
1458    private boolean needsToShowImeSwitchOngoingNotification() {
1459        if (!mShowOngoingImeSwitcherForPhones) return false;
1460        if (isScreenLocked()) return false;
1461        synchronized (mMethodMap) {
1462            List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1463            final int N = imis.size();
1464            if (N > 2) return true;
1465            if (N < 1) return false;
1466            int nonAuxCount = 0;
1467            int auxCount = 0;
1468            InputMethodSubtype nonAuxSubtype = null;
1469            InputMethodSubtype auxSubtype = null;
1470            for(int i = 0; i < N; ++i) {
1471                final InputMethodInfo imi = imis.get(i);
1472                final List<InputMethodSubtype> subtypes =
1473                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
1474                final int subtypeCount = subtypes.size();
1475                if (subtypeCount == 0) {
1476                    ++nonAuxCount;
1477                } else {
1478                    for (int j = 0; j < subtypeCount; ++j) {
1479                        final InputMethodSubtype subtype = subtypes.get(j);
1480                        if (!subtype.isAuxiliary()) {
1481                            ++nonAuxCount;
1482                            nonAuxSubtype = subtype;
1483                        } else {
1484                            ++auxCount;
1485                            auxSubtype = subtype;
1486                        }
1487                    }
1488                }
1489            }
1490            if (nonAuxCount > 1 || auxCount > 1) {
1491                return true;
1492            } else if (nonAuxCount == 1 && auxCount == 1) {
1493                if (nonAuxSubtype != null && auxSubtype != null
1494                        && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
1495                                || auxSubtype.overridesImplicitlyEnabledSubtype()
1496                                || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
1497                        && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
1498                    return false;
1499                }
1500                return true;
1501            }
1502            return false;
1503        }
1504    }
1505
1506    private boolean isKeyguardLocked() {
1507        return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
1508    }
1509
1510    // Caution! This method is called in this class. Handle multi-user carefully
1511    @SuppressWarnings("deprecation")
1512    @Override
1513    public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
1514        final long ident = Binder.clearCallingIdentity();
1515        try {
1516            if (token == null || mCurToken != token) {
1517                int uid = Binder.getCallingUid();
1518                Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
1519                return;
1520            }
1521            synchronized (mMethodMap) {
1522                // apply policy for binder calls
1523                if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
1524                    vis = 0;
1525                }
1526                mImeWindowVis = vis;
1527                mBackDisposition = backDisposition;
1528                if (mStatusBar != null) {
1529                    mStatusBar.setImeWindowStatus(token, vis, backDisposition);
1530                }
1531                final boolean iconVisibility = ((vis & (InputMethodService.IME_ACTIVE)) != 0)
1532                        && (mWindowManagerService.isHardKeyboardAvailable()
1533                                || (vis & (InputMethodService.IME_VISIBLE)) != 0);
1534                final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
1535                if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
1536                    // Used to load label
1537                    final CharSequence title = mRes.getText(
1538                            com.android.internal.R.string.select_input_method);
1539                    final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
1540                            mContext, imi, mCurrentSubtype);
1541
1542                    mImeSwitcherNotification.setLatestEventInfo(
1543                            mContext, title, summary, mImeSwitchPendingIntent);
1544                    if (mNotificationManager != null) {
1545                        if (DEBUG) {
1546                            Slog.d(TAG, "--- show notification: label =  " + summary);
1547                        }
1548                        mNotificationManager.notifyAsUser(null,
1549                                com.android.internal.R.string.select_input_method,
1550                                mImeSwitcherNotification, UserHandle.ALL);
1551                        mNotificationShown = true;
1552                    }
1553                } else {
1554                    if (mNotificationShown && mNotificationManager != null) {
1555                        if (DEBUG) {
1556                            Slog.d(TAG, "--- hide notification");
1557                        }
1558                        mNotificationManager.cancelAsUser(null,
1559                                com.android.internal.R.string.select_input_method, UserHandle.ALL);
1560                        mNotificationShown = false;
1561                    }
1562                }
1563            }
1564        } finally {
1565            Binder.restoreCallingIdentity(ident);
1566        }
1567    }
1568
1569    @Override
1570    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1571        if (!calledFromValidUser()) {
1572            return;
1573        }
1574        synchronized (mMethodMap) {
1575            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1576            for (int i = 0; i < spans.length; ++i) {
1577                SuggestionSpan ss = spans[i];
1578                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1579                    mSecureSuggestionSpans.put(ss, currentImi);
1580                }
1581            }
1582        }
1583    }
1584
1585    @Override
1586    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1587        if (!calledFromValidUser()) {
1588            return false;
1589        }
1590        synchronized (mMethodMap) {
1591            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1592            // TODO: Do not send the intent if the process of the targetImi is already dead.
1593            if (targetImi != null) {
1594                final String[] suggestions = span.getSuggestions();
1595                if (index < 0 || index >= suggestions.length) return false;
1596                final String className = span.getNotificationTargetClassName();
1597                final Intent intent = new Intent();
1598                // Ensures that only a class in the original IME package will receive the
1599                // notification.
1600                intent.setClassName(targetImi.getPackageName(), className);
1601                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1602                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1603                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1604                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1605                final long ident = Binder.clearCallingIdentity();
1606                try {
1607                    mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1608                } finally {
1609                    Binder.restoreCallingIdentity(ident);
1610                }
1611                return true;
1612            }
1613        }
1614        return false;
1615    }
1616
1617    void updateFromSettingsLocked(boolean enabledMayChange) {
1618        if (enabledMayChange) {
1619            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1620            for (int i=0; i<enabled.size(); i++) {
1621                // We allow the user to select "disabled until used" apps, so if they
1622                // are enabling one of those here we now need to make it enabled.
1623                InputMethodInfo imm = enabled.get(i);
1624                try {
1625                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
1626                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
1627                            mSettings.getCurrentUserId());
1628                    if (ai != null && ai.enabledSetting
1629                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
1630                        if (DEBUG) {
1631                            Slog.d(TAG, "Update state(" + imm.getId()
1632                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
1633                        }
1634                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
1635                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
1636                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
1637                                mContext.getBasePackageName());
1638                    }
1639                } catch (RemoteException e) {
1640                }
1641            }
1642        }
1643        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1644        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1645        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1646        // enabled.
1647        String id = mSettings.getSelectedInputMethod();
1648        // There is no input method selected, try to choose new applicable input method.
1649        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1650            id = mSettings.getSelectedInputMethod();
1651        }
1652        if (!TextUtils.isEmpty(id)) {
1653            try {
1654                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
1655            } catch (IllegalArgumentException e) {
1656                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1657                mCurMethodId = null;
1658                unbindCurrentMethodLocked(true, false);
1659            }
1660            mShortcutInputMethodsAndSubtypes.clear();
1661        } else {
1662            // There is no longer an input method set, so stop any current one.
1663            mCurMethodId = null;
1664            unbindCurrentMethodLocked(true, false);
1665        }
1666    }
1667
1668    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1669        InputMethodInfo info = mMethodMap.get(id);
1670        if (info == null) {
1671            throw new IllegalArgumentException("Unknown id: " + id);
1672        }
1673
1674        // See if we need to notify a subtype change within the same IME.
1675        if (id.equals(mCurMethodId)) {
1676            final int subtypeCount = info.getSubtypeCount();
1677            if (subtypeCount <= 0) {
1678                return;
1679            }
1680            final InputMethodSubtype oldSubtype = mCurrentSubtype;
1681            final InputMethodSubtype newSubtype;
1682            if (subtypeId >= 0 && subtypeId < subtypeCount) {
1683                newSubtype = info.getSubtypeAt(subtypeId);
1684            } else {
1685                // If subtype is null, try to find the most applicable one from
1686                // getCurrentInputMethodSubtype.
1687                newSubtype = getCurrentInputMethodSubtypeLocked();
1688            }
1689            if (newSubtype == null || oldSubtype == null) {
1690                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
1691                        + ", new subtype = " + newSubtype);
1692                return;
1693            }
1694            if (newSubtype != oldSubtype) {
1695                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1696                if (mCurMethod != null) {
1697                    try {
1698                        refreshImeWindowVisibilityLocked();
1699                        mCurMethod.changeInputMethodSubtype(newSubtype);
1700                    } catch (RemoteException e) {
1701                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
1702                    }
1703                }
1704            }
1705            return;
1706        }
1707
1708        // Changing to a different IME.
1709        final long ident = Binder.clearCallingIdentity();
1710        try {
1711            // Set a subtype to this input method.
1712            // subtypeId the name of a subtype which will be set.
1713            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1714            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1715            // because mCurMethodId is stored as a history in
1716            // setSelectedInputMethodAndSubtypeLocked().
1717            mCurMethodId = id;
1718
1719            if (ActivityManagerNative.isSystemReady()) {
1720                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1721                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1722                intent.putExtra("input_method_id", id);
1723                mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1724            }
1725            unbindCurrentClientLocked();
1726        } finally {
1727            Binder.restoreCallingIdentity(ident);
1728        }
1729    }
1730
1731    @Override
1732    public boolean showSoftInput(IInputMethodClient client, int flags,
1733            ResultReceiver resultReceiver) {
1734        if (!calledFromValidUser()) {
1735            return false;
1736        }
1737        int uid = Binder.getCallingUid();
1738        long ident = Binder.clearCallingIdentity();
1739        try {
1740            synchronized (mMethodMap) {
1741                if (mCurClient == null || client == null
1742                        || mCurClient.client.asBinder() != client.asBinder()) {
1743                    try {
1744                        // We need to check if this is the current client with
1745                        // focus in the window manager, to allow this call to
1746                        // be made before input is started in it.
1747                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1748                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1749                            return false;
1750                        }
1751                    } catch (RemoteException e) {
1752                        return false;
1753                    }
1754                }
1755
1756                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1757                return showCurrentInputLocked(flags, resultReceiver);
1758            }
1759        } finally {
1760            Binder.restoreCallingIdentity(ident);
1761        }
1762    }
1763
1764    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1765        mShowRequested = true;
1766        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1767            mShowExplicitlyRequested = true;
1768        }
1769        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1770            mShowExplicitlyRequested = true;
1771            mShowForced = true;
1772        }
1773
1774        if (!mSystemReady) {
1775            return false;
1776        }
1777
1778        boolean res = false;
1779        if (mCurMethod != null) {
1780            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
1781            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1782                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1783                    resultReceiver));
1784            mInputShown = true;
1785            if (mHaveConnection && !mVisibleBound) {
1786                bindCurrentInputMethodService(
1787                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
1788                                | Context.BIND_TREAT_LIKE_ACTIVITY);
1789                mVisibleBound = true;
1790            }
1791            res = true;
1792        } else if (mHaveConnection && SystemClock.uptimeMillis()
1793                >= (mLastBindTime+TIME_TO_RECONNECT)) {
1794            // The client has asked to have the input method shown, but
1795            // we have been sitting here too long with a connection to the
1796            // service and no interface received, so let's disconnect/connect
1797            // to try to prod things along.
1798            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1799                    SystemClock.uptimeMillis()-mLastBindTime,1);
1800            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
1801            mContext.unbindService(this);
1802            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1803                    | Context.BIND_NOT_VISIBLE);
1804        } else {
1805            if (DEBUG) {
1806                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
1807                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
1808            }
1809        }
1810
1811        return res;
1812    }
1813
1814    @Override
1815    public boolean hideSoftInput(IInputMethodClient client, int flags,
1816            ResultReceiver resultReceiver) {
1817        if (!calledFromValidUser()) {
1818            return false;
1819        }
1820        int uid = Binder.getCallingUid();
1821        long ident = Binder.clearCallingIdentity();
1822        try {
1823            synchronized (mMethodMap) {
1824                if (mCurClient == null || client == null
1825                        || mCurClient.client.asBinder() != client.asBinder()) {
1826                    try {
1827                        // We need to check if this is the current client with
1828                        // focus in the window manager, to allow this call to
1829                        // be made before input is started in it.
1830                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1831                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1832                                    + uid + ": " + client);
1833                            setImeWindowVisibilityStatusHiddenLocked();
1834                            return false;
1835                        }
1836                    } catch (RemoteException e) {
1837                        setImeWindowVisibilityStatusHiddenLocked();
1838                        return false;
1839                    }
1840                }
1841
1842                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1843                return hideCurrentInputLocked(flags, resultReceiver);
1844            }
1845        } finally {
1846            Binder.restoreCallingIdentity(ident);
1847        }
1848    }
1849
1850    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1851        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1852                && (mShowExplicitlyRequested || mShowForced)) {
1853            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
1854            return false;
1855        }
1856        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1857            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
1858            return false;
1859        }
1860        boolean res;
1861        if (mInputShown && mCurMethod != null) {
1862            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1863                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1864            res = true;
1865        } else {
1866            res = false;
1867        }
1868        if (mHaveConnection && mVisibleBound) {
1869            mContext.unbindService(mVisibleConnection);
1870            mVisibleBound = false;
1871        }
1872        mInputShown = false;
1873        mShowRequested = false;
1874        mShowExplicitlyRequested = false;
1875        mShowForced = false;
1876        return res;
1877    }
1878
1879    @Override
1880    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1881            int controlFlags, int softInputMode, int windowFlags,
1882            EditorInfo attribute, IInputContext inputContext) {
1883        // Needs to check the validity before clearing calling identity
1884        final boolean calledFromValidUser = calledFromValidUser();
1885
1886        InputBindResult res = null;
1887        long ident = Binder.clearCallingIdentity();
1888        try {
1889            synchronized (mMethodMap) {
1890                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1891                        + " controlFlags=#" + Integer.toHexString(controlFlags)
1892                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1893                        + " windowFlags=#" + Integer.toHexString(windowFlags));
1894
1895                ClientState cs = mClients.get(client.asBinder());
1896                if (cs == null) {
1897                    throw new IllegalArgumentException("unknown client "
1898                            + client.asBinder());
1899                }
1900
1901                try {
1902                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1903                        // Check with the window manager to make sure this client actually
1904                        // has a window with focus.  If not, reject.  This is thread safe
1905                        // because if the focus changes some time before or after, the
1906                        // next client receiving focus that has any interest in input will
1907                        // be calling through here after that change happens.
1908                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
1909                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1910                        return null;
1911                    }
1912                } catch (RemoteException e) {
1913                }
1914
1915                if (!calledFromValidUser) {
1916                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
1917                    Slog.w(TAG, "If you want to interect with IME, you need "
1918                            + "android.permission.INTERACT_ACROSS_USERS_FULL");
1919                    hideCurrentInputLocked(0, null);
1920                    return null;
1921                }
1922
1923                if (mCurFocusedWindow == windowToken) {
1924                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
1925                            + " attribute=" + attribute + ", token = " + windowToken);
1926                    if (attribute != null) {
1927                        return startInputUncheckedLocked(cs, inputContext, attribute,
1928                                controlFlags);
1929                    }
1930                    return null;
1931                }
1932                mCurFocusedWindow = windowToken;
1933
1934                // Should we auto-show the IME even if the caller has not
1935                // specified what should be done with it?
1936                // We only do this automatically if the window can resize
1937                // to accommodate the IME (so what the user sees will give
1938                // them good context without input information being obscured
1939                // by the IME) or if running on a large screen where there
1940                // is more room for the target window + IME.
1941                final boolean doAutoShow =
1942                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
1943                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
1944                        || mRes.getConfiguration().isLayoutSizeAtLeast(
1945                                Configuration.SCREENLAYOUT_SIZE_LARGE);
1946                final boolean isTextEditor =
1947                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
1948
1949                // We want to start input before showing the IME, but after closing
1950                // it.  We want to do this after closing it to help the IME disappear
1951                // more quickly (not get stuck behind it initializing itself for the
1952                // new focused input, even if its window wants to hide the IME).
1953                boolean didStart = false;
1954
1955                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
1956                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
1957                        if (!isTextEditor || !doAutoShow) {
1958                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
1959                                // There is no focus view, and this window will
1960                                // be behind any soft input window, so hide the
1961                                // soft input window if it is shown.
1962                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
1963                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
1964                            }
1965                        } else if (isTextEditor && doAutoShow && (softInputMode &
1966                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1967                            // There is a focus view, and we are navigating forward
1968                            // into the window, so show the input window for the user.
1969                            // We only do this automatically if the window can resize
1970                            // to accommodate the IME (so what the user sees will give
1971                            // them good context without input information being obscured
1972                            // by the IME) or if running on a large screen where there
1973                            // is more room for the target window + IME.
1974                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
1975                            if (attribute != null) {
1976                                res = startInputUncheckedLocked(cs, inputContext, attribute,
1977                                        controlFlags);
1978                                didStart = true;
1979                            }
1980                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
1981                        }
1982                        break;
1983                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
1984                        // Do nothing.
1985                        break;
1986                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
1987                        if ((softInputMode &
1988                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
1989                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
1990                            hideCurrentInputLocked(0, null);
1991                        }
1992                        break;
1993                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
1994                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
1995                        hideCurrentInputLocked(0, null);
1996                        break;
1997                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
1998                        if ((softInputMode &
1999                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2000                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2001                            if (attribute != null) {
2002                                res = startInputUncheckedLocked(cs, inputContext, attribute,
2003                                        controlFlags);
2004                                didStart = true;
2005                            }
2006                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2007                        }
2008                        break;
2009                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2010                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2011                        if (attribute != null) {
2012                            res = startInputUncheckedLocked(cs, inputContext, attribute,
2013                                    controlFlags);
2014                            didStart = true;
2015                        }
2016                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2017                        break;
2018                }
2019
2020                if (!didStart && attribute != null) {
2021                    res = startInputUncheckedLocked(cs, inputContext, attribute,
2022                            controlFlags);
2023                }
2024            }
2025        } finally {
2026            Binder.restoreCallingIdentity(ident);
2027        }
2028
2029        return res;
2030    }
2031
2032    @Override
2033    public void showInputMethodPickerFromClient(IInputMethodClient client) {
2034        if (!calledFromValidUser()) {
2035            return;
2036        }
2037        synchronized (mMethodMap) {
2038            if (mCurClient == null || client == null
2039                    || mCurClient.client.asBinder() != client.asBinder()) {
2040                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2041                        + Binder.getCallingUid() + ": " + client);
2042            }
2043
2044            // Always call subtype picker, because subtype picker is a superset of input method
2045            // picker.
2046            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
2047        }
2048    }
2049
2050    @Override
2051    public void setInputMethod(IBinder token, String id) {
2052        if (!calledFromValidUser()) {
2053            return;
2054        }
2055        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2056    }
2057
2058    @Override
2059    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2060        if (!calledFromValidUser()) {
2061            return;
2062        }
2063        synchronized (mMethodMap) {
2064            if (subtype != null) {
2065                setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode(
2066                        mMethodMap.get(id), subtype.hashCode()));
2067            } else {
2068                setInputMethod(token, id);
2069            }
2070        }
2071    }
2072
2073    @Override
2074    public void showInputMethodAndSubtypeEnablerFromClient(
2075            IInputMethodClient client, String inputMethodId) {
2076        if (!calledFromValidUser()) {
2077            return;
2078        }
2079        synchronized (mMethodMap) {
2080            if (mCurClient == null || client == null
2081                || mCurClient.client.asBinder() != client.asBinder()) {
2082                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
2083            }
2084            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2085                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2086        }
2087    }
2088
2089    @Override
2090    public boolean switchToLastInputMethod(IBinder token) {
2091        if (!calledFromValidUser()) {
2092            return false;
2093        }
2094        synchronized (mMethodMap) {
2095            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2096            final InputMethodInfo lastImi;
2097            if (lastIme != null) {
2098                lastImi = mMethodMap.get(lastIme.first);
2099            } else {
2100                lastImi = null;
2101            }
2102            String targetLastImiId = null;
2103            int subtypeId = NOT_A_SUBTYPE_ID;
2104            if (lastIme != null && lastImi != null) {
2105                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2106                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2107                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2108                        : mCurrentSubtype.hashCode();
2109                // If the last IME is the same as the current IME and the last subtype is not
2110                // defined, there is no need to switch to the last IME.
2111                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2112                    targetLastImiId = lastIme.first;
2113                    subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2114                }
2115            }
2116
2117            if (TextUtils.isEmpty(targetLastImiId)
2118                    && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
2119                // This is a safety net. If the currentSubtype can't be added to the history
2120                // and the framework couldn't find the last ime, we will make the last ime be
2121                // the most applicable enabled keyboard subtype of the system imes.
2122                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2123                if (enabled != null) {
2124                    final int N = enabled.size();
2125                    final String locale = mCurrentSubtype == null
2126                            ? mRes.getConfiguration().locale.toString()
2127                            : mCurrentSubtype.getLocale();
2128                    for (int i = 0; i < N; ++i) {
2129                        final InputMethodInfo imi = enabled.get(i);
2130                        if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
2131                            InputMethodSubtype keyboardSubtype =
2132                                    InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
2133                                            InputMethodUtils.getSubtypes(imi),
2134                                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
2135                            if (keyboardSubtype != null) {
2136                                targetLastImiId = imi.getId();
2137                                subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2138                                        imi, keyboardSubtype.hashCode());
2139                                if(keyboardSubtype.getLocale().equals(locale)) {
2140                                    break;
2141                                }
2142                            }
2143                        }
2144                    }
2145                }
2146            }
2147
2148            if (!TextUtils.isEmpty(targetLastImiId)) {
2149                if (DEBUG) {
2150                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2151                            + ", from: " + mCurMethodId + ", " + subtypeId);
2152                }
2153                setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
2154                return true;
2155            } else {
2156                return false;
2157            }
2158        }
2159    }
2160
2161    @Override
2162    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2163        if (!calledFromValidUser()) {
2164            return false;
2165        }
2166        synchronized (mMethodMap) {
2167            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethod(
2168                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
2169            if (nextSubtype == null) {
2170                return false;
2171            }
2172            setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
2173            return true;
2174        }
2175    }
2176
2177    @Override
2178    public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2179        if (!calledFromValidUser()) {
2180            return false;
2181        }
2182        synchronized (mMethodMap) {
2183            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethod(
2184                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
2185            if (nextSubtype == null) {
2186                return false;
2187            }
2188            return true;
2189        }
2190    }
2191
2192    @Override
2193    public InputMethodSubtype getLastInputMethodSubtype() {
2194        if (!calledFromValidUser()) {
2195            return null;
2196        }
2197        synchronized (mMethodMap) {
2198            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2199            // TODO: Handle the case of the last IME with no subtypes
2200            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2201                    || TextUtils.isEmpty(lastIme.second)) return null;
2202            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2203            if (lastImi == null) return null;
2204            try {
2205                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2206                final int lastSubtypeId =
2207                        InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2208                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2209                    return null;
2210                }
2211                return lastImi.getSubtypeAt(lastSubtypeId);
2212            } catch (NumberFormatException e) {
2213                return null;
2214            }
2215        }
2216    }
2217
2218    @Override
2219    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2220        if (!calledFromValidUser()) {
2221            return;
2222        }
2223        // By this IPC call, only a process which shares the same uid with the IME can add
2224        // additional input method subtypes to the IME.
2225        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
2226        synchronized (mMethodMap) {
2227            final InputMethodInfo imi = mMethodMap.get(imiId);
2228            if (imi == null) return;
2229            final String[] packageInfos;
2230            try {
2231                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2232            } catch (RemoteException e) {
2233                Slog.e(TAG, "Failed to get package infos");
2234                return;
2235            }
2236            if (packageInfos != null) {
2237                final int packageNum = packageInfos.length;
2238                for (int i = 0; i < packageNum; ++i) {
2239                    if (packageInfos[i].equals(imi.getPackageName())) {
2240                        mFileManager.addInputMethodSubtypes(imi, subtypes);
2241                        final long ident = Binder.clearCallingIdentity();
2242                        try {
2243                            buildInputMethodListLocked(mMethodList, mMethodMap,
2244                                    false /* resetDefaultEnabledIme */);
2245                        } finally {
2246                            Binder.restoreCallingIdentity(ident);
2247                        }
2248                        return;
2249                    }
2250                }
2251            }
2252        }
2253        return;
2254    }
2255
2256    @Override
2257    public int getInputMethodWindowVisibleHeight() {
2258        return mWindowManagerService.getInputMethodWindowVisibleHeight();
2259    }
2260
2261    @Override
2262    public void notifyTextCommitted() {
2263        if (DEBUG) {
2264            Slog.d(TAG, "Got the notification of commitText");
2265        }
2266        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2267        if (imi != null) {
2268            mSwitchingController.onCommitText(imi, mCurrentSubtype);
2269        }
2270    }
2271
2272    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2273        synchronized (mMethodMap) {
2274            if (token == null) {
2275                if (mContext.checkCallingOrSelfPermission(
2276                        android.Manifest.permission.WRITE_SECURE_SETTINGS)
2277                        != PackageManager.PERMISSION_GRANTED) {
2278                    throw new SecurityException(
2279                            "Using null token requires permission "
2280                            + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2281                }
2282            } else if (mCurToken != token) {
2283                Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2284                        + " token: " + token);
2285                return;
2286            }
2287
2288            final long ident = Binder.clearCallingIdentity();
2289            try {
2290                setInputMethodLocked(id, subtypeId);
2291            } finally {
2292                Binder.restoreCallingIdentity(ident);
2293            }
2294        }
2295    }
2296
2297    @Override
2298    public void hideMySoftInput(IBinder token, int flags) {
2299        if (!calledFromValidUser()) {
2300            return;
2301        }
2302        synchronized (mMethodMap) {
2303            if (token == null || mCurToken != token) {
2304                if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
2305                        + Binder.getCallingUid() + " token: " + token);
2306                return;
2307            }
2308            long ident = Binder.clearCallingIdentity();
2309            try {
2310                hideCurrentInputLocked(flags, null);
2311            } finally {
2312                Binder.restoreCallingIdentity(ident);
2313            }
2314        }
2315    }
2316
2317    @Override
2318    public void showMySoftInput(IBinder token, int flags) {
2319        if (!calledFromValidUser()) {
2320            return;
2321        }
2322        synchronized (mMethodMap) {
2323            if (token == null || mCurToken != token) {
2324                Slog.w(TAG, "Ignoring showMySoftInput of uid "
2325                        + Binder.getCallingUid() + " token: " + token);
2326                return;
2327            }
2328            long ident = Binder.clearCallingIdentity();
2329            try {
2330                showCurrentInputLocked(flags, null);
2331            } finally {
2332                Binder.restoreCallingIdentity(ident);
2333            }
2334        }
2335    }
2336
2337    void setEnabledSessionInMainThread(SessionState session) {
2338        if (mEnabledSession != session) {
2339            if (mEnabledSession != null) {
2340                try {
2341                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2342                    mEnabledSession.method.setSessionEnabled(
2343                            mEnabledSession.session, false);
2344                } catch (RemoteException e) {
2345                }
2346            }
2347            mEnabledSession = session;
2348            try {
2349                if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2350                session.method.setSessionEnabled(
2351                        session.session, true);
2352            } catch (RemoteException e) {
2353            }
2354        }
2355    }
2356
2357    @Override
2358    public boolean handleMessage(Message msg) {
2359        SomeArgs args;
2360        switch (msg.what) {
2361            case MSG_SHOW_IM_PICKER:
2362                showInputMethodMenu();
2363                return true;
2364
2365            case MSG_SHOW_IM_SUBTYPE_PICKER:
2366                showInputMethodSubtypeMenu();
2367                return true;
2368
2369            case MSG_SHOW_IM_SUBTYPE_ENABLER:
2370                args = (SomeArgs)msg.obj;
2371                showInputMethodAndSubtypeEnabler((String)args.arg1);
2372                args.recycle();
2373                return true;
2374
2375            case MSG_SHOW_IM_CONFIG:
2376                showConfigureInputMethods();
2377                return true;
2378
2379            // ---------------------------------------------------------
2380
2381            case MSG_UNBIND_INPUT:
2382                try {
2383                    ((IInputMethod)msg.obj).unbindInput();
2384                } catch (RemoteException e) {
2385                    // There is nothing interesting about the method dying.
2386                }
2387                return true;
2388            case MSG_BIND_INPUT:
2389                args = (SomeArgs)msg.obj;
2390                try {
2391                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2392                } catch (RemoteException e) {
2393                }
2394                args.recycle();
2395                return true;
2396            case MSG_SHOW_SOFT_INPUT:
2397                args = (SomeArgs)msg.obj;
2398                try {
2399                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
2400                            + msg.arg1 + ", " + args.arg2 + ")");
2401                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
2402                } catch (RemoteException e) {
2403                }
2404                args.recycle();
2405                return true;
2406            case MSG_HIDE_SOFT_INPUT:
2407                args = (SomeArgs)msg.obj;
2408                try {
2409                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
2410                            + args.arg2 + ")");
2411                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
2412                } catch (RemoteException e) {
2413                }
2414                args.recycle();
2415                return true;
2416            case MSG_ATTACH_TOKEN:
2417                args = (SomeArgs)msg.obj;
2418                try {
2419                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2420                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2421                } catch (RemoteException e) {
2422                }
2423                args.recycle();
2424                return true;
2425            case MSG_CREATE_SESSION: {
2426                args = (SomeArgs)msg.obj;
2427                IInputMethod method = (IInputMethod)args.arg1;
2428                InputChannel channel = (InputChannel)args.arg2;
2429                try {
2430                    method.createSession(channel, (IInputSessionCallback)args.arg3);
2431                } catch (RemoteException e) {
2432                } finally {
2433                    // Dispose the channel if the input method is not local to this process
2434                    // because the remote proxy will get its own copy when unparceled.
2435                    if (channel != null && Binder.isProxy(method)) {
2436                        channel.dispose();
2437                    }
2438                }
2439                args.recycle();
2440                return true;
2441            }
2442            // ---------------------------------------------------------
2443
2444            case MSG_START_INPUT:
2445                args = (SomeArgs)msg.obj;
2446                try {
2447                    SessionState session = (SessionState)args.arg1;
2448                    setEnabledSessionInMainThread(session);
2449                    session.method.startInput((IInputContext)args.arg2,
2450                            (EditorInfo)args.arg3);
2451                } catch (RemoteException e) {
2452                }
2453                args.recycle();
2454                return true;
2455            case MSG_RESTART_INPUT:
2456                args = (SomeArgs)msg.obj;
2457                try {
2458                    SessionState session = (SessionState)args.arg1;
2459                    setEnabledSessionInMainThread(session);
2460                    session.method.restartInput((IInputContext)args.arg2,
2461                            (EditorInfo)args.arg3);
2462                } catch (RemoteException e) {
2463                }
2464                args.recycle();
2465                return true;
2466
2467            // ---------------------------------------------------------
2468
2469            case MSG_UNBIND_METHOD:
2470                try {
2471                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
2472                } catch (RemoteException e) {
2473                    // There is nothing interesting about the last client dying.
2474                }
2475                return true;
2476            case MSG_BIND_METHOD: {
2477                args = (SomeArgs)msg.obj;
2478                IInputMethodClient client = (IInputMethodClient)args.arg1;
2479                InputBindResult res = (InputBindResult)args.arg2;
2480                try {
2481                    client.onBindMethod(res);
2482                } catch (RemoteException e) {
2483                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
2484                } finally {
2485                    // Dispose the channel if the input method is not local to this process
2486                    // because the remote proxy will get its own copy when unparceled.
2487                    if (res.channel != null && Binder.isProxy(client)) {
2488                        res.channel.dispose();
2489                    }
2490                }
2491                args.recycle();
2492                return true;
2493            }
2494            case MSG_SET_ACTIVE:
2495                try {
2496                    ((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
2497                } catch (RemoteException e) {
2498                    Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
2499                            + ((ClientState)msg.obj).pid + " uid "
2500                            + ((ClientState)msg.obj).uid);
2501                }
2502                return true;
2503
2504            // --------------------------------------------------------------
2505            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
2506                mHardKeyboardListener.handleHardKeyboardStatusChange(
2507                        msg.arg1 == 1, msg.arg2 == 1);
2508                return true;
2509        }
2510        return false;
2511    }
2512
2513    private boolean chooseNewDefaultIMELocked() {
2514        final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
2515                mSettings.getEnabledInputMethodListLocked());
2516        if (imi != null) {
2517            if (DEBUG) {
2518                Slog.d(TAG, "New default IME was selected: " + imi.getId());
2519            }
2520            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
2521            return true;
2522        }
2523
2524        return false;
2525    }
2526
2527    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
2528            HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
2529        if (DEBUG) {
2530            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
2531                    + " \n ------ \n" + InputMethodUtils.getStackTrace());
2532        }
2533        list.clear();
2534        map.clear();
2535
2536        // Use for queryIntentServicesAsUser
2537        final PackageManager pm = mContext.getPackageManager();
2538        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
2539        if (disabledSysImes == null) disabledSysImes = "";
2540
2541        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
2542                new Intent(InputMethod.SERVICE_INTERFACE),
2543                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
2544                mSettings.getCurrentUserId());
2545
2546        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
2547                mFileManager.getAllAdditionalInputMethodSubtypes();
2548        for (int i = 0; i < services.size(); ++i) {
2549            ResolveInfo ri = services.get(i);
2550            ServiceInfo si = ri.serviceInfo;
2551            ComponentName compName = new ComponentName(si.packageName, si.name);
2552            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
2553                    si.permission)) {
2554                Slog.w(TAG, "Skipping input method " + compName
2555                        + ": it does not require the permission "
2556                        + android.Manifest.permission.BIND_INPUT_METHOD);
2557                continue;
2558            }
2559
2560            if (DEBUG) Slog.d(TAG, "Checking " + compName);
2561
2562            try {
2563                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
2564                list.add(p);
2565                final String id = p.getId();
2566                map.put(id, p);
2567
2568                if (DEBUG) {
2569                    Slog.d(TAG, "Found an input method " + p);
2570                }
2571
2572            } catch (XmlPullParserException e) {
2573                Slog.w(TAG, "Unable to load input method " + compName, e);
2574            } catch (IOException e) {
2575                Slog.w(TAG, "Unable to load input method " + compName, e);
2576            }
2577        }
2578
2579        if (resetDefaultEnabledIme) {
2580            final ArrayList<InputMethodInfo> defaultEnabledIme =
2581                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
2582            for (int i = 0; i < defaultEnabledIme.size(); ++i) {
2583                final InputMethodInfo imi =  defaultEnabledIme.get(i);
2584                if (DEBUG) {
2585                    Slog.d(TAG, "--- enable ime = " + imi);
2586                }
2587                setInputMethodEnabledLocked(imi.getId(), true);
2588            }
2589        }
2590
2591        final String defaultImiId = mSettings.getSelectedInputMethod();
2592        if (!TextUtils.isEmpty(defaultImiId)) {
2593            if (!map.containsKey(defaultImiId)) {
2594                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
2595                if (chooseNewDefaultIMELocked()) {
2596                    updateFromSettingsLocked(true);
2597                }
2598            } else {
2599                // Double check that the default IME is certainly enabled.
2600                setInputMethodEnabledLocked(defaultImiId, true);
2601            }
2602        }
2603    }
2604
2605    // ----------------------------------------------------------------------
2606
2607    private void showInputMethodMenu() {
2608        showInputMethodMenuInternal(false);
2609    }
2610
2611    private void showInputMethodSubtypeMenu() {
2612        showInputMethodMenuInternal(true);
2613    }
2614
2615    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
2616        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2617        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2618                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2619                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2620        if (!TextUtils.isEmpty(inputMethodId)) {
2621            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
2622        }
2623        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
2624    }
2625
2626    private void showConfigureInputMethods() {
2627        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2628        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2629                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2630                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2631        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
2632    }
2633
2634    private boolean isScreenLocked() {
2635        return mKeyguardManager != null
2636                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
2637    }
2638    private void showInputMethodMenuInternal(boolean showSubtypes) {
2639        if (DEBUG) Slog.v(TAG, "Show switching menu");
2640
2641        final Context context = mContext;
2642        final boolean isScreenLocked = isScreenLocked();
2643
2644        final String lastInputMethodId = mSettings.getSelectedInputMethod();
2645        int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
2646        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2647
2648        synchronized (mMethodMap) {
2649            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2650                    mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
2651                            mContext);
2652            if (immis == null || immis.size() == 0) {
2653                return;
2654            }
2655
2656            hideInputMethodMenuLocked();
2657
2658            final List<ImeSubtypeListItem> imList =
2659                    mSwitchingController.getSortedInputMethodAndSubtypeList(
2660                            showSubtypes, mInputShown, isScreenLocked);
2661
2662            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
2663                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
2664                if (currentSubtype != null) {
2665                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2666                    lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2667                            currentImi, currentSubtype.hashCode());
2668                }
2669            }
2670
2671            final int N = imList.size();
2672            mIms = new InputMethodInfo[N];
2673            mSubtypeIds = new int[N];
2674            int checkedItem = 0;
2675            for (int i = 0; i < N; ++i) {
2676                final ImeSubtypeListItem item = imList.get(i);
2677                mIms[i] = item.mImi;
2678                mSubtypeIds[i] = item.mSubtypeId;
2679                if (mIms[i].getId().equals(lastInputMethodId)) {
2680                    int subtypeId = mSubtypeIds[i];
2681                    if ((subtypeId == NOT_A_SUBTYPE_ID)
2682                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2683                            || (subtypeId == lastInputMethodSubtypeId)) {
2684                        checkedItem = i;
2685                    }
2686                }
2687            }
2688            final TypedArray a = context.obtainStyledAttributes(null,
2689                    com.android.internal.R.styleable.DialogPreference,
2690                    com.android.internal.R.attr.alertDialogStyle, 0);
2691            mDialogBuilder = new AlertDialog.Builder(context)
2692                    .setOnCancelListener(new OnCancelListener() {
2693                        @Override
2694                        public void onCancel(DialogInterface dialog) {
2695                            hideInputMethodMenu();
2696                        }
2697                    })
2698                    .setIcon(a.getDrawable(
2699                            com.android.internal.R.styleable.DialogPreference_dialogTitle));
2700            a.recycle();
2701            final LayoutInflater inflater =
2702                    (LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2703            final View tv = inflater.inflate(
2704                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
2705            mDialogBuilder.setCustomTitle(tv);
2706
2707            // Setup layout for a toggle switch of the hardware keyboard
2708            mSwitchingDialogTitleView = tv;
2709            mSwitchingDialogTitleView.findViewById(
2710                    com.android.internal.R.id.hard_keyboard_section).setVisibility(
2711                            mWindowManagerService.isHardKeyboardAvailable() ?
2712                                    View.VISIBLE : View.GONE);
2713            final Switch hardKeySwitch =  ((Switch)mSwitchingDialogTitleView.findViewById(
2714                    com.android.internal.R.id.hard_keyboard_switch));
2715            hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled());
2716            hardKeySwitch.setOnCheckedChangeListener(
2717                    new OnCheckedChangeListener() {
2718                        @Override
2719                        public void onCheckedChanged(
2720                                CompoundButton buttonView, boolean isChecked) {
2721                            mWindowManagerService.setHardKeyboardEnabled(isChecked);
2722                            // Ensure that the input method dialog is dismissed when changing
2723                            // the hardware keyboard state.
2724                            hideInputMethodMenu();
2725                        }
2726                    });
2727
2728            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
2729                    com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
2730                    checkedItem);
2731
2732            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
2733                    new AlertDialog.OnClickListener() {
2734                        @Override
2735                        public void onClick(DialogInterface dialog, int which) {
2736                            synchronized (mMethodMap) {
2737                                if (mIms == null || mIms.length <= which
2738                                        || mSubtypeIds == null || mSubtypeIds.length <= which) {
2739                                    return;
2740                                }
2741                                InputMethodInfo im = mIms[which];
2742                                int subtypeId = mSubtypeIds[which];
2743                                adapter.mCheckedItem = which;
2744                                adapter.notifyDataSetChanged();
2745                                hideInputMethodMenu();
2746                                if (im != null) {
2747                                    if ((subtypeId < 0)
2748                                            || (subtypeId >= im.getSubtypeCount())) {
2749                                        subtypeId = NOT_A_SUBTYPE_ID;
2750                                    }
2751                                    setInputMethodLocked(im.getId(), subtypeId);
2752                                }
2753                            }
2754                        }
2755                    });
2756
2757            if (showSubtypes && !isScreenLocked) {
2758                mDialogBuilder.setPositiveButton(
2759                        com.android.internal.R.string.configure_input_methods,
2760                        new DialogInterface.OnClickListener() {
2761                            @Override
2762                            public void onClick(DialogInterface dialog, int whichButton) {
2763                                showConfigureInputMethods();
2764                            }
2765                        });
2766            }
2767            mSwitchingDialog = mDialogBuilder.create();
2768            mSwitchingDialog.setCanceledOnTouchOutside(true);
2769            mSwitchingDialog.getWindow().setType(
2770                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2771            mSwitchingDialog.getWindow().getAttributes().privateFlags |=
2772                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
2773            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2774            mSwitchingDialog.show();
2775        }
2776    }
2777
2778    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
2779        private final LayoutInflater mInflater;
2780        private final int mTextViewResourceId;
2781        private final List<ImeSubtypeListItem> mItemsList;
2782        public int mCheckedItem;
2783        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
2784                List<ImeSubtypeListItem> itemsList, int checkedItem) {
2785            super(context, textViewResourceId, itemsList);
2786            mTextViewResourceId = textViewResourceId;
2787            mItemsList = itemsList;
2788            mCheckedItem = checkedItem;
2789            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2790        }
2791
2792        @Override
2793        public View getView(int position, View convertView, ViewGroup parent) {
2794            final View view = convertView != null ? convertView
2795                    : mInflater.inflate(mTextViewResourceId, null);
2796            if (position < 0 || position >= mItemsList.size()) return view;
2797            final ImeSubtypeListItem item = mItemsList.get(position);
2798            final CharSequence imeName = item.mImeName;
2799            final CharSequence subtypeName = item.mSubtypeName;
2800            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
2801            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
2802            if (TextUtils.isEmpty(subtypeName)) {
2803                firstTextView.setText(imeName);
2804                secondTextView.setVisibility(View.GONE);
2805            } else {
2806                firstTextView.setText(subtypeName);
2807                secondTextView.setText(imeName);
2808                secondTextView.setVisibility(View.VISIBLE);
2809            }
2810            final RadioButton radioButton =
2811                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
2812            radioButton.setChecked(position == mCheckedItem);
2813            return view;
2814        }
2815    }
2816
2817    void hideInputMethodMenu() {
2818        synchronized (mMethodMap) {
2819            hideInputMethodMenuLocked();
2820        }
2821    }
2822
2823    void hideInputMethodMenuLocked() {
2824        if (DEBUG) Slog.v(TAG, "Hide switching menu");
2825
2826        if (mSwitchingDialog != null) {
2827            mSwitchingDialog.dismiss();
2828            mSwitchingDialog = null;
2829        }
2830
2831        mDialogBuilder = null;
2832        mIms = null;
2833    }
2834
2835    // ----------------------------------------------------------------------
2836
2837    @Override
2838    public boolean setInputMethodEnabled(String id, boolean enabled) {
2839        // TODO: Make this work even for non-current users?
2840        if (!calledFromValidUser()) {
2841            return false;
2842        }
2843        synchronized (mMethodMap) {
2844            if (mContext.checkCallingOrSelfPermission(
2845                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2846                    != PackageManager.PERMISSION_GRANTED) {
2847                throw new SecurityException(
2848                        "Requires permission "
2849                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2850            }
2851
2852            long ident = Binder.clearCallingIdentity();
2853            try {
2854                return setInputMethodEnabledLocked(id, enabled);
2855            } finally {
2856                Binder.restoreCallingIdentity(ident);
2857            }
2858        }
2859    }
2860
2861    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2862        // Make sure this is a valid input method.
2863        InputMethodInfo imm = mMethodMap.get(id);
2864        if (imm == null) {
2865            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2866        }
2867
2868        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2869                .getEnabledInputMethodsAndSubtypeListLocked();
2870
2871        if (enabled) {
2872            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2873                if (pair.first.equals(id)) {
2874                    // We are enabling this input method, but it is already enabled.
2875                    // Nothing to do. The previous state was enabled.
2876                    return true;
2877                }
2878            }
2879            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
2880            // Previous state was disabled.
2881            return false;
2882        } else {
2883            StringBuilder builder = new StringBuilder();
2884            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
2885                    builder, enabledInputMethodsList, id)) {
2886                // Disabled input method is currently selected, switch to another one.
2887                final String selId = mSettings.getSelectedInputMethod();
2888                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
2889                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
2890                    resetSelectedInputMethodAndSubtypeLocked("");
2891                }
2892                // Previous state was enabled.
2893                return true;
2894            } else {
2895                // We are disabling the input method but it is already disabled.
2896                // Nothing to do.  The previous state was disabled.
2897                return false;
2898            }
2899        }
2900    }
2901
2902    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
2903            boolean setSubtypeOnly) {
2904        // Update the history of InputMethod and Subtype
2905        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
2906
2907        // Set Subtype here
2908        if (imi == null || subtypeId < 0) {
2909            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2910            mCurrentSubtype = null;
2911        } else {
2912            if (subtypeId < imi.getSubtypeCount()) {
2913                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
2914                mSettings.putSelectedSubtype(subtype.hashCode());
2915                mCurrentSubtype = subtype;
2916            } else {
2917                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
2918                // If the subtype is not specified, choose the most applicable one
2919                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
2920            }
2921        }
2922
2923        // Workaround.
2924        // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
2925        // IMEs are not recognized and considered uninstalled.
2926        // Actually, we can't move everything after SystemReady because
2927        // IMMS needs to run in the encryption lock screen. So, we just skip changing
2928        // the default IME here and try cheking the default IME again in systemReady().
2929        // TODO: Do nothing before system ready and implement a separated logic for
2930        // the encryption lock screen.
2931        // TODO: ASEC should be ready before IMMS is instantiated.
2932        if (mSystemReady && !setSubtypeOnly) {
2933            // Set InputMethod here
2934            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
2935        }
2936    }
2937
2938    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
2939        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
2940        int lastSubtypeId = NOT_A_SUBTYPE_ID;
2941        // newDefaultIme is empty when there is no candidate for the selected IME.
2942        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
2943            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
2944            if (subtypeHashCode != null) {
2945                try {
2946                    lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2947                            imi, Integer.valueOf(subtypeHashCode));
2948                } catch (NumberFormatException e) {
2949                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
2950                }
2951            }
2952        }
2953        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
2954    }
2955
2956    // If there are no selected shortcuts, tries finding the most applicable ones.
2957    private Pair<InputMethodInfo, InputMethodSubtype>
2958            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
2959        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
2960        InputMethodInfo mostApplicableIMI = null;
2961        InputMethodSubtype mostApplicableSubtype = null;
2962        boolean foundInSystemIME = false;
2963
2964        // Search applicable subtype for each InputMethodInfo
2965        for (InputMethodInfo imi: imis) {
2966            final String imiId = imi.getId();
2967            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
2968                continue;
2969            }
2970            InputMethodSubtype subtype = null;
2971            final List<InputMethodSubtype> enabledSubtypes =
2972                    mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
2973            // 1. Search by the current subtype's locale from enabledSubtypes.
2974            if (mCurrentSubtype != null) {
2975                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
2976                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
2977            }
2978            // 2. Search by the system locale from enabledSubtypes.
2979            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
2980            if (subtype == null) {
2981                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
2982                        mRes, enabledSubtypes, mode, null, true);
2983            }
2984            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
2985                    InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
2986            final ArrayList<InputMethodSubtype> subtypesForSearch =
2987                    overridingImplicitlyEnabledSubtypes.isEmpty()
2988                            ? InputMethodUtils.getSubtypes(imi)
2989                            : overridingImplicitlyEnabledSubtypes;
2990            // 4. Search by the current subtype's locale from all subtypes.
2991            if (subtype == null && mCurrentSubtype != null) {
2992                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
2993                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
2994            }
2995            // 5. Search by the system locale from all subtypes.
2996            // 6. Search the first enabled subtype matched with mode from all subtypes.
2997            if (subtype == null) {
2998                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
2999                        mRes, subtypesForSearch, mode, null, true);
3000            }
3001            if (subtype != null) {
3002                if (imiId.equals(mCurMethodId)) {
3003                    // The current input method is the most applicable IME.
3004                    mostApplicableIMI = imi;
3005                    mostApplicableSubtype = subtype;
3006                    break;
3007                } else if (!foundInSystemIME) {
3008                    // The system input method is 2nd applicable IME.
3009                    mostApplicableIMI = imi;
3010                    mostApplicableSubtype = subtype;
3011                    if ((imi.getServiceInfo().applicationInfo.flags
3012                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
3013                        foundInSystemIME = true;
3014                    }
3015                }
3016            }
3017        }
3018        if (DEBUG) {
3019            if (mostApplicableIMI != null) {
3020                Slog.w(TAG, "Most applicable shortcut input method was:"
3021                        + mostApplicableIMI.getId());
3022                if (mostApplicableSubtype != null) {
3023                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3024                            + "," + mostApplicableSubtype.getMode() + ","
3025                            + mostApplicableSubtype.getLocale());
3026                }
3027            }
3028        }
3029        if (mostApplicableIMI != null) {
3030            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
3031                    mostApplicableSubtype);
3032        } else {
3033            return null;
3034        }
3035    }
3036
3037    /**
3038     * @return Return the current subtype of this input method.
3039     */
3040    @Override
3041    public InputMethodSubtype getCurrentInputMethodSubtype() {
3042        // TODO: Make this work even for non-current users?
3043        if (!calledFromValidUser()) {
3044            return null;
3045        }
3046        synchronized (mMethodMap) {
3047            return getCurrentInputMethodSubtypeLocked();
3048        }
3049    }
3050
3051    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3052        if (mCurMethodId == null) {
3053            return null;
3054        }
3055        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3056        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3057        if (imi == null || imi.getSubtypeCount() == 0) {
3058            return null;
3059        }
3060        if (!subtypeIsSelected || mCurrentSubtype == null
3061                || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3062            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
3063            if (subtypeId == NOT_A_SUBTYPE_ID) {
3064                // If there are no selected subtypes, the framework will try to find
3065                // the most applicable subtype from explicitly or implicitly enabled
3066                // subtypes.
3067                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3068                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3069                // If there is only one explicitly or implicitly enabled subtype,
3070                // just returns it.
3071                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3072                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3073                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3074                    mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3075                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
3076                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
3077                    if (mCurrentSubtype == null) {
3078                        mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3079                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3080                                true);
3081                    }
3082                }
3083            } else {
3084                mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3085            }
3086        }
3087        return mCurrentSubtype;
3088    }
3089
3090    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
3091            InputMethodSubtype subtype) {
3092        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
3093            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
3094        } else {
3095            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3096            subtypes.add(subtype);
3097            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
3098        }
3099    }
3100
3101    // TODO: We should change the return type from List to List<Parcelable>
3102    @SuppressWarnings("rawtypes")
3103    @Override
3104    public List getShortcutInputMethodsAndSubtypes() {
3105        synchronized (mMethodMap) {
3106            ArrayList<Object> ret = new ArrayList<Object>();
3107            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3108                // If there are no selected shortcut subtypes, the framework will try to find
3109                // the most applicable subtype from all subtypes whose mode is
3110                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3111                Pair<InputMethodInfo, InputMethodSubtype> info =
3112                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3113                            InputMethodUtils.SUBTYPE_MODE_VOICE);
3114                if (info != null) {
3115                    ret.add(info.first);
3116                    ret.add(info.second);
3117                }
3118                return ret;
3119            }
3120            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3121                ret.add(imi);
3122                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3123                    ret.add(subtype);
3124                }
3125            }
3126            return ret;
3127        }
3128    }
3129
3130    @Override
3131    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3132        // TODO: Make this work even for non-current users?
3133        if (!calledFromValidUser()) {
3134            return false;
3135        }
3136        synchronized (mMethodMap) {
3137            if (subtype != null && mCurMethodId != null) {
3138                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3139                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
3140                if (subtypeId != NOT_A_SUBTYPE_ID) {
3141                    setInputMethodLocked(mCurMethodId, subtypeId);
3142                    return true;
3143                }
3144            }
3145            return false;
3146        }
3147    }
3148
3149    // TODO: Cache the state for each user and reset when the cached user is removed.
3150    private static class InputMethodFileManager {
3151        private static final String SYSTEM_PATH = "system";
3152        private static final String INPUT_METHOD_PATH = "inputmethod";
3153        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3154        private static final String NODE_SUBTYPES = "subtypes";
3155        private static final String NODE_SUBTYPE = "subtype";
3156        private static final String NODE_IMI = "imi";
3157        private static final String ATTR_ID = "id";
3158        private static final String ATTR_LABEL = "label";
3159        private static final String ATTR_ICON = "icon";
3160        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3161        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3162        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3163        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3164        private final AtomicFile mAdditionalInputMethodSubtypeFile;
3165        private final HashMap<String, InputMethodInfo> mMethodMap;
3166        private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3167                new HashMap<String, List<InputMethodSubtype>>();
3168        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3169            if (methodMap == null) {
3170                throw new NullPointerException("methodMap is null");
3171            }
3172            mMethodMap = methodMap;
3173            final File systemDir = userId == UserHandle.USER_OWNER
3174                    ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3175                    : Environment.getUserSystemDirectory(userId);
3176            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3177            if (!inputMethodDir.mkdirs()) {
3178                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3179            }
3180            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3181            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3182            if (!subtypeFile.exists()) {
3183                // If "subtypes.xml" doesn't exist, create a blank file.
3184                writeAdditionalInputMethodSubtypes(
3185                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3186            } else {
3187                readAdditionalInputMethodSubtypes(
3188                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3189            }
3190        }
3191
3192        private void deleteAllInputMethodSubtypes(String imiId) {
3193            synchronized (mMethodMap) {
3194                mAdditionalSubtypesMap.remove(imiId);
3195                writeAdditionalInputMethodSubtypes(
3196                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3197            }
3198        }
3199
3200        public void addInputMethodSubtypes(
3201                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3202            synchronized (mMethodMap) {
3203                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3204                final int N = additionalSubtypes.length;
3205                for (int i = 0; i < N; ++i) {
3206                    final InputMethodSubtype subtype = additionalSubtypes[i];
3207                    if (!subtypes.contains(subtype)) {
3208                        subtypes.add(subtype);
3209                    } else {
3210                        Slog.w(TAG, "Duplicated subtype definition found: "
3211                                + subtype.getLocale() + ", " + subtype.getMode());
3212                    }
3213                }
3214                mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3215                writeAdditionalInputMethodSubtypes(
3216                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3217            }
3218        }
3219
3220        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3221            synchronized (mMethodMap) {
3222                return mAdditionalSubtypesMap;
3223            }
3224        }
3225
3226        private static void writeAdditionalInputMethodSubtypes(
3227                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3228                HashMap<String, InputMethodInfo> methodMap) {
3229            // Safety net for the case that this function is called before methodMap is set.
3230            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3231            FileOutputStream fos = null;
3232            try {
3233                fos = subtypesFile.startWrite();
3234                final XmlSerializer out = new FastXmlSerializer();
3235                out.setOutput(fos, "utf-8");
3236                out.startDocument(null, true);
3237                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3238                out.startTag(null, NODE_SUBTYPES);
3239                for (String imiId : allSubtypes.keySet()) {
3240                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3241                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3242                        continue;
3243                    }
3244                    out.startTag(null, NODE_IMI);
3245                    out.attribute(null, ATTR_ID, imiId);
3246                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3247                    final int N = subtypesList.size();
3248                    for (int i = 0; i < N; ++i) {
3249                        final InputMethodSubtype subtype = subtypesList.get(i);
3250                        out.startTag(null, NODE_SUBTYPE);
3251                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3252                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3253                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3254                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3255                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3256                        out.attribute(null, ATTR_IS_AUXILIARY,
3257                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3258                        out.endTag(null, NODE_SUBTYPE);
3259                    }
3260                    out.endTag(null, NODE_IMI);
3261                }
3262                out.endTag(null, NODE_SUBTYPES);
3263                out.endDocument();
3264                subtypesFile.finishWrite(fos);
3265            } catch (java.io.IOException e) {
3266                Slog.w(TAG, "Error writing subtypes", e);
3267                if (fos != null) {
3268                    subtypesFile.failWrite(fos);
3269                }
3270            }
3271        }
3272
3273        private static void readAdditionalInputMethodSubtypes(
3274                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3275            if (allSubtypes == null || subtypesFile == null) return;
3276            allSubtypes.clear();
3277            FileInputStream fis = null;
3278            try {
3279                fis = subtypesFile.openRead();
3280                final XmlPullParser parser = Xml.newPullParser();
3281                parser.setInput(fis, null);
3282                int type = parser.getEventType();
3283                // Skip parsing until START_TAG
3284                while ((type = parser.next()) != XmlPullParser.START_TAG
3285                        && type != XmlPullParser.END_DOCUMENT) {}
3286                String firstNodeName = parser.getName();
3287                if (!NODE_SUBTYPES.equals(firstNodeName)) {
3288                    throw new XmlPullParserException("Xml doesn't start with subtypes");
3289                }
3290                final int depth =parser.getDepth();
3291                String currentImiId = null;
3292                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3293                while (((type = parser.next()) != XmlPullParser.END_TAG
3294                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3295                    if (type != XmlPullParser.START_TAG)
3296                        continue;
3297                    final String nodeName = parser.getName();
3298                    if (NODE_IMI.equals(nodeName)) {
3299                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
3300                        if (TextUtils.isEmpty(currentImiId)) {
3301                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3302                            continue;
3303                        }
3304                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3305                        allSubtypes.put(currentImiId, tempSubtypesArray);
3306                    } else if (NODE_SUBTYPE.equals(nodeName)) {
3307                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3308                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3309                            continue;
3310                        }
3311                        final int icon = Integer.valueOf(
3312                                parser.getAttributeValue(null, ATTR_ICON));
3313                        final int label = Integer.valueOf(
3314                                parser.getAttributeValue(null, ATTR_LABEL));
3315                        final String imeSubtypeLocale =
3316                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3317                        final String imeSubtypeMode =
3318                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3319                        final String imeSubtypeExtraValue =
3320                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3321                        final boolean isAuxiliary = "1".equals(String.valueOf(
3322                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3323                        final InputMethodSubtype subtype =
3324                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
3325                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
3326                        tempSubtypesArray.add(subtype);
3327                    }
3328                }
3329            } catch (XmlPullParserException e) {
3330                Slog.w(TAG, "Error reading subtypes: " + e);
3331                return;
3332            } catch (java.io.IOException e) {
3333                Slog.w(TAG, "Error reading subtypes: " + e);
3334                return;
3335            } catch (NumberFormatException e) {
3336                Slog.w(TAG, "Error reading subtypes: " + e);
3337                return;
3338            } finally {
3339                if (fis != null) {
3340                    try {
3341                        fis.close();
3342                    } catch (java.io.IOException e1) {
3343                        Slog.w(TAG, "Failed to close.");
3344                    }
3345                }
3346            }
3347        }
3348    }
3349
3350    @Override
3351    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3352        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3353                != PackageManager.PERMISSION_GRANTED) {
3354
3355            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3356                    + Binder.getCallingPid()
3357                    + ", uid=" + Binder.getCallingUid());
3358            return;
3359        }
3360
3361        IInputMethod method;
3362        ClientState client;
3363
3364        final Printer p = new PrintWriterPrinter(pw);
3365
3366        synchronized (mMethodMap) {
3367            p.println("Current Input Method Manager state:");
3368            int N = mMethodList.size();
3369            p.println("  Input Methods:");
3370            for (int i=0; i<N; i++) {
3371                InputMethodInfo info = mMethodList.get(i);
3372                p.println("  InputMethod #" + i + ":");
3373                info.dump(p, "    ");
3374            }
3375            p.println("  Clients:");
3376            for (ClientState ci : mClients.values()) {
3377                p.println("  Client " + ci + ":");
3378                p.println("    client=" + ci.client);
3379                p.println("    inputContext=" + ci.inputContext);
3380                p.println("    sessionRequested=" + ci.sessionRequested);
3381                p.println("    curSession=" + ci.curSession);
3382            }
3383            p.println("  mCurMethodId=" + mCurMethodId);
3384            client = mCurClient;
3385            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
3386            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
3387            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3388                    + " mBoundToMethod=" + mBoundToMethod);
3389            p.println("  mCurToken=" + mCurToken);
3390            p.println("  mCurIntent=" + mCurIntent);
3391            method = mCurMethod;
3392            p.println("  mCurMethod=" + mCurMethod);
3393            p.println("  mEnabledSession=" + mEnabledSession);
3394            p.println("  mShowRequested=" + mShowRequested
3395                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3396                    + " mShowForced=" + mShowForced
3397                    + " mInputShown=" + mInputShown);
3398            p.println("  mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
3399        }
3400
3401        p.println(" ");
3402        if (client != null) {
3403            pw.flush();
3404            try {
3405                client.client.asBinder().dump(fd, args);
3406            } catch (RemoteException e) {
3407                p.println("Input method client dead: " + e);
3408            }
3409        } else {
3410            p.println("No input method client.");
3411        }
3412
3413        p.println(" ");
3414        if (method != null) {
3415            pw.flush();
3416            try {
3417                method.asBinder().dump(fd, args);
3418            } catch (RemoteException e) {
3419                p.println("Input method service dead: " + e);
3420            }
3421        } else {
3422            p.println("No input method service.");
3423        }
3424    }
3425}
3426