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