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