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