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