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