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