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