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