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