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