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