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