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