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