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