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