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