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