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