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