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