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