InputMethodManagerService.java revision 7b5a96ba8364d5c180780b2f878f5a2b949cfdac
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.setLatestEventInfo(
1592                            mContext, title, summary, mImeSwitchPendingIntent);
1593                    if ((mNotificationManager != null)
1594                            && !mWindowManagerService.hasNavigationBar()) {
1595                        if (DEBUG) {
1596                            Slog.d(TAG, "--- show notification: label =  " + summary);
1597                        }
1598                        mNotificationManager.notifyAsUser(null,
1599                                com.android.internal.R.string.select_input_method,
1600                                mImeSwitcherNotification, UserHandle.ALL);
1601                        mNotificationShown = true;
1602                    }
1603                } else {
1604                    if (mNotificationShown && mNotificationManager != null) {
1605                        if (DEBUG) {
1606                            Slog.d(TAG, "--- hide notification");
1607                        }
1608                        mNotificationManager.cancelAsUser(null,
1609                                com.android.internal.R.string.select_input_method, UserHandle.ALL);
1610                        mNotificationShown = false;
1611                    }
1612                }
1613            }
1614        } finally {
1615            Binder.restoreCallingIdentity(ident);
1616        }
1617    }
1618
1619    @Override
1620    public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
1621        if (!calledFromValidUser()) {
1622            return;
1623        }
1624        synchronized (mMethodMap) {
1625            final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
1626            for (int i = 0; i < spans.length; ++i) {
1627                SuggestionSpan ss = spans[i];
1628                if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
1629                    mSecureSuggestionSpans.put(ss, currentImi);
1630                }
1631            }
1632        }
1633    }
1634
1635    @Override
1636    public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
1637        if (!calledFromValidUser()) {
1638            return false;
1639        }
1640        synchronized (mMethodMap) {
1641            final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
1642            // TODO: Do not send the intent if the process of the targetImi is already dead.
1643            if (targetImi != null) {
1644                final String[] suggestions = span.getSuggestions();
1645                if (index < 0 || index >= suggestions.length) return false;
1646                final String className = span.getNotificationTargetClassName();
1647                final Intent intent = new Intent();
1648                // Ensures that only a class in the original IME package will receive the
1649                // notification.
1650                intent.setClassName(targetImi.getPackageName(), className);
1651                intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
1652                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
1653                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
1654                intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
1655                final long ident = Binder.clearCallingIdentity();
1656                try {
1657                    mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1658                } finally {
1659                    Binder.restoreCallingIdentity(ident);
1660                }
1661                return true;
1662            }
1663        }
1664        return false;
1665    }
1666
1667    void updateFromSettingsLocked(boolean enabledMayChange) {
1668        updateInputMethodsFromSettingsLocked(enabledMayChange);
1669        updateKeyboardFromSettingsLocked();
1670    }
1671
1672    void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
1673        if (enabledMayChange) {
1674            List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
1675            for (int i=0; i<enabled.size(); i++) {
1676                // We allow the user to select "disabled until used" apps, so if they
1677                // are enabling one of those here we now need to make it enabled.
1678                InputMethodInfo imm = enabled.get(i);
1679                try {
1680                    ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
1681                            PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
1682                            mSettings.getCurrentUserId());
1683                    if (ai != null && ai.enabledSetting
1684                            == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
1685                        if (DEBUG) {
1686                            Slog.d(TAG, "Update state(" + imm.getId()
1687                                    + "): DISABLED_UNTIL_USED -> DEFAULT");
1688                        }
1689                        mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
1690                                PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
1691                                PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
1692                                mContext.getBasePackageName());
1693                    }
1694                } catch (RemoteException e) {
1695                }
1696            }
1697        }
1698        // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
1699        // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
1700        // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
1701        // enabled.
1702        String id = mSettings.getSelectedInputMethod();
1703        // There is no input method selected, try to choose new applicable input method.
1704        if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
1705            id = mSettings.getSelectedInputMethod();
1706        }
1707        if (!TextUtils.isEmpty(id)) {
1708            try {
1709                setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
1710            } catch (IllegalArgumentException e) {
1711                Slog.w(TAG, "Unknown input method from prefs: " + id, e);
1712                mCurMethodId = null;
1713                unbindCurrentMethodLocked(true, false);
1714            }
1715            mShortcutInputMethodsAndSubtypes.clear();
1716        } else {
1717            // There is no longer an input method set, so stop any current one.
1718            mCurMethodId = null;
1719            unbindCurrentMethodLocked(true, false);
1720        }
1721        // Here is not the perfect place to reset the switching controller. Ideally
1722        // mSwitchingController and mSettings should be able to share the same state.
1723        // TODO: Make sure that mSwitchingController and mSettings are sharing the
1724        // the same enabled IMEs list.
1725        mSwitchingController.resetCircularListLocked(mContext);
1726
1727    }
1728
1729    public void updateKeyboardFromSettingsLocked() {
1730        mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
1731        if (mSwitchingDialog != null
1732                && mSwitchingDialogTitleView != null
1733                && mSwitchingDialog.isShowing()) {
1734            final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
1735                    com.android.internal.R.id.hard_keyboard_switch);
1736            hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
1737        }
1738    }
1739
1740    /* package */ void setInputMethodLocked(String id, int subtypeId) {
1741        InputMethodInfo info = mMethodMap.get(id);
1742        if (info == null) {
1743            throw new IllegalArgumentException("Unknown id: " + id);
1744        }
1745
1746        // See if we need to notify a subtype change within the same IME.
1747        if (id.equals(mCurMethodId)) {
1748            final int subtypeCount = info.getSubtypeCount();
1749            if (subtypeCount <= 0) {
1750                return;
1751            }
1752            final InputMethodSubtype oldSubtype = mCurrentSubtype;
1753            final InputMethodSubtype newSubtype;
1754            if (subtypeId >= 0 && subtypeId < subtypeCount) {
1755                newSubtype = info.getSubtypeAt(subtypeId);
1756            } else {
1757                // If subtype is null, try to find the most applicable one from
1758                // getCurrentInputMethodSubtype.
1759                newSubtype = getCurrentInputMethodSubtypeLocked();
1760            }
1761            if (newSubtype == null || oldSubtype == null) {
1762                Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
1763                        + ", new subtype = " + newSubtype);
1764                return;
1765            }
1766            if (newSubtype != oldSubtype) {
1767                setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
1768                if (mCurMethod != null) {
1769                    try {
1770                        refreshImeWindowVisibilityLocked();
1771                        mCurMethod.changeInputMethodSubtype(newSubtype);
1772                    } catch (RemoteException e) {
1773                        Slog.w(TAG, "Failed to call changeInputMethodSubtype");
1774                    }
1775                }
1776            }
1777            return;
1778        }
1779
1780        // Changing to a different IME.
1781        final long ident = Binder.clearCallingIdentity();
1782        try {
1783            // Set a subtype to this input method.
1784            // subtypeId the name of a subtype which will be set.
1785            setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
1786            // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
1787            // because mCurMethodId is stored as a history in
1788            // setSelectedInputMethodAndSubtypeLocked().
1789            mCurMethodId = id;
1790
1791            if (ActivityManagerNative.isSystemReady()) {
1792                Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
1793                intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
1794                intent.putExtra("input_method_id", id);
1795                mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
1796            }
1797            unbindCurrentClientLocked();
1798        } finally {
1799            Binder.restoreCallingIdentity(ident);
1800        }
1801    }
1802
1803    @Override
1804    public boolean showSoftInput(IInputMethodClient client, int flags,
1805            ResultReceiver resultReceiver) {
1806        if (!calledFromValidUser()) {
1807            return false;
1808        }
1809        int uid = Binder.getCallingUid();
1810        long ident = Binder.clearCallingIdentity();
1811        try {
1812            synchronized (mMethodMap) {
1813                if (mCurClient == null || client == null
1814                        || mCurClient.client.asBinder() != client.asBinder()) {
1815                    try {
1816                        // We need to check if this is the current client with
1817                        // focus in the window manager, to allow this call to
1818                        // be made before input is started in it.
1819                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1820                            Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
1821                            return false;
1822                        }
1823                    } catch (RemoteException e) {
1824                        return false;
1825                    }
1826                }
1827
1828                if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
1829                return showCurrentInputLocked(flags, resultReceiver);
1830            }
1831        } finally {
1832            Binder.restoreCallingIdentity(ident);
1833        }
1834    }
1835
1836    boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1837        mShowRequested = true;
1838        if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
1839            mShowExplicitlyRequested = true;
1840        }
1841        if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
1842            mShowExplicitlyRequested = true;
1843            mShowForced = true;
1844        }
1845
1846        if (!mSystemReady) {
1847            return false;
1848        }
1849
1850        boolean res = false;
1851        if (mCurMethod != null) {
1852            if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
1853            executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
1854                    MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
1855                    resultReceiver));
1856            mInputShown = true;
1857            if (mHaveConnection && !mVisibleBound) {
1858                bindCurrentInputMethodService(
1859                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
1860                                | Context.BIND_TREAT_LIKE_ACTIVITY);
1861                mVisibleBound = true;
1862            }
1863            res = true;
1864        } else if (mHaveConnection && SystemClock.uptimeMillis()
1865                >= (mLastBindTime+TIME_TO_RECONNECT)) {
1866            // The client has asked to have the input method shown, but
1867            // we have been sitting here too long with a connection to the
1868            // service and no interface received, so let's disconnect/connect
1869            // to try to prod things along.
1870            EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
1871                    SystemClock.uptimeMillis()-mLastBindTime,1);
1872            Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
1873            mContext.unbindService(this);
1874            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1875                    | Context.BIND_NOT_VISIBLE);
1876        } else {
1877            if (DEBUG) {
1878                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
1879                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
1880            }
1881        }
1882
1883        return res;
1884    }
1885
1886    @Override
1887    public boolean hideSoftInput(IInputMethodClient client, int flags,
1888            ResultReceiver resultReceiver) {
1889        if (!calledFromValidUser()) {
1890            return false;
1891        }
1892        int uid = Binder.getCallingUid();
1893        long ident = Binder.clearCallingIdentity();
1894        try {
1895            synchronized (mMethodMap) {
1896                if (mCurClient == null || client == null
1897                        || mCurClient.client.asBinder() != client.asBinder()) {
1898                    try {
1899                        // We need to check if this is the current client with
1900                        // focus in the window manager, to allow this call to
1901                        // be made before input is started in it.
1902                        if (!mIWindowManager.inputMethodClientHasFocus(client)) {
1903                            if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
1904                                    + uid + ": " + client);
1905                            setImeWindowVisibilityStatusHiddenLocked();
1906                            return false;
1907                        }
1908                    } catch (RemoteException e) {
1909                        setImeWindowVisibilityStatusHiddenLocked();
1910                        return false;
1911                    }
1912                }
1913
1914                if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
1915                return hideCurrentInputLocked(flags, resultReceiver);
1916            }
1917        } finally {
1918            Binder.restoreCallingIdentity(ident);
1919        }
1920    }
1921
1922    boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
1923        if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
1924                && (mShowExplicitlyRequested || mShowForced)) {
1925            if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
1926            return false;
1927        }
1928        if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
1929            if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
1930            return false;
1931        }
1932        boolean res;
1933        if (mInputShown && mCurMethod != null) {
1934            executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1935                    MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
1936            res = true;
1937        } else {
1938            res = false;
1939        }
1940        if (mHaveConnection && mVisibleBound) {
1941            mContext.unbindService(mVisibleConnection);
1942            mVisibleBound = false;
1943        }
1944        mInputShown = false;
1945        mShowRequested = false;
1946        mShowExplicitlyRequested = false;
1947        mShowForced = false;
1948        return res;
1949    }
1950
1951    @Override
1952    public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
1953            int controlFlags, int softInputMode, int windowFlags,
1954            EditorInfo attribute, IInputContext inputContext) {
1955        // Needs to check the validity before clearing calling identity
1956        final boolean calledFromValidUser = calledFromValidUser();
1957
1958        InputBindResult res = null;
1959        long ident = Binder.clearCallingIdentity();
1960        try {
1961            synchronized (mMethodMap) {
1962                if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
1963                        + " controlFlags=#" + Integer.toHexString(controlFlags)
1964                        + " softInputMode=#" + Integer.toHexString(softInputMode)
1965                        + " windowFlags=#" + Integer.toHexString(windowFlags));
1966
1967                ClientState cs = mClients.get(client.asBinder());
1968                if (cs == null) {
1969                    throw new IllegalArgumentException("unknown client "
1970                            + client.asBinder());
1971                }
1972
1973                try {
1974                    if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1975                        // Check with the window manager to make sure this client actually
1976                        // has a window with focus.  If not, reject.  This is thread safe
1977                        // because if the focus changes some time before or after, the
1978                        // next client receiving focus that has any interest in input will
1979                        // be calling through here after that change happens.
1980                        Slog.w(TAG, "Focus gain on non-focused client " + cs.client
1981                                + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1982                        return null;
1983                    }
1984                } catch (RemoteException e) {
1985                }
1986
1987                if (!calledFromValidUser) {
1988                    Slog.w(TAG, "A background user is requesting window. Hiding IME.");
1989                    Slog.w(TAG, "If you want to interect with IME, you need "
1990                            + "android.permission.INTERACT_ACROSS_USERS_FULL");
1991                    hideCurrentInputLocked(0, null);
1992                    return null;
1993                }
1994
1995                if (mCurFocusedWindow == windowToken) {
1996                    Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
1997                            + " attribute=" + attribute + ", token = " + windowToken);
1998                    if (attribute != null) {
1999                        return startInputUncheckedLocked(cs, inputContext, attribute,
2000                                controlFlags);
2001                    }
2002                    return null;
2003                }
2004                mCurFocusedWindow = windowToken;
2005
2006                // Should we auto-show the IME even if the caller has not
2007                // specified what should be done with it?
2008                // We only do this automatically if the window can resize
2009                // to accommodate the IME (so what the user sees will give
2010                // them good context without input information being obscured
2011                // by the IME) or if running on a large screen where there
2012                // is more room for the target window + IME.
2013                final boolean doAutoShow =
2014                        (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
2015                                == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
2016                        || mRes.getConfiguration().isLayoutSizeAtLeast(
2017                                Configuration.SCREENLAYOUT_SIZE_LARGE);
2018                final boolean isTextEditor =
2019                        (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
2020
2021                // We want to start input before showing the IME, but after closing
2022                // it.  We want to do this after closing it to help the IME disappear
2023                // more quickly (not get stuck behind it initializing itself for the
2024                // new focused input, even if its window wants to hide the IME).
2025                boolean didStart = false;
2026
2027                switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
2028                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
2029                        if (!isTextEditor || !doAutoShow) {
2030                            if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
2031                                // There is no focus view, and this window will
2032                                // be behind any soft input window, so hide the
2033                                // soft input window if it is shown.
2034                                if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
2035                                hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
2036                            }
2037                        } else if (isTextEditor && doAutoShow && (softInputMode &
2038                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2039                            // There is a focus view, and we are navigating forward
2040                            // into the window, so show the input window for the user.
2041                            // We only do this automatically if the window can resize
2042                            // to accommodate the IME (so what the user sees will give
2043                            // them good context without input information being obscured
2044                            // by the IME) or if running on a large screen where there
2045                            // is more room for the target window + IME.
2046                            if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
2047                            if (attribute != null) {
2048                                res = startInputUncheckedLocked(cs, inputContext, attribute,
2049                                        controlFlags);
2050                                didStart = true;
2051                            }
2052                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2053                        }
2054                        break;
2055                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2056                        // Do nothing.
2057                        break;
2058                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
2059                        if ((softInputMode &
2060                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2061                            if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
2062                            hideCurrentInputLocked(0, null);
2063                        }
2064                        break;
2065                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2066                        if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2067                        hideCurrentInputLocked(0, null);
2068                        break;
2069                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
2070                        if ((softInputMode &
2071                                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2072                            if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2073                            if (attribute != null) {
2074                                res = startInputUncheckedLocked(cs, inputContext, attribute,
2075                                        controlFlags);
2076                                didStart = true;
2077                            }
2078                            showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2079                        }
2080                        break;
2081                    case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2082                        if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2083                        if (attribute != null) {
2084                            res = startInputUncheckedLocked(cs, inputContext, attribute,
2085                                    controlFlags);
2086                            didStart = true;
2087                        }
2088                        showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2089                        break;
2090                }
2091
2092                if (!didStart && attribute != null) {
2093                    res = startInputUncheckedLocked(cs, inputContext, attribute,
2094                            controlFlags);
2095                }
2096            }
2097        } finally {
2098            Binder.restoreCallingIdentity(ident);
2099        }
2100
2101        return res;
2102    }
2103
2104    @Override
2105    public void showInputMethodPickerFromClient(IInputMethodClient client) {
2106        if (!calledFromValidUser()) {
2107            return;
2108        }
2109        synchronized (mMethodMap) {
2110            if (mCurClient == null || client == null
2111                    || mCurClient.client.asBinder() != client.asBinder()) {
2112                Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2113                        + Binder.getCallingUid() + ": " + client);
2114            }
2115
2116            // Always call subtype picker, because subtype picker is a superset of input method
2117            // picker.
2118            mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
2119        }
2120    }
2121
2122    @Override
2123    public void setInputMethod(IBinder token, String id) {
2124        if (!calledFromValidUser()) {
2125            return;
2126        }
2127        setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2128    }
2129
2130    @Override
2131    public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2132        if (!calledFromValidUser()) {
2133            return;
2134        }
2135        synchronized (mMethodMap) {
2136            if (subtype != null) {
2137                setInputMethodWithSubtypeIdLocked(token, id,
2138                        InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
2139                                subtype.hashCode()));
2140            } else {
2141                setInputMethod(token, id);
2142            }
2143        }
2144    }
2145
2146    @Override
2147    public void showInputMethodAndSubtypeEnablerFromClient(
2148            IInputMethodClient client, String inputMethodId) {
2149        if (!calledFromValidUser()) {
2150            return;
2151        }
2152        synchronized (mMethodMap) {
2153            if (mCurClient == null || client == null
2154                || mCurClient.client.asBinder() != client.asBinder()) {
2155                Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
2156            }
2157            executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2158                    MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2159        }
2160    }
2161
2162    @Override
2163    public boolean switchToLastInputMethod(IBinder token) {
2164        if (!calledFromValidUser()) {
2165            return false;
2166        }
2167        synchronized (mMethodMap) {
2168            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2169            final InputMethodInfo lastImi;
2170            if (lastIme != null) {
2171                lastImi = mMethodMap.get(lastIme.first);
2172            } else {
2173                lastImi = null;
2174            }
2175            String targetLastImiId = null;
2176            int subtypeId = NOT_A_SUBTYPE_ID;
2177            if (lastIme != null && lastImi != null) {
2178                final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2179                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2180                final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2181                        : mCurrentSubtype.hashCode();
2182                // If the last IME is the same as the current IME and the last subtype is not
2183                // defined, there is no need to switch to the last IME.
2184                if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2185                    targetLastImiId = lastIme.first;
2186                    subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2187                }
2188            }
2189
2190            if (TextUtils.isEmpty(targetLastImiId)
2191                    && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
2192                // This is a safety net. If the currentSubtype can't be added to the history
2193                // and the framework couldn't find the last ime, we will make the last ime be
2194                // the most applicable enabled keyboard subtype of the system imes.
2195                final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2196                if (enabled != null) {
2197                    final int N = enabled.size();
2198                    final String locale = mCurrentSubtype == null
2199                            ? mRes.getConfiguration().locale.toString()
2200                            : mCurrentSubtype.getLocale();
2201                    for (int i = 0; i < N; ++i) {
2202                        final InputMethodInfo imi = enabled.get(i);
2203                        if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
2204                            InputMethodSubtype keyboardSubtype =
2205                                    InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
2206                                            InputMethodUtils.getSubtypes(imi),
2207                                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
2208                            if (keyboardSubtype != null) {
2209                                targetLastImiId = imi.getId();
2210                                subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2211                                        imi, keyboardSubtype.hashCode());
2212                                if(keyboardSubtype.getLocale().equals(locale)) {
2213                                    break;
2214                                }
2215                            }
2216                        }
2217                    }
2218                }
2219            }
2220
2221            if (!TextUtils.isEmpty(targetLastImiId)) {
2222                if (DEBUG) {
2223                    Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2224                            + ", from: " + mCurMethodId + ", " + subtypeId);
2225                }
2226                setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
2227                return true;
2228            } else {
2229                return false;
2230            }
2231        }
2232    }
2233
2234    @Override
2235    public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2236        if (!calledFromValidUser()) {
2237            return false;
2238        }
2239        synchronized (mMethodMap) {
2240            if (!calledWithValidToken(token)) {
2241                final int uid = Binder.getCallingUid();
2242                Slog.e(TAG, "Ignoring switchToNextInputMethod due to an invalid token. uid:" + uid
2243                        + " token:" + token);
2244                return false;
2245            }
2246            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2247                    onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
2248            if (nextSubtype == null) {
2249                return false;
2250            }
2251            setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
2252                    nextSubtype.mSubtypeId);
2253            return true;
2254        }
2255    }
2256
2257    @Override
2258    public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2259        if (!calledFromValidUser()) {
2260            return false;
2261        }
2262        synchronized (mMethodMap) {
2263            if (!calledWithValidToken(token)) {
2264                final int uid = Binder.getCallingUid();
2265                Slog.e(TAG, "Ignoring shouldOfferSwitchingToNextInputMethod due to an invalid "
2266                        + "token. uid:" + uid + " token:" + token);
2267                return false;
2268            }
2269            final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2270                    false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
2271            if (nextSubtype == null) {
2272                return false;
2273            }
2274            return true;
2275        }
2276    }
2277
2278    @Override
2279    public InputMethodSubtype getLastInputMethodSubtype() {
2280        if (!calledFromValidUser()) {
2281            return null;
2282        }
2283        synchronized (mMethodMap) {
2284            final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2285            // TODO: Handle the case of the last IME with no subtypes
2286            if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2287                    || TextUtils.isEmpty(lastIme.second)) return null;
2288            final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2289            if (lastImi == null) return null;
2290            try {
2291                final int lastSubtypeHash = Integer.valueOf(lastIme.second);
2292                final int lastSubtypeId =
2293                        InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2294                if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2295                    return null;
2296                }
2297                return lastImi.getSubtypeAt(lastSubtypeId);
2298            } catch (NumberFormatException e) {
2299                return null;
2300            }
2301        }
2302    }
2303
2304    @Override
2305    public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2306        if (!calledFromValidUser()) {
2307            return;
2308        }
2309        // By this IPC call, only a process which shares the same uid with the IME can add
2310        // additional input method subtypes to the IME.
2311        if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
2312        synchronized (mMethodMap) {
2313            final InputMethodInfo imi = mMethodMap.get(imiId);
2314            if (imi == null) return;
2315            final String[] packageInfos;
2316            try {
2317                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2318            } catch (RemoteException e) {
2319                Slog.e(TAG, "Failed to get package infos");
2320                return;
2321            }
2322            if (packageInfos != null) {
2323                final int packageNum = packageInfos.length;
2324                for (int i = 0; i < packageNum; ++i) {
2325                    if (packageInfos[i].equals(imi.getPackageName())) {
2326                        mFileManager.addInputMethodSubtypes(imi, subtypes);
2327                        final long ident = Binder.clearCallingIdentity();
2328                        try {
2329                            buildInputMethodListLocked(mMethodList, mMethodMap,
2330                                    false /* resetDefaultEnabledIme */);
2331                        } finally {
2332                            Binder.restoreCallingIdentity(ident);
2333                        }
2334                        return;
2335                    }
2336                }
2337            }
2338        }
2339        return;
2340    }
2341
2342    @Override
2343    public int getInputMethodWindowVisibleHeight() {
2344        return mWindowManagerService.getInputMethodWindowVisibleHeight();
2345    }
2346
2347    @Override
2348    public void notifyUserAction(int sequenceNumber) {
2349        if (DEBUG) {
2350            Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
2351        }
2352        synchronized (mMethodMap) {
2353            if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
2354                if (DEBUG) {
2355                    Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
2356                            + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
2357                            + " actual: " + sequenceNumber);
2358                }
2359                return;
2360            }
2361            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2362            if (imi != null) {
2363                mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
2364            }
2365        }
2366    }
2367
2368    private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2369        synchronized (mMethodMap) {
2370            setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
2371        }
2372    }
2373
2374    private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
2375        if (token == null) {
2376            if (mContext.checkCallingOrSelfPermission(
2377                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2378                    != PackageManager.PERMISSION_GRANTED) {
2379                throw new SecurityException(
2380                        "Using null token requires permission "
2381                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2382            }
2383        } else if (mCurToken != token) {
2384            Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2385                    + " token: " + token);
2386            return;
2387        }
2388
2389        final long ident = Binder.clearCallingIdentity();
2390        try {
2391            setInputMethodLocked(id, subtypeId);
2392        } finally {
2393            Binder.restoreCallingIdentity(ident);
2394        }
2395    }
2396
2397    @Override
2398    public void hideMySoftInput(IBinder token, int flags) {
2399        if (!calledFromValidUser()) {
2400            return;
2401        }
2402        synchronized (mMethodMap) {
2403            if (!calledWithValidToken(token)) {
2404                final int uid = Binder.getCallingUid();
2405                Slog.e(TAG, "Ignoring hideInputMethod due to an invalid token. uid:"
2406                        + uid + " token:" + token);
2407                return;
2408            }
2409            long ident = Binder.clearCallingIdentity();
2410            try {
2411                hideCurrentInputLocked(flags, null);
2412            } finally {
2413                Binder.restoreCallingIdentity(ident);
2414            }
2415        }
2416    }
2417
2418    @Override
2419    public void showMySoftInput(IBinder token, int flags) {
2420        if (!calledFromValidUser()) {
2421            return;
2422        }
2423        synchronized (mMethodMap) {
2424            if (!calledWithValidToken(token)) {
2425                final int uid = Binder.getCallingUid();
2426                Slog.e(TAG, "Ignoring showMySoftInput due to an invalid token. uid:"
2427                        + uid + " token:" + token);
2428                return;
2429            }
2430            long ident = Binder.clearCallingIdentity();
2431            try {
2432                showCurrentInputLocked(flags, null);
2433            } finally {
2434                Binder.restoreCallingIdentity(ident);
2435            }
2436        }
2437    }
2438
2439    void setEnabledSessionInMainThread(SessionState session) {
2440        if (mEnabledSession != session) {
2441            if (mEnabledSession != null && mEnabledSession.session != null) {
2442                try {
2443                    if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
2444                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
2445                } catch (RemoteException e) {
2446                }
2447            }
2448            mEnabledSession = session;
2449            if (mEnabledSession != null && mEnabledSession.session != null) {
2450                try {
2451                    if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
2452                    mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
2453                } catch (RemoteException e) {
2454                }
2455            }
2456        }
2457    }
2458
2459    @Override
2460    public boolean handleMessage(Message msg) {
2461        SomeArgs args;
2462        switch (msg.what) {
2463            case MSG_SHOW_IM_PICKER:
2464                showInputMethodMenu();
2465                return true;
2466
2467            case MSG_SHOW_IM_SUBTYPE_PICKER:
2468                showInputMethodSubtypeMenu();
2469                return true;
2470
2471            case MSG_SHOW_IM_SUBTYPE_ENABLER:
2472                args = (SomeArgs)msg.obj;
2473                showInputMethodAndSubtypeEnabler((String)args.arg1);
2474                args.recycle();
2475                return true;
2476
2477            case MSG_SHOW_IM_CONFIG:
2478                showConfigureInputMethods();
2479                return true;
2480
2481            // ---------------------------------------------------------
2482
2483            case MSG_UNBIND_INPUT:
2484                try {
2485                    ((IInputMethod)msg.obj).unbindInput();
2486                } catch (RemoteException e) {
2487                    // There is nothing interesting about the method dying.
2488                }
2489                return true;
2490            case MSG_BIND_INPUT:
2491                args = (SomeArgs)msg.obj;
2492                try {
2493                    ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
2494                } catch (RemoteException e) {
2495                }
2496                args.recycle();
2497                return true;
2498            case MSG_SHOW_SOFT_INPUT:
2499                args = (SomeArgs)msg.obj;
2500                try {
2501                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
2502                            + msg.arg1 + ", " + args.arg2 + ")");
2503                    ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
2504                } catch (RemoteException e) {
2505                }
2506                args.recycle();
2507                return true;
2508            case MSG_HIDE_SOFT_INPUT:
2509                args = (SomeArgs)msg.obj;
2510                try {
2511                    if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
2512                            + args.arg2 + ")");
2513                    ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
2514                } catch (RemoteException e) {
2515                }
2516                args.recycle();
2517                return true;
2518            case MSG_ATTACH_TOKEN:
2519                args = (SomeArgs)msg.obj;
2520                try {
2521                    if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
2522                    ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
2523                } catch (RemoteException e) {
2524                }
2525                args.recycle();
2526                return true;
2527            case MSG_CREATE_SESSION: {
2528                args = (SomeArgs)msg.obj;
2529                IInputMethod method = (IInputMethod)args.arg1;
2530                InputChannel channel = (InputChannel)args.arg2;
2531                try {
2532                    method.createSession(channel, (IInputSessionCallback)args.arg3);
2533                } catch (RemoteException e) {
2534                } finally {
2535                    // Dispose the channel if the input method is not local to this process
2536                    // because the remote proxy will get its own copy when unparceled.
2537                    if (channel != null && Binder.isProxy(method)) {
2538                        channel.dispose();
2539                    }
2540                }
2541                args.recycle();
2542                return true;
2543            }
2544            // ---------------------------------------------------------
2545
2546            case MSG_START_INPUT:
2547                args = (SomeArgs)msg.obj;
2548                try {
2549                    SessionState session = (SessionState)args.arg1;
2550                    setEnabledSessionInMainThread(session);
2551                    session.method.startInput((IInputContext)args.arg2,
2552                            (EditorInfo)args.arg3);
2553                } catch (RemoteException e) {
2554                }
2555                args.recycle();
2556                return true;
2557            case MSG_RESTART_INPUT:
2558                args = (SomeArgs)msg.obj;
2559                try {
2560                    SessionState session = (SessionState)args.arg1;
2561                    setEnabledSessionInMainThread(session);
2562                    session.method.restartInput((IInputContext)args.arg2,
2563                            (EditorInfo)args.arg3);
2564                } catch (RemoteException e) {
2565                }
2566                args.recycle();
2567                return true;
2568
2569            // ---------------------------------------------------------
2570
2571            case MSG_UNBIND_METHOD:
2572                try {
2573                    ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
2574                } catch (RemoteException e) {
2575                    // There is nothing interesting about the last client dying.
2576                }
2577                return true;
2578            case MSG_BIND_METHOD: {
2579                args = (SomeArgs)msg.obj;
2580                IInputMethodClient client = (IInputMethodClient)args.arg1;
2581                InputBindResult res = (InputBindResult)args.arg2;
2582                try {
2583                    client.onBindMethod(res);
2584                } catch (RemoteException e) {
2585                    Slog.w(TAG, "Client died receiving input method " + args.arg2);
2586                } finally {
2587                    // Dispose the channel if the input method is not local to this process
2588                    // because the remote proxy will get its own copy when unparceled.
2589                    if (res.channel != null && Binder.isProxy(client)) {
2590                        res.channel.dispose();
2591                    }
2592                }
2593                args.recycle();
2594                return true;
2595            }
2596            case MSG_SET_ACTIVE:
2597                try {
2598                    ((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
2599                } catch (RemoteException e) {
2600                    Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
2601                            + ((ClientState)msg.obj).pid + " uid "
2602                            + ((ClientState)msg.obj).uid);
2603                }
2604                return true;
2605            case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
2606                final int sequenceNumber = msg.arg1;
2607                final IInputMethodClient client = (IInputMethodClient)msg.obj;
2608                try {
2609                    client.setUserActionNotificationSequenceNumber(sequenceNumber);
2610                } catch (RemoteException e) {
2611                    Slog.w(TAG, "Got RemoteException sending "
2612                            + "setUserActionNotificationSequenceNumber("
2613                            + sequenceNumber + ") notification to pid "
2614                            + ((ClientState)msg.obj).pid + " uid "
2615                            + ((ClientState)msg.obj).uid);
2616                }
2617                return true;
2618            }
2619
2620            // --------------------------------------------------------------
2621            case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
2622                mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
2623                return true;
2624        }
2625        return false;
2626    }
2627
2628    private boolean chooseNewDefaultIMELocked() {
2629        final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
2630                mSettings.getEnabledInputMethodListLocked());
2631        if (imi != null) {
2632            if (DEBUG) {
2633                Slog.d(TAG, "New default IME was selected: " + imi.getId());
2634            }
2635            resetSelectedInputMethodAndSubtypeLocked(imi.getId());
2636            return true;
2637        }
2638
2639        return false;
2640    }
2641
2642    void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
2643            HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
2644        if (DEBUG) {
2645            Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
2646                    + " \n ------ \n" + InputMethodUtils.getStackTrace());
2647        }
2648        list.clear();
2649        map.clear();
2650
2651        // Use for queryIntentServicesAsUser
2652        final PackageManager pm = mContext.getPackageManager();
2653        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
2654        if (disabledSysImes == null) disabledSysImes = "";
2655
2656        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
2657                new Intent(InputMethod.SERVICE_INTERFACE),
2658                PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
2659                mSettings.getCurrentUserId());
2660
2661        final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
2662                mFileManager.getAllAdditionalInputMethodSubtypes();
2663        for (int i = 0; i < services.size(); ++i) {
2664            ResolveInfo ri = services.get(i);
2665            ServiceInfo si = ri.serviceInfo;
2666            ComponentName compName = new ComponentName(si.packageName, si.name);
2667            if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
2668                    si.permission)) {
2669                Slog.w(TAG, "Skipping input method " + compName
2670                        + ": it does not require the permission "
2671                        + android.Manifest.permission.BIND_INPUT_METHOD);
2672                continue;
2673            }
2674
2675            if (DEBUG) Slog.d(TAG, "Checking " + compName);
2676
2677            try {
2678                InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
2679                list.add(p);
2680                final String id = p.getId();
2681                map.put(id, p);
2682
2683                if (DEBUG) {
2684                    Slog.d(TAG, "Found an input method " + p);
2685                }
2686
2687            } catch (XmlPullParserException e) {
2688                Slog.w(TAG, "Unable to load input method " + compName, e);
2689            } catch (IOException e) {
2690                Slog.w(TAG, "Unable to load input method " + compName, e);
2691            }
2692        }
2693
2694        if (resetDefaultEnabledIme) {
2695            final ArrayList<InputMethodInfo> defaultEnabledIme =
2696                    InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
2697            for (int i = 0; i < defaultEnabledIme.size(); ++i) {
2698                final InputMethodInfo imi =  defaultEnabledIme.get(i);
2699                if (DEBUG) {
2700                    Slog.d(TAG, "--- enable ime = " + imi);
2701                }
2702                setInputMethodEnabledLocked(imi.getId(), true);
2703            }
2704        }
2705
2706        final String defaultImiId = mSettings.getSelectedInputMethod();
2707        if (!TextUtils.isEmpty(defaultImiId)) {
2708            if (!map.containsKey(defaultImiId)) {
2709                Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
2710                if (chooseNewDefaultIMELocked()) {
2711                    updateInputMethodsFromSettingsLocked(true);
2712                }
2713            } else {
2714                // Double check that the default IME is certainly enabled.
2715                setInputMethodEnabledLocked(defaultImiId, true);
2716            }
2717        }
2718        // Here is not the perfect place to reset the switching controller. Ideally
2719        // mSwitchingController and mSettings should be able to share the same state.
2720        // TODO: Make sure that mSwitchingController and mSettings are sharing the
2721        // the same enabled IMEs list.
2722        mSwitchingController.resetCircularListLocked(mContext);
2723    }
2724
2725    // ----------------------------------------------------------------------
2726
2727    private void showInputMethodMenu() {
2728        showInputMethodMenuInternal(false);
2729    }
2730
2731    private void showInputMethodSubtypeMenu() {
2732        showInputMethodMenuInternal(true);
2733    }
2734
2735    private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
2736        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
2737        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2738                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2739                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2740        if (!TextUtils.isEmpty(inputMethodId)) {
2741            intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
2742        }
2743        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
2744    }
2745
2746    private void showConfigureInputMethods() {
2747        Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
2748        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
2749                | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
2750                | Intent.FLAG_ACTIVITY_CLEAR_TOP);
2751        mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
2752    }
2753
2754    private boolean isScreenLocked() {
2755        return mKeyguardManager != null
2756                && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
2757    }
2758
2759    private void showInputMethodMenuInternal(boolean showSubtypes) {
2760        if (DEBUG) Slog.v(TAG, "Show switching menu");
2761
2762        final Context context = mContext;
2763        final boolean isScreenLocked = isScreenLocked();
2764
2765        final String lastInputMethodId = mSettings.getSelectedInputMethod();
2766        int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
2767        if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
2768
2769        synchronized (mMethodMap) {
2770            final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
2771                    mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
2772                            mContext);
2773            if (immis == null || immis.size() == 0) {
2774                return;
2775            }
2776
2777            hideInputMethodMenuLocked();
2778
2779            final List<ImeSubtypeListItem> imList =
2780                    mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
2781                            showSubtypes, mInputShown, isScreenLocked);
2782
2783            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
2784                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
2785                if (currentSubtype != null) {
2786                    final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2787                    lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2788                            currentImi, currentSubtype.hashCode());
2789                }
2790            }
2791
2792            final int N = imList.size();
2793            mIms = new InputMethodInfo[N];
2794            mSubtypeIds = new int[N];
2795            int checkedItem = 0;
2796            for (int i = 0; i < N; ++i) {
2797                final ImeSubtypeListItem item = imList.get(i);
2798                mIms[i] = item.mImi;
2799                mSubtypeIds[i] = item.mSubtypeId;
2800                if (mIms[i].getId().equals(lastInputMethodId)) {
2801                    int subtypeId = mSubtypeIds[i];
2802                    if ((subtypeId == NOT_A_SUBTYPE_ID)
2803                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
2804                            || (subtypeId == lastInputMethodSubtypeId)) {
2805                        checkedItem = i;
2806                    }
2807                }
2808            }
2809            final Context themedContext = new ContextThemeWrapper(context,
2810                    android.R.style.Theme_DeviceDefault_Settings);
2811            mDialogBuilder = new AlertDialog.Builder(themedContext);
2812            final TypedArray a = themedContext.obtainStyledAttributes(null,
2813                    com.android.internal.R.styleable.DialogPreference,
2814                    com.android.internal.R.attr.alertDialogStyle, 0);
2815            mDialogBuilder.setIcon(a.getDrawable(
2816                    com.android.internal.R.styleable.DialogPreference_dialogIcon));
2817            a.recycle();
2818            mDialogBuilder.setOnCancelListener(new OnCancelListener() {
2819                @Override
2820                public void onCancel(DialogInterface dialog) {
2821                    hideInputMethodMenu();
2822                }
2823            });
2824            final LayoutInflater inflater =
2825                    (LayoutInflater)themedContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2826            final View tv = inflater.inflate(
2827                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
2828            mDialogBuilder.setCustomTitle(tv);
2829
2830            // Setup layout for a toggle switch of the hardware keyboard
2831            mSwitchingDialogTitleView = tv;
2832            mSwitchingDialogTitleView
2833                    .findViewById(com.android.internal.R.id.hard_keyboard_section)
2834                    .setVisibility(mWindowManagerService.isHardKeyboardAvailable()
2835                            ? View.VISIBLE : View.GONE);
2836            final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
2837                    com.android.internal.R.id.hard_keyboard_switch);
2838            hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
2839            hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
2840                @Override
2841                public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
2842                    mSettings.setShowImeWithHardKeyboard(isChecked);
2843                    // Ensure that the input method dialog is dismissed when changing
2844                    // the hardware keyboard state.
2845                    hideInputMethodMenu();
2846                }
2847            });
2848
2849            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(themedContext,
2850                    com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
2851            final OnClickListener choiceListener = new OnClickListener() {
2852                @Override
2853                public void onClick(final DialogInterface dialog, final int which) {
2854                    synchronized (mMethodMap) {
2855                        if (mIms == null || mIms.length <= which || mSubtypeIds == null
2856                                || mSubtypeIds.length <= which) {
2857                            return;
2858                        }
2859                        final InputMethodInfo im = mIms[which];
2860                        int subtypeId = mSubtypeIds[which];
2861                        adapter.mCheckedItem = which;
2862                        adapter.notifyDataSetChanged();
2863                        hideInputMethodMenu();
2864                        if (im != null) {
2865                            if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
2866                                subtypeId = NOT_A_SUBTYPE_ID;
2867                            }
2868                            setInputMethodLocked(im.getId(), subtypeId);
2869                        }
2870                    }
2871                }
2872            };
2873            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
2874
2875            if (showSubtypes && !isScreenLocked) {
2876                final OnClickListener positiveListener = new OnClickListener() {
2877                    @Override
2878                    public void onClick(DialogInterface dialog, int whichButton) {
2879                        showConfigureInputMethods();
2880                    }
2881                };
2882                mDialogBuilder.setPositiveButton(
2883                        com.android.internal.R.string.configure_input_methods, positiveListener);
2884            }
2885            mSwitchingDialog = mDialogBuilder.create();
2886            mSwitchingDialog.setCanceledOnTouchOutside(true);
2887            mSwitchingDialog.getWindow().setType(
2888                    WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
2889            mSwitchingDialog.getWindow().getAttributes().privateFlags |=
2890                    WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
2891            mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
2892            updateImeWindowStatusLocked();
2893            mSwitchingDialog.show();
2894        }
2895    }
2896
2897    private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
2898        private final LayoutInflater mInflater;
2899        private final int mTextViewResourceId;
2900        private final List<ImeSubtypeListItem> mItemsList;
2901        public int mCheckedItem;
2902        public ImeSubtypeListAdapter(Context context, int textViewResourceId,
2903                List<ImeSubtypeListItem> itemsList, int checkedItem) {
2904            super(context, textViewResourceId, itemsList);
2905            mTextViewResourceId = textViewResourceId;
2906            mItemsList = itemsList;
2907            mCheckedItem = checkedItem;
2908            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2909        }
2910
2911        @Override
2912        public View getView(int position, View convertView, ViewGroup parent) {
2913            final View view = convertView != null ? convertView
2914                    : mInflater.inflate(mTextViewResourceId, null);
2915            if (position < 0 || position >= mItemsList.size()) return view;
2916            final ImeSubtypeListItem item = mItemsList.get(position);
2917            final CharSequence imeName = item.mImeName;
2918            final CharSequence subtypeName = item.mSubtypeName;
2919            final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
2920            final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
2921            if (TextUtils.isEmpty(subtypeName)) {
2922                firstTextView.setText(imeName);
2923                secondTextView.setVisibility(View.GONE);
2924            } else {
2925                firstTextView.setText(subtypeName);
2926                secondTextView.setText(imeName);
2927                secondTextView.setVisibility(View.VISIBLE);
2928            }
2929            final RadioButton radioButton =
2930                    (RadioButton)view.findViewById(com.android.internal.R.id.radio);
2931            radioButton.setChecked(position == mCheckedItem);
2932            return view;
2933        }
2934    }
2935
2936    void hideInputMethodMenu() {
2937        synchronized (mMethodMap) {
2938            hideInputMethodMenuLocked();
2939        }
2940    }
2941
2942    void hideInputMethodMenuLocked() {
2943        if (DEBUG) Slog.v(TAG, "Hide switching menu");
2944
2945        if (mSwitchingDialog != null) {
2946            mSwitchingDialog.dismiss();
2947            mSwitchingDialog = null;
2948        }
2949
2950        updateImeWindowStatusLocked();
2951        mDialogBuilder = null;
2952        mIms = null;
2953    }
2954
2955    // ----------------------------------------------------------------------
2956
2957    @Override
2958    public boolean setInputMethodEnabled(String id, boolean enabled) {
2959        // TODO: Make this work even for non-current users?
2960        if (!calledFromValidUser()) {
2961            return false;
2962        }
2963        synchronized (mMethodMap) {
2964            if (mContext.checkCallingOrSelfPermission(
2965                    android.Manifest.permission.WRITE_SECURE_SETTINGS)
2966                    != PackageManager.PERMISSION_GRANTED) {
2967                throw new SecurityException(
2968                        "Requires permission "
2969                        + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2970            }
2971
2972            long ident = Binder.clearCallingIdentity();
2973            try {
2974                return setInputMethodEnabledLocked(id, enabled);
2975            } finally {
2976                Binder.restoreCallingIdentity(ident);
2977            }
2978        }
2979    }
2980
2981    boolean setInputMethodEnabledLocked(String id, boolean enabled) {
2982        // Make sure this is a valid input method.
2983        InputMethodInfo imm = mMethodMap.get(id);
2984        if (imm == null) {
2985            throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
2986        }
2987
2988        List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
2989                .getEnabledInputMethodsAndSubtypeListLocked();
2990
2991        if (enabled) {
2992            for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
2993                if (pair.first.equals(id)) {
2994                    // We are enabling this input method, but it is already enabled.
2995                    // Nothing to do. The previous state was enabled.
2996                    return true;
2997                }
2998            }
2999            mSettings.appendAndPutEnabledInputMethodLocked(id, false);
3000            // Previous state was disabled.
3001            return false;
3002        } else {
3003            StringBuilder builder = new StringBuilder();
3004            if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3005                    builder, enabledInputMethodsList, id)) {
3006                // Disabled input method is currently selected, switch to another one.
3007                final String selId = mSettings.getSelectedInputMethod();
3008                if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
3009                    Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
3010                    resetSelectedInputMethodAndSubtypeLocked("");
3011                }
3012                // Previous state was enabled.
3013                return true;
3014            } else {
3015                // We are disabling the input method but it is already disabled.
3016                // Nothing to do.  The previous state was disabled.
3017                return false;
3018            }
3019        }
3020    }
3021
3022    private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
3023            boolean setSubtypeOnly) {
3024        // Update the history of InputMethod and Subtype
3025        mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
3026
3027        mCurUserActionNotificationSequenceNumber =
3028                Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
3029        if (DEBUG) {
3030            Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
3031                    + mCurUserActionNotificationSequenceNumber);
3032        }
3033
3034        if (mCurClient != null && mCurClient.client != null) {
3035            executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
3036                    MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
3037                    mCurUserActionNotificationSequenceNumber, mCurClient.client));
3038        }
3039
3040        // Set Subtype here
3041        if (imi == null || subtypeId < 0) {
3042            mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3043            mCurrentSubtype = null;
3044        } else {
3045            if (subtypeId < imi.getSubtypeCount()) {
3046                InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
3047                mSettings.putSelectedSubtype(subtype.hashCode());
3048                mCurrentSubtype = subtype;
3049            } else {
3050                mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3051                // If the subtype is not specified, choose the most applicable one
3052                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
3053            }
3054        }
3055
3056        // Workaround.
3057        // ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
3058        // IMEs are not recognized and considered uninstalled.
3059        // Actually, we can't move everything after SystemReady because
3060        // IMMS needs to run in the encryption lock screen. So, we just skip changing
3061        // the default IME here and try cheking the default IME again in systemReady().
3062        // TODO: Do nothing before system ready and implement a separated logic for
3063        // the encryption lock screen.
3064        // TODO: ASEC should be ready before IMMS is instantiated.
3065        if (mSystemReady && !setSubtypeOnly) {
3066            // Set InputMethod here
3067            mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
3068        }
3069    }
3070
3071    private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
3072        InputMethodInfo imi = mMethodMap.get(newDefaultIme);
3073        int lastSubtypeId = NOT_A_SUBTYPE_ID;
3074        // newDefaultIme is empty when there is no candidate for the selected IME.
3075        if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
3076            String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
3077            if (subtypeHashCode != null) {
3078                try {
3079                    lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3080                            imi, Integer.valueOf(subtypeHashCode));
3081                } catch (NumberFormatException e) {
3082                    Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
3083                }
3084            }
3085        }
3086        setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
3087    }
3088
3089    // If there are no selected shortcuts, tries finding the most applicable ones.
3090    private Pair<InputMethodInfo, InputMethodSubtype>
3091            findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
3092        List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
3093        InputMethodInfo mostApplicableIMI = null;
3094        InputMethodSubtype mostApplicableSubtype = null;
3095        boolean foundInSystemIME = false;
3096
3097        // Search applicable subtype for each InputMethodInfo
3098        for (InputMethodInfo imi: imis) {
3099            final String imiId = imi.getId();
3100            if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3101                continue;
3102            }
3103            InputMethodSubtype subtype = null;
3104            final List<InputMethodSubtype> enabledSubtypes =
3105                    mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3106            // 1. Search by the current subtype's locale from enabledSubtypes.
3107            if (mCurrentSubtype != null) {
3108                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3109                        mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
3110            }
3111            // 2. Search by the system locale from enabledSubtypes.
3112            // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
3113            if (subtype == null) {
3114                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3115                        mRes, enabledSubtypes, mode, null, true);
3116            }
3117            final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
3118                    InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
3119            final ArrayList<InputMethodSubtype> subtypesForSearch =
3120                    overridingImplicitlyEnabledSubtypes.isEmpty()
3121                            ? InputMethodUtils.getSubtypes(imi)
3122                            : overridingImplicitlyEnabledSubtypes;
3123            // 4. Search by the current subtype's locale from all subtypes.
3124            if (subtype == null && mCurrentSubtype != null) {
3125                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3126                        mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
3127            }
3128            // 5. Search by the system locale from all subtypes.
3129            // 6. Search the first enabled subtype matched with mode from all subtypes.
3130            if (subtype == null) {
3131                subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3132                        mRes, subtypesForSearch, mode, null, true);
3133            }
3134            if (subtype != null) {
3135                if (imiId.equals(mCurMethodId)) {
3136                    // The current input method is the most applicable IME.
3137                    mostApplicableIMI = imi;
3138                    mostApplicableSubtype = subtype;
3139                    break;
3140                } else if (!foundInSystemIME) {
3141                    // The system input method is 2nd applicable IME.
3142                    mostApplicableIMI = imi;
3143                    mostApplicableSubtype = subtype;
3144                    if ((imi.getServiceInfo().applicationInfo.flags
3145                            & ApplicationInfo.FLAG_SYSTEM) != 0) {
3146                        foundInSystemIME = true;
3147                    }
3148                }
3149            }
3150        }
3151        if (DEBUG) {
3152            if (mostApplicableIMI != null) {
3153                Slog.w(TAG, "Most applicable shortcut input method was:"
3154                        + mostApplicableIMI.getId());
3155                if (mostApplicableSubtype != null) {
3156                    Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3157                            + "," + mostApplicableSubtype.getMode() + ","
3158                            + mostApplicableSubtype.getLocale());
3159                }
3160            }
3161        }
3162        if (mostApplicableIMI != null) {
3163            return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
3164                    mostApplicableSubtype);
3165        } else {
3166            return null;
3167        }
3168    }
3169
3170    /**
3171     * @return Return the current subtype of this input method.
3172     */
3173    @Override
3174    public InputMethodSubtype getCurrentInputMethodSubtype() {
3175        // TODO: Make this work even for non-current users?
3176        if (!calledFromValidUser()) {
3177            return null;
3178        }
3179        synchronized (mMethodMap) {
3180            return getCurrentInputMethodSubtypeLocked();
3181        }
3182    }
3183
3184    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3185        if (mCurMethodId == null) {
3186            return null;
3187        }
3188        final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3189        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3190        if (imi == null || imi.getSubtypeCount() == 0) {
3191            return null;
3192        }
3193        if (!subtypeIsSelected || mCurrentSubtype == null
3194                || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3195            int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
3196            if (subtypeId == NOT_A_SUBTYPE_ID) {
3197                // If there are no selected subtypes, the framework will try to find
3198                // the most applicable subtype from explicitly or implicitly enabled
3199                // subtypes.
3200                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3201                        mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3202                // If there is only one explicitly or implicitly enabled subtype,
3203                // just returns it.
3204                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3205                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3206                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3207                    mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3208                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
3209                            InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
3210                    if (mCurrentSubtype == null) {
3211                        mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3212                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3213                                true);
3214                    }
3215                }
3216            } else {
3217                mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3218            }
3219        }
3220        return mCurrentSubtype;
3221    }
3222
3223    private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
3224            InputMethodSubtype subtype) {
3225        if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
3226            mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
3227        } else {
3228            ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3229            subtypes.add(subtype);
3230            mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
3231        }
3232    }
3233
3234    // TODO: We should change the return type from List to List<Parcelable>
3235    @SuppressWarnings("rawtypes")
3236    @Override
3237    public List getShortcutInputMethodsAndSubtypes() {
3238        synchronized (mMethodMap) {
3239            ArrayList<Object> ret = new ArrayList<Object>();
3240            if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3241                // If there are no selected shortcut subtypes, the framework will try to find
3242                // the most applicable subtype from all subtypes whose mode is
3243                // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3244                Pair<InputMethodInfo, InputMethodSubtype> info =
3245                    findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3246                            InputMethodUtils.SUBTYPE_MODE_VOICE);
3247                if (info != null) {
3248                    ret.add(info.first);
3249                    ret.add(info.second);
3250                }
3251                return ret;
3252            }
3253            for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3254                ret.add(imi);
3255                for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3256                    ret.add(subtype);
3257                }
3258            }
3259            return ret;
3260        }
3261    }
3262
3263    @Override
3264    public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3265        // TODO: Make this work even for non-current users?
3266        if (!calledFromValidUser()) {
3267            return false;
3268        }
3269        synchronized (mMethodMap) {
3270            if (subtype != null && mCurMethodId != null) {
3271                InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3272                int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
3273                if (subtypeId != NOT_A_SUBTYPE_ID) {
3274                    setInputMethodLocked(mCurMethodId, subtypeId);
3275                    return true;
3276                }
3277            }
3278            return false;
3279        }
3280    }
3281
3282    // TODO: Cache the state for each user and reset when the cached user is removed.
3283    private static class InputMethodFileManager {
3284        private static final String SYSTEM_PATH = "system";
3285        private static final String INPUT_METHOD_PATH = "inputmethod";
3286        private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3287        private static final String NODE_SUBTYPES = "subtypes";
3288        private static final String NODE_SUBTYPE = "subtype";
3289        private static final String NODE_IMI = "imi";
3290        private static final String ATTR_ID = "id";
3291        private static final String ATTR_LABEL = "label";
3292        private static final String ATTR_ICON = "icon";
3293        private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3294        private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3295        private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3296        private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3297        private final AtomicFile mAdditionalInputMethodSubtypeFile;
3298        private final HashMap<String, InputMethodInfo> mMethodMap;
3299        private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3300                new HashMap<String, List<InputMethodSubtype>>();
3301        public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3302            if (methodMap == null) {
3303                throw new NullPointerException("methodMap is null");
3304            }
3305            mMethodMap = methodMap;
3306            final File systemDir = userId == UserHandle.USER_OWNER
3307                    ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3308                    : Environment.getUserSystemDirectory(userId);
3309            final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3310            if (!inputMethodDir.mkdirs()) {
3311                Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3312            }
3313            final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3314            mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3315            if (!subtypeFile.exists()) {
3316                // If "subtypes.xml" doesn't exist, create a blank file.
3317                writeAdditionalInputMethodSubtypes(
3318                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3319            } else {
3320                readAdditionalInputMethodSubtypes(
3321                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3322            }
3323        }
3324
3325        private void deleteAllInputMethodSubtypes(String imiId) {
3326            synchronized (mMethodMap) {
3327                mAdditionalSubtypesMap.remove(imiId);
3328                writeAdditionalInputMethodSubtypes(
3329                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3330            }
3331        }
3332
3333        public void addInputMethodSubtypes(
3334                InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3335            synchronized (mMethodMap) {
3336                final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
3337                final int N = additionalSubtypes.length;
3338                for (int i = 0; i < N; ++i) {
3339                    final InputMethodSubtype subtype = additionalSubtypes[i];
3340                    if (!subtypes.contains(subtype)) {
3341                        subtypes.add(subtype);
3342                    } else {
3343                        Slog.w(TAG, "Duplicated subtype definition found: "
3344                                + subtype.getLocale() + ", " + subtype.getMode());
3345                    }
3346                }
3347                mAdditionalSubtypesMap.put(imi.getId(), subtypes);
3348                writeAdditionalInputMethodSubtypes(
3349                        mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3350            }
3351        }
3352
3353        public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
3354            synchronized (mMethodMap) {
3355                return mAdditionalSubtypesMap;
3356            }
3357        }
3358
3359        private static void writeAdditionalInputMethodSubtypes(
3360                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
3361                HashMap<String, InputMethodInfo> methodMap) {
3362            // Safety net for the case that this function is called before methodMap is set.
3363            final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
3364            FileOutputStream fos = null;
3365            try {
3366                fos = subtypesFile.startWrite();
3367                final XmlSerializer out = new FastXmlSerializer();
3368                out.setOutput(fos, "utf-8");
3369                out.startDocument(null, true);
3370                out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
3371                out.startTag(null, NODE_SUBTYPES);
3372                for (String imiId : allSubtypes.keySet()) {
3373                    if (isSetMethodMap && !methodMap.containsKey(imiId)) {
3374                        Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
3375                        continue;
3376                    }
3377                    out.startTag(null, NODE_IMI);
3378                    out.attribute(null, ATTR_ID, imiId);
3379                    final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
3380                    final int N = subtypesList.size();
3381                    for (int i = 0; i < N; ++i) {
3382                        final InputMethodSubtype subtype = subtypesList.get(i);
3383                        out.startTag(null, NODE_SUBTYPE);
3384                        out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
3385                        out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
3386                        out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
3387                        out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
3388                        out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
3389                        out.attribute(null, ATTR_IS_AUXILIARY,
3390                                String.valueOf(subtype.isAuxiliary() ? 1 : 0));
3391                        out.endTag(null, NODE_SUBTYPE);
3392                    }
3393                    out.endTag(null, NODE_IMI);
3394                }
3395                out.endTag(null, NODE_SUBTYPES);
3396                out.endDocument();
3397                subtypesFile.finishWrite(fos);
3398            } catch (java.io.IOException e) {
3399                Slog.w(TAG, "Error writing subtypes", e);
3400                if (fos != null) {
3401                    subtypesFile.failWrite(fos);
3402                }
3403            }
3404        }
3405
3406        private static void readAdditionalInputMethodSubtypes(
3407                HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
3408            if (allSubtypes == null || subtypesFile == null) return;
3409            allSubtypes.clear();
3410            FileInputStream fis = null;
3411            try {
3412                fis = subtypesFile.openRead();
3413                final XmlPullParser parser = Xml.newPullParser();
3414                parser.setInput(fis, null);
3415                int type = parser.getEventType();
3416                // Skip parsing until START_TAG
3417                while ((type = parser.next()) != XmlPullParser.START_TAG
3418                        && type != XmlPullParser.END_DOCUMENT) {}
3419                String firstNodeName = parser.getName();
3420                if (!NODE_SUBTYPES.equals(firstNodeName)) {
3421                    throw new XmlPullParserException("Xml doesn't start with subtypes");
3422                }
3423                final int depth =parser.getDepth();
3424                String currentImiId = null;
3425                ArrayList<InputMethodSubtype> tempSubtypesArray = null;
3426                while (((type = parser.next()) != XmlPullParser.END_TAG
3427                        || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
3428                    if (type != XmlPullParser.START_TAG)
3429                        continue;
3430                    final String nodeName = parser.getName();
3431                    if (NODE_IMI.equals(nodeName)) {
3432                        currentImiId = parser.getAttributeValue(null, ATTR_ID);
3433                        if (TextUtils.isEmpty(currentImiId)) {
3434                            Slog.w(TAG, "Invalid imi id found in subtypes.xml");
3435                            continue;
3436                        }
3437                        tempSubtypesArray = new ArrayList<InputMethodSubtype>();
3438                        allSubtypes.put(currentImiId, tempSubtypesArray);
3439                    } else if (NODE_SUBTYPE.equals(nodeName)) {
3440                        if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
3441                            Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
3442                            continue;
3443                        }
3444                        final int icon = Integer.valueOf(
3445                                parser.getAttributeValue(null, ATTR_ICON));
3446                        final int label = Integer.valueOf(
3447                                parser.getAttributeValue(null, ATTR_LABEL));
3448                        final String imeSubtypeLocale =
3449                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
3450                        final String imeSubtypeMode =
3451                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
3452                        final String imeSubtypeExtraValue =
3453                                parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
3454                        final boolean isAuxiliary = "1".equals(String.valueOf(
3455                                parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
3456                        final InputMethodSubtype subtype =
3457                                new InputMethodSubtype(label, icon, imeSubtypeLocale,
3458                                        imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
3459                        tempSubtypesArray.add(subtype);
3460                    }
3461                }
3462            } catch (XmlPullParserException e) {
3463                Slog.w(TAG, "Error reading subtypes: " + e);
3464                return;
3465            } catch (java.io.IOException e) {
3466                Slog.w(TAG, "Error reading subtypes: " + e);
3467                return;
3468            } catch (NumberFormatException e) {
3469                Slog.w(TAG, "Error reading subtypes: " + e);
3470                return;
3471            } finally {
3472                if (fis != null) {
3473                    try {
3474                        fis.close();
3475                    } catch (java.io.IOException e1) {
3476                        Slog.w(TAG, "Failed to close.");
3477                    }
3478                }
3479            }
3480        }
3481    }
3482
3483    @Override
3484    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
3485        if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
3486                != PackageManager.PERMISSION_GRANTED) {
3487
3488            pw.println("Permission Denial: can't dump InputMethodManager from from pid="
3489                    + Binder.getCallingPid()
3490                    + ", uid=" + Binder.getCallingUid());
3491            return;
3492        }
3493
3494        IInputMethod method;
3495        ClientState client;
3496
3497        final Printer p = new PrintWriterPrinter(pw);
3498
3499        synchronized (mMethodMap) {
3500            p.println("Current Input Method Manager state:");
3501            int N = mMethodList.size();
3502            p.println("  Input Methods:");
3503            for (int i=0; i<N; i++) {
3504                InputMethodInfo info = mMethodList.get(i);
3505                p.println("  InputMethod #" + i + ":");
3506                info.dump(p, "    ");
3507            }
3508            p.println("  Clients:");
3509            for (ClientState ci : mClients.values()) {
3510                p.println("  Client " + ci + ":");
3511                p.println("    client=" + ci.client);
3512                p.println("    inputContext=" + ci.inputContext);
3513                p.println("    sessionRequested=" + ci.sessionRequested);
3514                p.println("    curSession=" + ci.curSession);
3515            }
3516            p.println("  mCurMethodId=" + mCurMethodId);
3517            client = mCurClient;
3518            p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
3519            p.println("  mCurFocusedWindow=" + mCurFocusedWindow);
3520            p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
3521                    + " mBoundToMethod=" + mBoundToMethod);
3522            p.println("  mCurToken=" + mCurToken);
3523            p.println("  mCurIntent=" + mCurIntent);
3524            method = mCurMethod;
3525            p.println("  mCurMethod=" + mCurMethod);
3526            p.println("  mEnabledSession=" + mEnabledSession);
3527            p.println("  mShowRequested=" + mShowRequested
3528                    + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
3529                    + " mShowForced=" + mShowForced
3530                    + " mInputShown=" + mInputShown);
3531            p.println("  mCurUserActionNotificationSequenceNumber="
3532                    + mCurUserActionNotificationSequenceNumber);
3533            p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mScreenOn);
3534        }
3535
3536        p.println(" ");
3537        if (client != null) {
3538            pw.flush();
3539            try {
3540                client.client.asBinder().dump(fd, args);
3541            } catch (RemoteException e) {
3542                p.println("Input method client dead: " + e);
3543            }
3544        } else {
3545            p.println("No input method client.");
3546        }
3547
3548        p.println(" ");
3549        if (method != null) {
3550            pw.flush();
3551            try {
3552                method.asBinder().dump(fd, args);
3553            } catch (RemoteException e) {
3554                p.println("Input method service dead: " + e);
3555            }
3556        } else {
3557            p.println("No input method service.");
3558        }
3559    }
3560}
3561