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