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