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