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