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