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 static android.view.Display.DEFAULT_DISPLAY;
     19 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
     20 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
     21 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG;
     22 import static java.lang.annotation.RetentionPolicy.SOURCE;
     23 
     24 import com.android.internal.annotations.GuardedBy;
     25 import com.android.internal.content.PackageMonitor;
     26 import com.android.internal.inputmethod.IInputContentUriToken;
     27 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController;
     28 import com.android.internal.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
     29 import com.android.internal.inputmethod.InputMethodUtils;
     30 import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
     31 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
     32 import com.android.internal.notification.SystemNotificationChannels;
     33 import com.android.internal.os.HandlerCaller;
     34 import com.android.internal.os.SomeArgs;
     35 import com.android.internal.os.TransferPipe;
     36 import com.android.internal.util.DumpUtils;
     37 import com.android.internal.util.FastXmlSerializer;
     38 import com.android.internal.util.IndentingPrintWriter;
     39 import com.android.internal.view.IInputContext;
     40 import com.android.internal.view.IInputMethod;
     41 import com.android.internal.view.IInputMethodClient;
     42 import com.android.internal.view.IInputMethodManager;
     43 import com.android.internal.view.IInputMethodSession;
     44 import com.android.internal.view.IInputSessionCallback;
     45 import com.android.internal.view.InputBindResult;
     46 import com.android.internal.view.InputMethodClient;
     47 import com.android.server.statusbar.StatusBarManagerService;
     48 
     49 import org.xmlpull.v1.XmlPullParser;
     50 import org.xmlpull.v1.XmlPullParserException;
     51 import org.xmlpull.v1.XmlSerializer;
     52 
     53 import android.Manifest;
     54 import android.annotation.AnyThread;
     55 import android.annotation.BinderThread;
     56 import android.annotation.ColorInt;
     57 import android.annotation.IntDef;
     58 import android.annotation.MainThread;
     59 import android.annotation.NonNull;
     60 import android.annotation.Nullable;
     61 import android.annotation.RequiresPermission;
     62 import android.annotation.TestApi;
     63 import android.annotation.UserIdInt;
     64 import android.app.ActivityManager;
     65 import android.app.ActivityManagerInternal;
     66 import android.app.ActivityThread;
     67 import android.app.AlertDialog;
     68 import android.app.AppGlobals;
     69 import android.app.AppOpsManager;
     70 import android.app.KeyguardManager;
     71 import android.app.Notification;
     72 import android.app.NotificationManager;
     73 import android.app.PendingIntent;
     74 import android.content.BroadcastReceiver;
     75 import android.content.ComponentName;
     76 import android.content.ContentProvider;
     77 import android.content.ContentResolver;
     78 import android.content.Context;
     79 import android.content.DialogInterface;
     80 import android.content.DialogInterface.OnCancelListener;
     81 import android.content.DialogInterface.OnClickListener;
     82 import android.content.Intent;
     83 import android.content.IntentFilter;
     84 import android.content.ServiceConnection;
     85 import android.content.pm.ApplicationInfo;
     86 import android.content.pm.IPackageManager;
     87 import android.content.pm.PackageManager;
     88 import android.content.pm.ResolveInfo;
     89 import android.content.pm.ServiceInfo;
     90 import android.content.res.Configuration;
     91 import android.content.res.Resources;
     92 import android.content.res.TypedArray;
     93 import android.database.ContentObserver;
     94 import android.graphics.drawable.Drawable;
     95 import android.inputmethodservice.InputMethodService;
     96 import android.net.Uri;
     97 import android.os.Binder;
     98 import android.os.Bundle;
     99 import android.os.Debug;
    100 import android.os.Environment;
    101 import android.os.Handler;
    102 import android.os.IBinder;
    103 import android.os.IInterface;
    104 import android.os.Message;
    105 import android.os.LocaleList;
    106 import android.os.Parcel;
    107 import android.os.Process;
    108 import android.os.RemoteException;
    109 import android.os.ResultReceiver;
    110 import android.os.ServiceManager;
    111 import android.os.ShellCallback;
    112 import android.os.ShellCommand;
    113 import android.os.SystemClock;
    114 import android.os.SystemProperties;
    115 import android.os.UserHandle;
    116 import android.os.UserManager;
    117 import android.provider.Settings;
    118 import android.service.vr.IVrManager;
    119 import android.service.vr.IVrStateCallbacks;
    120 import android.text.TextUtils;
    121 import android.text.style.SuggestionSpan;
    122 import android.util.ArraySet;
    123 import android.util.AtomicFile;
    124 import android.util.EventLog;
    125 import android.util.LruCache;
    126 import android.util.Pair;
    127 import android.util.PrintWriterPrinter;
    128 import android.util.Printer;
    129 import android.util.Slog;
    130 import android.util.Xml;
    131 import android.view.ContextThemeWrapper;
    132 import android.view.IWindowManager;
    133 import android.view.InputChannel;
    134 import android.view.LayoutInflater;
    135 import android.view.View;
    136 import android.view.ViewGroup;
    137 import android.view.Window;
    138 import android.view.WindowManager;
    139 import android.view.inputmethod.EditorInfo;
    140 import android.view.inputmethod.InputBinding;
    141 import android.view.inputmethod.InputConnection;
    142 import android.view.inputmethod.InputConnectionInspector;
    143 import android.view.inputmethod.InputMethod;
    144 import android.view.inputmethod.InputMethodInfo;
    145 import android.view.inputmethod.InputMethodManager;
    146 import android.view.inputmethod.InputMethodManagerInternal;
    147 import android.view.inputmethod.InputMethodSubtype;
    148 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
    149 import android.widget.ArrayAdapter;
    150 import android.widget.CompoundButton;
    151 import android.widget.CompoundButton.OnCheckedChangeListener;
    152 import android.widget.RadioButton;
    153 import android.widget.Switch;
    154 import android.widget.TextView;
    155 import android.widget.Toast;
    156 
    157 import com.android.server.wm.WindowManagerInternal;
    158 
    159 import java.io.File;
    160 import java.io.FileDescriptor;
    161 import java.io.FileInputStream;
    162 import java.io.FileOutputStream;
    163 import java.io.IOException;
    164 import java.io.PrintWriter;
    165 import java.lang.annotation.Retention;
    166 import java.nio.charset.StandardCharsets;
    167 import java.security.InvalidParameterException;
    168 import java.text.SimpleDateFormat;
    169 import java.util.ArrayList;
    170 import java.util.Collections;
    171 import java.util.Date;
    172 import java.util.HashMap;
    173 import java.util.List;
    174 import java.util.Locale;
    175 import java.util.WeakHashMap;
    176 import java.util.concurrent.atomic.AtomicInteger;
    177 
    178 /**
    179  * This class provides a system service that manages input methods.
    180  */
    181 public class InputMethodManagerService extends IInputMethodManager.Stub
    182         implements ServiceConnection, Handler.Callback {
    183     static final boolean DEBUG = false;
    184     static final String TAG = "InputMethodManagerService";
    185 
    186     @Retention(SOURCE)
    187     @IntDef({ShellCommandResult.SUCCESS, ShellCommandResult.FAILURE})
    188     private @interface ShellCommandResult {
    189         int SUCCESS = 0;
    190         int FAILURE = -1;
    191     }
    192 
    193     static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
    194     static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
    195     static final int MSG_SHOW_IM_CONFIG = 3;
    196 
    197     static final int MSG_UNBIND_INPUT = 1000;
    198     static final int MSG_BIND_INPUT = 1010;
    199     static final int MSG_SHOW_SOFT_INPUT = 1020;
    200     static final int MSG_HIDE_SOFT_INPUT = 1030;
    201     static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
    202     static final int MSG_ATTACH_TOKEN = 1040;
    203     static final int MSG_CREATE_SESSION = 1050;
    204 
    205     static final int MSG_START_INPUT = 2000;
    206     static final int MSG_START_VR_INPUT = 2010;
    207 
    208     static final int MSG_UNBIND_CLIENT = 3000;
    209     static final int MSG_BIND_CLIENT = 3010;
    210     static final int MSG_SET_ACTIVE = 3020;
    211     static final int MSG_SET_INTERACTIVE = 3030;
    212     static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
    213     static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
    214     static final int MSG_SWITCH_IME = 3050;
    215 
    216     static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
    217 
    218     static final int MSG_SYSTEM_UNLOCK_USER = 5000;
    219 
    220     static final long TIME_TO_RECONNECT = 3 * 1000;
    221 
    222     static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
    223 
    224     private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
    225     private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
    226 
    227     /**
    228      * Binding flags for establishing connection to the {@link InputMethodService}.
    229      */
    230     private static final int IME_CONNECTION_BIND_FLAGS =
    231             Context.BIND_AUTO_CREATE
    232             | Context.BIND_NOT_VISIBLE
    233             | Context.BIND_NOT_FOREGROUND
    234             | Context.BIND_IMPORTANT_BACKGROUND;
    235 
    236     /**
    237      * Binding flags used only while the {@link InputMethodService} is showing window.
    238      */
    239     private static final int IME_VISIBLE_BIND_FLAGS =
    240             Context.BIND_AUTO_CREATE
    241             | Context.BIND_TREAT_LIKE_ACTIVITY
    242             | Context.BIND_FOREGROUND_SERVICE
    243             | Context.BIND_SHOWING_UI;
    244 
    245     @Retention(SOURCE)
    246     @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
    247     private @interface  HardKeyboardBehavior {
    248         int WIRELESS_AFFORDANCE = 0;
    249         int WIRED_AFFORDANCE = 1;
    250     }
    251 
    252     /**
    253      * A protected broadcast intent action for internal use for {@link PendingIntent} in
    254      * the notification.
    255      */
    256     private static final String ACTION_SHOW_INPUT_METHOD_PICKER =
    257             "com.android.server.InputMethodManagerService.SHOW_INPUT_METHOD_PICKER";
    258 
    259     /**
    260      * Debug flag for overriding runtime {@link SystemProperties}.
    261      */
    262     @AnyThread
    263     private static final class DebugFlag {
    264         private static final Object LOCK = new Object();
    265         private final String mKey;
    266         private final boolean mDefaultValue;
    267         @GuardedBy("LOCK")
    268         private boolean mValue;
    269 
    270         public DebugFlag(String key, boolean defaultValue) {
    271             mKey = key;
    272             mDefaultValue = defaultValue;
    273             mValue = SystemProperties.getBoolean(key, defaultValue);
    274         }
    275 
    276         void refresh() {
    277             synchronized (LOCK) {
    278                 mValue = SystemProperties.getBoolean(mKey, mDefaultValue);
    279             }
    280         }
    281 
    282         boolean value() {
    283             synchronized (LOCK) {
    284                 return mValue;
    285             }
    286         }
    287     }
    288 
    289     /**
    290      * Debug flags that can be overridden using "adb shell setprop <key>"
    291      * Note: These flags are cached. To refresh, run "adb shell ime refresh_debug_properties".
    292      */
    293     private static final class DebugFlags {
    294         static final DebugFlag FLAG_OPTIMIZE_START_INPUT =
    295                 new DebugFlag("debug.optimize_startinput", false);
    296     }
    297 
    298 
    299     final Context mContext;
    300     final Resources mRes;
    301     final Handler mHandler;
    302     final InputMethodSettings mSettings;
    303     final SettingsObserver mSettingsObserver;
    304     final IWindowManager mIWindowManager;
    305     final WindowManagerInternal mWindowManagerInternal;
    306     final HandlerCaller mCaller;
    307     final boolean mHasFeature;
    308     private InputMethodFileManager mFileManager;
    309     private final HardKeyboardListener mHardKeyboardListener;
    310     private final AppOpsManager mAppOpsManager;
    311     private final UserManager mUserManager;
    312 
    313     // All known input methods.  mMethodMap also serves as the global
    314     // lock for this class.
    315     final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
    316     final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
    317     private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
    318             new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
    319     private final InputMethodSubtypeSwitchingController mSwitchingController;
    320 
    321     /**
    322      * Tracks how many times {@link #mMethodMap} was updated.
    323      */
    324     @GuardedBy("mMethodMap")
    325     private int mMethodMapUpdateCount = 0;
    326 
    327     // Used to bring IME service up to visible adjustment while it is being shown.
    328     final ServiceConnection mVisibleConnection = new ServiceConnection() {
    329         @Override public void onServiceConnected(ComponentName name, IBinder service) {
    330         }
    331 
    332         @Override public void onServiceDisconnected(ComponentName name) {
    333         }
    334     };
    335     boolean mVisibleBound = false;
    336 
    337     // Ongoing notification
    338     private NotificationManager mNotificationManager;
    339     private KeyguardManager mKeyguardManager;
    340     private @Nullable StatusBarManagerService mStatusBar;
    341     private Notification.Builder mImeSwitcherNotification;
    342     private PendingIntent mImeSwitchPendingIntent;
    343     private boolean mShowOngoingImeSwitcherForPhones;
    344     private boolean mNotificationShown;
    345 
    346     static class SessionState {
    347         final ClientState client;
    348         final IInputMethod method;
    349 
    350         IInputMethodSession session;
    351         InputChannel channel;
    352 
    353         @Override
    354         public String toString() {
    355             return "SessionState{uid " + client.uid + " pid " + client.pid
    356                     + " method " + Integer.toHexString(
    357                             System.identityHashCode(method))
    358                     + " session " + Integer.toHexString(
    359                             System.identityHashCode(session))
    360                     + " channel " + channel
    361                     + "}";
    362         }
    363 
    364         SessionState(ClientState _client, IInputMethod _method,
    365                 IInputMethodSession _session, InputChannel _channel) {
    366             client = _client;
    367             method = _method;
    368             session = _session;
    369             channel = _channel;
    370         }
    371     }
    372 
    373     /**
    374      * VR state callback.
    375      * Listens for when VR mode finishes.
    376      */
    377     private final IVrStateCallbacks mVrStateCallbacks = new IVrStateCallbacks.Stub() {
    378         @Override
    379         public void onVrStateChanged(boolean enabled) {
    380             if (!enabled) {
    381                 restoreNonVrImeFromSettingsNoCheck();
    382             }
    383         }
    384     };
    385 
    386     private void restoreNonVrImeFromSettingsNoCheck() {
    387         // switch back to non-VR InputMethod from settings.
    388         synchronized (mMethodMap) {
    389             final String lastInputId = mSettings.getSelectedInputMethod();
    390             setInputMethodLocked(lastInputId,
    391                     mSettings.getSelectedInputMethodSubtypeId(lastInputId));
    392         }
    393     }
    394 
    395     static final class ClientState {
    396         final IInputMethodClient client;
    397         final IInputContext inputContext;
    398         final int uid;
    399         final int pid;
    400         final InputBinding binding;
    401 
    402         boolean sessionRequested;
    403         SessionState curSession;
    404 
    405         @Override
    406         public String toString() {
    407             return "ClientState{" + Integer.toHexString(
    408                     System.identityHashCode(this)) + " uid " + uid
    409                     + " pid " + pid + "}";
    410         }
    411 
    412         ClientState(IInputMethodClient _client, IInputContext _inputContext,
    413                 int _uid, int _pid) {
    414             client = _client;
    415             inputContext = _inputContext;
    416             uid = _uid;
    417             pid = _pid;
    418             binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
    419         }
    420     }
    421 
    422     final HashMap<IBinder, ClientState> mClients = new HashMap<>();
    423 
    424     /**
    425      * Set once the system is ready to run third party code.
    426      */
    427     boolean mSystemReady;
    428 
    429     /**
    430      * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
    431      * method.  This is to be synchronized with the secure settings keyed with
    432      * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
    433      *
    434      * <p>This can be transiently {@code null} when the system is re-initializing input method
    435      * settings, e.g., the system locale is just changed.</p>
    436      *
    437      * <p>Note that {@link #mCurId} is used to track which IME is being connected to
    438      * {@link InputMethodManagerService}.</p>
    439      *
    440      * @see #mCurId
    441      */
    442     @Nullable
    443     String mCurMethodId;
    444 
    445     /**
    446      * The current binding sequence number, incremented every time there is
    447      * a new bind performed.
    448      */
    449     int mCurSeq;
    450 
    451     /**
    452      * The client that is currently bound to an input method.
    453      */
    454     ClientState mCurClient;
    455 
    456     /**
    457      * The last window token that we confirmed to be focused.  This is always updated upon reports
    458      * from the input method client.  If the window state is already changed before the report is
    459      * handled, this field just keeps the last value.
    460      */
    461     IBinder mCurFocusedWindow;
    462 
    463     /**
    464      * {@link WindowManager.LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
    465      *
    466      * @see #mCurFocusedWindow
    467      */
    468     int mCurFocusedWindowSoftInputMode;
    469 
    470     /**
    471      * The client by which {@link #mCurFocusedWindow} was reported.
    472      */
    473     ClientState mCurFocusedWindowClient;
    474 
    475     /**
    476      * The input context last provided by the current client.
    477      */
    478     IInputContext mCurInputContext;
    479 
    480     /**
    481      * The missing method flags for the input context last provided by the current client.
    482      *
    483      * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
    484      */
    485     int mCurInputContextMissingMethods;
    486 
    487     /**
    488      * The attributes last provided by the current client.
    489      */
    490     EditorInfo mCurAttribute;
    491 
    492     /**
    493      * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
    494      * connected to or in the process of connecting to.
    495      *
    496      * <p>This can be {@code null} when no input method is connected.</p>
    497      *
    498      * @see #mCurMethodId
    499      */
    500     @Nullable
    501     String mCurId;
    502 
    503     /**
    504      * The current subtype of the current input method.
    505      */
    506     private InputMethodSubtype mCurrentSubtype;
    507 
    508     // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
    509     private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
    510             mShortcutInputMethodsAndSubtypes = new HashMap<>();
    511 
    512     // Was the keyguard locked when this client became current?
    513     private boolean mCurClientInKeyguard;
    514 
    515     /**
    516      * Set to true if our ServiceConnection is currently actively bound to
    517      * a service (whether or not we have gotten its IBinder back yet).
    518      */
    519     boolean mHaveConnection;
    520 
    521     /**
    522      * Set if the client has asked for the input method to be shown.
    523      */
    524     boolean mShowRequested;
    525 
    526     /**
    527      * Set if we were explicitly told to show the input method.
    528      */
    529     boolean mShowExplicitlyRequested;
    530 
    531     /**
    532      * Set if we were forced to be shown.
    533      */
    534     boolean mShowForced;
    535 
    536     /**
    537      * Set if we last told the input method to show itself.
    538      */
    539     boolean mInputShown;
    540 
    541     /**
    542      * {@code true} if the current input method is in fullscreen mode.
    543      */
    544     boolean mInFullscreenMode;
    545 
    546     /**
    547      * The Intent used to connect to the current input method.
    548      */
    549     Intent mCurIntent;
    550 
    551     /**
    552      * The token we have made for the currently active input method, to
    553      * identify it in the future.
    554      */
    555     IBinder mCurToken;
    556 
    557     /**
    558      * If non-null, this is the input method service we are currently connected
    559      * to.
    560      */
    561     IInputMethod mCurMethod;
    562 
    563     /**
    564      * Time that we last initiated a bind to the input method, to determine
    565      * if we should try to disconnect and reconnect to it.
    566      */
    567     long mLastBindTime;
    568 
    569     /**
    570      * Have we called mCurMethod.bindInput()?
    571      */
    572     boolean mBoundToMethod;
    573 
    574     /**
    575      * Currently enabled session.  Only touched by service thread, not
    576      * protected by a lock.
    577      */
    578     SessionState mEnabledSession;
    579 
    580     /**
    581      * True if the device is currently interactive with user.  The value is true initially.
    582      */
    583     boolean mIsInteractive = true;
    584 
    585     int mCurUserActionNotificationSequenceNumber = 0;
    586 
    587     int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
    588 
    589     /**
    590      * A set of status bits regarding the active IME.
    591      *
    592      * <p>This value is a combination of following two bits:</p>
    593      * <dl>
    594      * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
    595      * <dd>
    596      *   If this bit is ON, connected IME is ready to accept touch/key events.
    597      * </dd>
    598      * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
    599      * <dd>
    600      *   If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
    601      * </dd>
    602      * </dl>
    603      * <em>Do not update this value outside of setImeWindowStatus.</em>
    604      */
    605     int mImeWindowVis;
    606 
    607     private AlertDialog.Builder mDialogBuilder;
    608     private AlertDialog mSwitchingDialog;
    609     private IBinder mSwitchingDialogToken = new Binder();
    610     private View mSwitchingDialogTitleView;
    611     private Toast mSubtypeSwitchedByShortCutToast;
    612     private InputMethodInfo[] mIms;
    613     private int[] mSubtypeIds;
    614     private LocaleList mLastSystemLocales;
    615     private boolean mShowImeWithHardKeyboard;
    616     private boolean mAccessibilityRequestingNoSoftKeyboard;
    617     private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
    618     private final IPackageManager mIPackageManager;
    619     private final String mSlotIme;
    620     @HardKeyboardBehavior
    621     private final int mHardKeyboardBehavior;
    622 
    623     /**
    624      * Whether we temporarily allow IMEs implemented in instant apps to run for testing.
    625      *
    626      * <p>Note: This is quite dangerous.  Don't forget to reset after you finish testing.</p>
    627      */
    628     private boolean mBindInstantServiceAllowed = false;
    629 
    630     /**
    631      * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the
    632      * internal message queue. Any subsequent state change inside {@link InputMethodManagerService}
    633      * will not affect those tasks that are already posted.
    634      *
    635      * <p>Posting {@link #MSG_START_INPUT} message basically means that
    636      * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
    637      * back in the current IME process shortly, which will also affect what the current IME starts
    638      * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
    639      * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
    640      * logical input session between the client application and the current IME.</p>
    641      *
    642      * <p>Be careful to not keep strong references to this object forever, which can prevent
    643      * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
    644      * </p>
    645      */
    646     private static class StartInputInfo {
    647         private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
    648 
    649         final int mSequenceNumber;
    650         final long mTimestamp;
    651         final long mWallTime;
    652         @NonNull
    653         final IBinder mImeToken;
    654         @NonNull
    655         final String mImeId;
    656         // @InputMethodClient.StartInputReason
    657         final int mStartInputReason;
    658         final boolean mRestarting;
    659         @Nullable
    660         final IBinder mTargetWindow;
    661         @NonNull
    662         final EditorInfo mEditorInfo;
    663         final int mTargetWindowSoftInputMode;
    664         final int mClientBindSequenceNumber;
    665 
    666         StartInputInfo(@NonNull IBinder imeToken, @NonNull String imeId,
    667                 /* @InputMethodClient.StartInputReason */ int startInputReason, boolean restarting,
    668                 @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo,
    669                 int targetWindowSoftInputMode, int clientBindSequenceNumber) {
    670             mSequenceNumber = sSequenceNumber.getAndIncrement();
    671             mTimestamp = SystemClock.uptimeMillis();
    672             mWallTime = System.currentTimeMillis();
    673             mImeToken = imeToken;
    674             mImeId = imeId;
    675             mStartInputReason = startInputReason;
    676             mRestarting = restarting;
    677             mTargetWindow = targetWindow;
    678             mEditorInfo = editorInfo;
    679             mTargetWindowSoftInputMode = targetWindowSoftInputMode;
    680             mClientBindSequenceNumber = clientBindSequenceNumber;
    681         }
    682     }
    683 
    684     @GuardedBy("mMethodMap")
    685     private final WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
    686 
    687     /**
    688      * A ring buffer to store the history of {@link StartInputInfo}.
    689      */
    690     private static final class StartInputHistory {
    691         /**
    692          * Entry size for non low-RAM devices.
    693          *
    694          * <p>TODO: Consider to follow what other system services have been doing to manage
    695          * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
    696          */
    697         private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16;
    698 
    699         /**
    700          * Entry size for non low-RAM devices.
    701          *
    702          * <p>TODO: Consider to follow what other system services have been doing to manage
    703          * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
    704          */
    705         private final static int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
    706 
    707         private static int getEntrySize() {
    708             if (ActivityManager.isLowRamDeviceStatic()) {
    709                 return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
    710             } else {
    711                 return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
    712             }
    713         }
    714 
    715         /**
    716          * Backing store for the ring bugger.
    717          */
    718         private final Entry[] mEntries = new Entry[getEntrySize()];
    719 
    720         /**
    721          * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
    722          * write.
    723          */
    724         private int mNextIndex = 0;
    725 
    726         /**
    727          * Recyclable entry to store the information in {@link StartInputInfo}.
    728          */
    729         private static final class Entry {
    730             int mSequenceNumber;
    731             long mTimestamp;
    732             long mWallTime;
    733             @NonNull
    734             String mImeTokenString;
    735             @NonNull
    736             String mImeId;
    737             /* @InputMethodClient.StartInputReason */
    738             int mStartInputReason;
    739             boolean mRestarting;
    740             @NonNull
    741             String mTargetWindowString;
    742             @NonNull
    743             EditorInfo mEditorInfo;
    744             int mTargetWindowSoftInputMode;
    745             int mClientBindSequenceNumber;
    746 
    747             Entry(@NonNull StartInputInfo original) {
    748                 set(original);
    749             }
    750 
    751             void set(@NonNull StartInputInfo original) {
    752                 mSequenceNumber = original.mSequenceNumber;
    753                 mTimestamp = original.mTimestamp;
    754                 mWallTime = original.mWallTime;
    755                 // Intentionally convert to String so as not to keep a strong reference to a Binder
    756                 // object.
    757                 mImeTokenString = String.valueOf(original.mImeToken);
    758                 mImeId = original.mImeId;
    759                 mStartInputReason = original.mStartInputReason;
    760                 mRestarting = original.mRestarting;
    761                 // Intentionally convert to String so as not to keep a strong reference to a Binder
    762                 // object.
    763                 mTargetWindowString = String.valueOf(original.mTargetWindow);
    764                 mEditorInfo = original.mEditorInfo;
    765                 mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
    766                 mClientBindSequenceNumber = original.mClientBindSequenceNumber;
    767             }
    768         }
    769 
    770         /**
    771          * Add a new entry and discard the oldest entry as needed.
    772          * @param info {@lin StartInputInfo} to be added.
    773          */
    774         void addEntry(@NonNull StartInputInfo info) {
    775             final int index = mNextIndex;
    776             if (mEntries[index] == null) {
    777                 mEntries[index] = new Entry(info);
    778             } else {
    779                 mEntries[index].set(info);
    780             }
    781             mNextIndex = (mNextIndex + 1) % mEntries.length;
    782         }
    783 
    784         void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
    785             final SimpleDateFormat dataFormat =
    786                     new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
    787 
    788             for (int i = 0; i < mEntries.length; ++i) {
    789                 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
    790                 if (entry == null) {
    791                     continue;
    792                 }
    793                 pw.print(prefix);
    794                 pw.println("StartInput #" + entry.mSequenceNumber + ":");
    795 
    796                 pw.print(prefix);
    797                 pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
    798                         + " (timestamp=" + entry.mTimestamp + ")"
    799                         + " reason="
    800                         + InputMethodClient.getStartInputReason(entry.mStartInputReason)
    801                         + " restarting=" + entry.mRestarting);
    802 
    803                 pw.print(prefix);
    804                 pw.println(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
    805 
    806                 pw.print(prefix);
    807                 pw.println(" targetWin=" + entry.mTargetWindowString
    808                         + " [" + entry.mEditorInfo.packageName + "]"
    809                         + " clientBindSeq=" + entry.mClientBindSequenceNumber);
    810 
    811                 pw.print(prefix);
    812                 pw.println(" softInputMode=" + InputMethodClient.softInputModeToString(
    813                                 entry.mTargetWindowSoftInputMode));
    814 
    815                 pw.print(prefix);
    816                 pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
    817                         + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
    818                         + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
    819                         + " fieldName=" + entry.mEditorInfo.fieldName
    820                         + " actionId=" + entry.mEditorInfo.actionId
    821                         + " actionLabel=" + entry.mEditorInfo.actionLabel);
    822             }
    823         }
    824     }
    825 
    826     @GuardedBy("mMethodMap")
    827     @NonNull
    828     private final StartInputHistory mStartInputHistory = new StartInputHistory();
    829 
    830     class SettingsObserver extends ContentObserver {
    831         int mUserId;
    832         boolean mRegistered = false;
    833         @NonNull
    834         String mLastEnabled = "";
    835 
    836         /**
    837          * <em>This constructor must be called within the lock.</em>
    838          */
    839         SettingsObserver(Handler handler) {
    840             super(handler);
    841         }
    842 
    843         public void registerContentObserverLocked(@UserIdInt int userId) {
    844             if (mRegistered && mUserId == userId) {
    845                 return;
    846             }
    847             ContentResolver resolver = mContext.getContentResolver();
    848             if (mRegistered) {
    849                 mContext.getContentResolver().unregisterContentObserver(this);
    850                 mRegistered = false;
    851             }
    852             if (mUserId != userId) {
    853                 mLastEnabled = "";
    854                 mUserId = userId;
    855             }
    856             resolver.registerContentObserver(Settings.Secure.getUriFor(
    857                     Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
    858             resolver.registerContentObserver(Settings.Secure.getUriFor(
    859                     Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
    860             resolver.registerContentObserver(Settings.Secure.getUriFor(
    861                     Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
    862             resolver.registerContentObserver(Settings.Secure.getUriFor(
    863                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
    864             resolver.registerContentObserver(Settings.Secure.getUriFor(
    865                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
    866             mRegistered = true;
    867         }
    868 
    869         @Override public void onChange(boolean selfChange, Uri uri) {
    870             final Uri showImeUri = Settings.Secure.getUriFor(
    871                     Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
    872             final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
    873                     Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
    874             synchronized (mMethodMap) {
    875                 if (showImeUri.equals(uri)) {
    876                     updateKeyboardFromSettingsLocked();
    877                 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
    878                     mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
    879                             mContext.getContentResolver(),
    880                             Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
    881                             0, mUserId) == 1;
    882                     if (mAccessibilityRequestingNoSoftKeyboard) {
    883                         final boolean showRequested = mShowRequested;
    884                         hideCurrentInputLocked(0, null);
    885                         mShowRequested = showRequested;
    886                     } else if (mShowRequested) {
    887                         showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
    888                     }
    889                 } else {
    890                     boolean enabledChanged = false;
    891                     String newEnabled = mSettings.getEnabledInputMethodsStr();
    892                     if (!mLastEnabled.equals(newEnabled)) {
    893                         mLastEnabled = newEnabled;
    894                         enabledChanged = true;
    895                     }
    896                     updateInputMethodsFromSettingsLocked(enabledChanged);
    897                 }
    898             }
    899         }
    900 
    901         @Override
    902         public String toString() {
    903             return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
    904                     + " mLastEnabled=" + mLastEnabled + "}";
    905         }
    906     }
    907 
    908     class ImmsBroadcastReceiver extends BroadcastReceiver {
    909         @Override
    910         public void onReceive(Context context, Intent intent) {
    911             final String action = intent.getAction();
    912             if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
    913                 hideInputMethodMenu();
    914                 // No need to update mIsInteractive
    915                 return;
    916             } else if (Intent.ACTION_USER_ADDED.equals(action)
    917                     || Intent.ACTION_USER_REMOVED.equals(action)) {
    918                 updateCurrentProfileIds();
    919                 return;
    920             } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
    921                 onActionLocaleChanged();
    922             } else if (ACTION_SHOW_INPUT_METHOD_PICKER.equals(action)) {
    923                 // ACTION_SHOW_INPUT_METHOD_PICKER action is a protected-broadcast and it is
    924                 // guaranteed to be send only from the system, so that there is no need for extra
    925                 // security check such as
    926                 // {@link #canShowInputMethodPickerLocked(IInputMethodClient)}.
    927                 mHandler.obtainMessage(
    928                         MSG_SHOW_IM_SUBTYPE_PICKER,
    929                         InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES,
    930                         0 /* arg2 */)
    931                         .sendToTarget();
    932             } else {
    933                 Slog.w(TAG, "Unexpected intent " + intent);
    934             }
    935         }
    936     }
    937 
    938     /**
    939      * Start a VR InputMethod that matches IME with package name of {@param component}.
    940      * Note: This method is called from {@link android.app.VrManager}.
    941      */
    942     private void startVrInputMethodNoCheck(@Nullable ComponentName component) {
    943         if (component == null) {
    944             // clear the current VR-only IME (if any) and restore normal IME.
    945             restoreNonVrImeFromSettingsNoCheck();
    946             return;
    947         }
    948 
    949         synchronized (mMethodMap) {
    950             String packageName = component.getPackageName();
    951             for (InputMethodInfo info : mMethodList) {
    952                 if (TextUtils.equals(info.getPackageName(), packageName) && info.isVrOnly()) {
    953                     // set this is as current inputMethod without updating settings.
    954                     setInputMethodEnabledLocked(info.getId(), true);
    955                     setInputMethodLocked(info.getId(), NOT_A_SUBTYPE_ID);
    956                     break;
    957                 }
    958             }
    959         }
    960     }
    961 
    962     /**
    963      * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
    964      *
    965      * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
    966      * the users. We should ignore this event if this is about any background user's locale.</p>
    967      *
    968      * <p>Caution: This method must not be called when system is not ready.</p>
    969      */
    970     void onActionLocaleChanged() {
    971         synchronized (mMethodMap) {
    972             final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
    973             if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
    974                 return;
    975             }
    976             buildInputMethodListLocked(true);
    977             // If the locale is changed, needs to reset the default ime
    978             resetDefaultImeLocked(mContext);
    979             updateFromSettingsLocked(true);
    980             mLastSystemLocales = possibleNewLocale;
    981         }
    982     }
    983 
    984     final class MyPackageMonitor extends PackageMonitor {
    985         /**
    986          * Package names that are known to contain {@link InputMethodService}.
    987          *
    988          * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
    989          * all the packages when the user is unlocked, and direct-boot awareness will not be changed
    990          * dynamically unless the entire package is updated, which also always triggers package
    991          * rescanning.</p>
    992          */
    993         @GuardedBy("mMethodMap")
    994         final private ArraySet<String> mKnownImePackageNames = new ArraySet<>();
    995 
    996         /**
    997          * Packages that are appeared, disappeared, or modified for whatever reason.
    998          *
    999          * <p>Note: For now we intentionally use {@link ArrayList} instead of {@link ArraySet}
   1000          * because 1) the number of elements is almost always 1 or so, and 2) we do not care
   1001          * duplicate elements for our use case.</p>
   1002          *
   1003          * <p>This object must be accessed only from callback methods in {@link PackageMonitor},
   1004          * which should be bound to {@link #getRegisteredHandler()}.</p>
   1005          */
   1006         private final ArrayList<String> mChangedPackages = new ArrayList<>();
   1007 
   1008         /**
   1009          * {@code true} if one or more packages that contain {@link InputMethodService} appeared.
   1010          *
   1011          * <p>This field must be accessed only from callback methods in {@link PackageMonitor},
   1012          * which should be bound to {@link #getRegisteredHandler()}.</p>
   1013          */
   1014         private boolean mImePackageAppeared = false;
   1015 
   1016         @GuardedBy("mMethodMap")
   1017         void clearKnownImePackageNamesLocked() {
   1018             mKnownImePackageNames.clear();
   1019         }
   1020 
   1021         @GuardedBy("mMethodMap")
   1022         final void addKnownImePackageNameLocked(@NonNull String packageName) {
   1023             mKnownImePackageNames.add(packageName);
   1024         }
   1025 
   1026         @GuardedBy("mMethodMap")
   1027         private boolean isChangingPackagesOfCurrentUserLocked() {
   1028             final int userId = getChangingUserId();
   1029             final boolean retval = userId == mSettings.getCurrentUserId();
   1030             if (DEBUG) {
   1031                 if (!retval) {
   1032                     Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
   1033                 }
   1034             }
   1035             return retval;
   1036         }
   1037 
   1038         @Override
   1039         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
   1040             synchronized (mMethodMap) {
   1041                 if (!isChangingPackagesOfCurrentUserLocked()) {
   1042                     return false;
   1043                 }
   1044                 String curInputMethodId = mSettings.getSelectedInputMethod();
   1045                 final int N = mMethodList.size();
   1046                 if (curInputMethodId != null) {
   1047                     for (int i=0; i<N; i++) {
   1048                         InputMethodInfo imi = mMethodList.get(i);
   1049                         if (imi.getId().equals(curInputMethodId)) {
   1050                             for (String pkg : packages) {
   1051                                 if (imi.getPackageName().equals(pkg)) {
   1052                                     if (!doit) {
   1053                                         return true;
   1054                                     }
   1055                                     resetSelectedInputMethodAndSubtypeLocked("");
   1056                                     chooseNewDefaultIMELocked();
   1057                                     return true;
   1058                                 }
   1059                             }
   1060                         }
   1061                     }
   1062                 }
   1063             }
   1064             return false;
   1065         }
   1066 
   1067         @Override
   1068         public void onBeginPackageChanges() {
   1069             clearPackageChangeState();
   1070         }
   1071 
   1072         @Override
   1073         public void onPackageAppeared(String packageName, int reason) {
   1074             if (!mImePackageAppeared) {
   1075                 final PackageManager pm = mContext.getPackageManager();
   1076                 final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
   1077                         new Intent(InputMethod.SERVICE_INTERFACE).setPackage(packageName),
   1078                         getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
   1079                         getChangingUserId());
   1080                 // No need to lock this because we access it only on getRegisteredHandler().
   1081                 if (!services.isEmpty()) {
   1082                     mImePackageAppeared = true;
   1083                 }
   1084             }
   1085             // No need to lock this because we access it only on getRegisteredHandler().
   1086             mChangedPackages.add(packageName);
   1087         }
   1088 
   1089         @Override
   1090         public void onPackageDisappeared(String packageName, int reason) {
   1091             // No need to lock this because we access it only on getRegisteredHandler().
   1092             mChangedPackages.add(packageName);
   1093         }
   1094 
   1095         @Override
   1096         public void onPackageModified(String packageName) {
   1097             // No need to lock this because we access it only on getRegisteredHandler().
   1098             mChangedPackages.add(packageName);
   1099         }
   1100 
   1101         @Override
   1102         public void onPackagesSuspended(String[] packages) {
   1103             // No need to lock this because we access it only on getRegisteredHandler().
   1104             for (String packageName : packages) {
   1105                 mChangedPackages.add(packageName);
   1106             }
   1107         }
   1108 
   1109         @Override
   1110         public void onPackagesUnsuspended(String[] packages) {
   1111             // No need to lock this because we access it only on getRegisteredHandler().
   1112             for (String packageName : packages) {
   1113                 mChangedPackages.add(packageName);
   1114             }
   1115         }
   1116 
   1117         @Override
   1118         public void onFinishPackageChanges() {
   1119             onFinishPackageChangesInternal();
   1120             clearPackageChangeState();
   1121         }
   1122 
   1123         private void clearPackageChangeState() {
   1124             // No need to lock them because we access these fields only on getRegisteredHandler().
   1125             mChangedPackages.clear();
   1126             mImePackageAppeared = false;
   1127         }
   1128 
   1129         @GuardedBy("mMethodMap")
   1130         private boolean shouldRebuildInputMethodListLocked() {
   1131             // This method is guaranteed to be called only by getRegisteredHandler().
   1132 
   1133             // If there is any new package that contains at least one IME, then rebuilt the list
   1134             // of IMEs.
   1135             if (mImePackageAppeared) {
   1136                 return true;
   1137             }
   1138 
   1139             // Otherwise, check if mKnownImePackageNames and mChangedPackages have any intersection.
   1140             // TODO: Consider to create a utility method to do the following test. List.retainAll()
   1141             // is an option, but it may still do some extra operations that we do not need here.
   1142             final int N = mChangedPackages.size();
   1143             for (int i = 0; i < N; ++i) {
   1144                 final String packageName = mChangedPackages.get(i);
   1145                 if (mKnownImePackageNames.contains(packageName)) {
   1146                     return true;
   1147                 }
   1148             }
   1149             return false;
   1150         }
   1151 
   1152         private void onFinishPackageChangesInternal() {
   1153             synchronized (mMethodMap) {
   1154                 if (!isChangingPackagesOfCurrentUserLocked()) {
   1155                     return;
   1156                 }
   1157                 if (!shouldRebuildInputMethodListLocked()) {
   1158                     return;
   1159                 }
   1160 
   1161                 InputMethodInfo curIm = null;
   1162                 String curInputMethodId = mSettings.getSelectedInputMethod();
   1163                 final int N = mMethodList.size();
   1164                 if (curInputMethodId != null) {
   1165                     for (int i=0; i<N; i++) {
   1166                         InputMethodInfo imi = mMethodList.get(i);
   1167                         final String imiId = imi.getId();
   1168                         if (imiId.equals(curInputMethodId)) {
   1169                             curIm = imi;
   1170                         }
   1171 
   1172                         int change = isPackageDisappearing(imi.getPackageName());
   1173                         if (isPackageModified(imi.getPackageName())) {
   1174                             mFileManager.deleteAllInputMethodSubtypes(imiId);
   1175                         }
   1176                         if (change == PACKAGE_TEMPORARY_CHANGE
   1177                                 || change == PACKAGE_PERMANENT_CHANGE) {
   1178                             Slog.i(TAG, "Input method uninstalled, disabling: "
   1179                                     + imi.getComponent());
   1180                             setInputMethodEnabledLocked(imi.getId(), false);
   1181                         }
   1182                     }
   1183                 }
   1184 
   1185                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
   1186 
   1187                 boolean changed = false;
   1188 
   1189                 if (curIm != null) {
   1190                     int change = isPackageDisappearing(curIm.getPackageName());
   1191                     if (change == PACKAGE_TEMPORARY_CHANGE
   1192                             || change == PACKAGE_PERMANENT_CHANGE) {
   1193                         ServiceInfo si = null;
   1194                         try {
   1195                             si = mIPackageManager.getServiceInfo(
   1196                                     curIm.getComponent(), 0, mSettings.getCurrentUserId());
   1197                         } catch (RemoteException ex) {
   1198                         }
   1199                         if (si == null) {
   1200                             // Uh oh, current input method is no longer around!
   1201                             // Pick another one...
   1202                             Slog.i(TAG, "Current input method removed: " + curInputMethodId);
   1203                             updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
   1204                             if (!chooseNewDefaultIMELocked()) {
   1205                                 changed = true;
   1206                                 curIm = null;
   1207                                 Slog.i(TAG, "Unsetting current input method");
   1208                                 resetSelectedInputMethodAndSubtypeLocked("");
   1209                             }
   1210                         }
   1211                     }
   1212                 }
   1213 
   1214                 if (curIm == null) {
   1215                     // We currently don't have a default input method... is
   1216                     // one now available?
   1217                     changed = chooseNewDefaultIMELocked();
   1218                 } else if (!changed && isPackageModified(curIm.getPackageName())) {
   1219                     // Even if the current input method is still available, mCurrentSubtype could
   1220                     // be obsolete when the package is modified in practice.
   1221                     changed = true;
   1222                 }
   1223 
   1224                 if (changed) {
   1225                     updateFromSettingsLocked(false);
   1226                 }
   1227             }
   1228         }
   1229     }
   1230 
   1231     private static final class MethodCallback extends IInputSessionCallback.Stub {
   1232         private final InputMethodManagerService mParentIMMS;
   1233         private final IInputMethod mMethod;
   1234         private final InputChannel mChannel;
   1235 
   1236         MethodCallback(InputMethodManagerService imms, IInputMethod method,
   1237                 InputChannel channel) {
   1238             mParentIMMS = imms;
   1239             mMethod = method;
   1240             mChannel = channel;
   1241         }
   1242 
   1243         @Override
   1244         public void sessionCreated(IInputMethodSession session) {
   1245             long ident = Binder.clearCallingIdentity();
   1246             try {
   1247                 mParentIMMS.onSessionCreated(mMethod, session, mChannel);
   1248             } finally {
   1249                 Binder.restoreCallingIdentity(ident);
   1250             }
   1251         }
   1252     }
   1253 
   1254     private class HardKeyboardListener
   1255             implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
   1256         @Override
   1257         public void onHardKeyboardStatusChange(boolean available) {
   1258             mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
   1259                         available ? 1 : 0));
   1260         }
   1261 
   1262         public void handleHardKeyboardStatusChange(boolean available) {
   1263             if (DEBUG) {
   1264                 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
   1265             }
   1266             synchronized(mMethodMap) {
   1267                 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
   1268                         && mSwitchingDialog.isShowing()) {
   1269                     mSwitchingDialogTitleView.findViewById(
   1270                             com.android.internal.R.id.hard_keyboard_section).setVisibility(
   1271                                     available ? View.VISIBLE : View.GONE);
   1272                 }
   1273             }
   1274         }
   1275     }
   1276 
   1277     public static final class Lifecycle extends SystemService {
   1278         private InputMethodManagerService mService;
   1279 
   1280         public Lifecycle(Context context) {
   1281             super(context);
   1282             mService = new InputMethodManagerService(context);
   1283         }
   1284 
   1285         @Override
   1286         public void onStart() {
   1287             LocalServices.addService(InputMethodManagerInternal.class,
   1288                     new LocalServiceImpl(mService.mHandler));
   1289             publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
   1290         }
   1291 
   1292         @Override
   1293         public void onSwitchUser(@UserIdInt int userHandle) {
   1294             // Called on ActivityManager thread.
   1295             // TODO: Dispatch this to a worker thread as needed.
   1296             mService.onSwitchUser(userHandle);
   1297         }
   1298 
   1299         @Override
   1300         public void onBootPhase(int phase) {
   1301             // Called on ActivityManager thread.
   1302             // TODO: Dispatch this to a worker thread as needed.
   1303             if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
   1304                 StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
   1305                         .getService(Context.STATUS_BAR_SERVICE);
   1306                 mService.systemRunning(statusBarService);
   1307             }
   1308         }
   1309 
   1310         @Override
   1311         public void onUnlockUser(final @UserIdInt int userHandle) {
   1312             // Called on ActivityManager thread.
   1313             mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
   1314                     userHandle /* arg1 */, 0 /* arg2 */));
   1315         }
   1316     }
   1317 
   1318     void onUnlockUser(@UserIdInt int userId) {
   1319         synchronized(mMethodMap) {
   1320             final int currentUserId = mSettings.getCurrentUserId();
   1321             if (DEBUG) {
   1322                 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
   1323             }
   1324             if (userId != currentUserId) {
   1325                 return;
   1326             }
   1327             mSettings.switchCurrentUser(currentUserId, !mSystemReady);
   1328             if (mSystemReady) {
   1329                 // We need to rebuild IMEs.
   1330                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
   1331                 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
   1332             }
   1333         }
   1334     }
   1335 
   1336     void onSwitchUser(@UserIdInt int userId) {
   1337         synchronized (mMethodMap) {
   1338             switchUserLocked(userId);
   1339         }
   1340     }
   1341 
   1342     public InputMethodManagerService(Context context) {
   1343         mIPackageManager = AppGlobals.getPackageManager();
   1344         mContext = context;
   1345         mRes = context.getResources();
   1346         mHandler = new Handler(this);
   1347         // Note: SettingsObserver doesn't register observers in its constructor.
   1348         mSettingsObserver = new SettingsObserver(mHandler);
   1349         mIWindowManager = IWindowManager.Stub.asInterface(
   1350                 ServiceManager.getService(Context.WINDOW_SERVICE));
   1351         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
   1352         mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
   1353             @Override
   1354             public void executeMessage(Message msg) {
   1355                 handleMessage(msg);
   1356             }
   1357         }, true /*asyncHandler*/);
   1358         mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
   1359         mUserManager = mContext.getSystemService(UserManager.class);
   1360         mHardKeyboardListener = new HardKeyboardListener();
   1361         mHasFeature = context.getPackageManager().hasSystemFeature(
   1362                 PackageManager.FEATURE_INPUT_METHODS);
   1363         mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
   1364         mHardKeyboardBehavior = mContext.getResources().getInteger(
   1365                 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
   1366 
   1367         Bundle extras = new Bundle();
   1368         extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
   1369         @ColorInt final int accentColor = mContext.getColor(
   1370                 com.android.internal.R.color.system_notification_accent_color);
   1371         mImeSwitcherNotification =
   1372                 new Notification.Builder(mContext, SystemNotificationChannels.VIRTUAL_KEYBOARD)
   1373                         .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
   1374                         .setWhen(0)
   1375                         .setOngoing(true)
   1376                         .addExtras(extras)
   1377                         .setCategory(Notification.CATEGORY_SYSTEM)
   1378                         .setColor(accentColor);
   1379 
   1380         Intent intent = new Intent(ACTION_SHOW_INPUT_METHOD_PICKER)
   1381                 .setPackage(mContext.getPackageName());
   1382         mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
   1383 
   1384         mShowOngoingImeSwitcherForPhones = false;
   1385 
   1386         mNotificationShown = false;
   1387         int userId = 0;
   1388         try {
   1389             userId = ActivityManager.getService().getCurrentUser().id;
   1390         } catch (RemoteException e) {
   1391             Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
   1392         }
   1393 
   1394         // mSettings should be created before buildInputMethodListLocked
   1395         mSettings = new InputMethodSettings(
   1396                 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
   1397 
   1398         updateCurrentProfileIds();
   1399         mFileManager = new InputMethodFileManager(mMethodMap, userId);
   1400         mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
   1401                 mSettings, context);
   1402         // Register VR-state listener.
   1403         IVrManager vrManager = (IVrManager) ServiceManager.getService(Context.VR_SERVICE);
   1404         if (vrManager != null) {
   1405             try {
   1406                 vrManager.registerListener(mVrStateCallbacks);
   1407             } catch (RemoteException e) {
   1408                 Slog.e(TAG, "Failed to register VR mode state listener.");
   1409             }
   1410         }
   1411     }
   1412 
   1413     private void resetDefaultImeLocked(Context context) {
   1414         // Do not reset the default (current) IME when it is a 3rd-party IME
   1415         if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
   1416             return;
   1417         }
   1418         final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
   1419                 context, mSettings.getEnabledInputMethodListLocked());
   1420         if (suitableImes.isEmpty()) {
   1421             Slog.i(TAG, "No default found");
   1422             return;
   1423         }
   1424         final InputMethodInfo defIm = suitableImes.get(0);
   1425         if (DEBUG) {
   1426             Slog.i(TAG, "Default found, using " + defIm.getId());
   1427         }
   1428         setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
   1429     }
   1430 
   1431     @GuardedBy("mMethodMap")
   1432     private void switchUserLocked(int newUserId) {
   1433         if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
   1434                 + " currentUserId=" + mSettings.getCurrentUserId());
   1435 
   1436         // ContentObserver should be registered again when the user is changed
   1437         mSettingsObserver.registerContentObserverLocked(newUserId);
   1438 
   1439         // If the system is not ready or the device is not yed unlocked by the user, then we use
   1440         // copy-on-write settings.
   1441         final boolean useCopyOnWriteSettings =
   1442                 !mSystemReady || !mUserManager.isUserUnlockingOrUnlocked(newUserId);
   1443         mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
   1444         updateCurrentProfileIds();
   1445         // InputMethodFileManager should be reset when the user is changed
   1446         mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
   1447         final String defaultImiId = mSettings.getSelectedInputMethod();
   1448 
   1449         if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
   1450                 + " defaultImiId=" + defaultImiId);
   1451 
   1452         // For secondary users, the list of enabled IMEs may not have been updated since the
   1453         // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
   1454         // not be empty even if the IME has been uninstalled by the primary user.
   1455         // Even in such cases, IMMS works fine because it will find the most applicable
   1456         // IME for that user.
   1457         final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
   1458         mLastSystemLocales = mRes.getConfiguration().getLocales();
   1459 
   1460         // TODO: Is it really possible that switchUserLocked() happens before system ready?
   1461         if (mSystemReady) {
   1462             hideCurrentInputLocked(0, null);
   1463             resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_USER);
   1464             buildInputMethodListLocked(initialUserSwitch);
   1465             if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
   1466                 // This is the first time of the user switch and
   1467                 // set the current ime to the proper one.
   1468                 resetDefaultImeLocked(mContext);
   1469             }
   1470             updateFromSettingsLocked(true);
   1471             try {
   1472                 startInputInnerLocked();
   1473             } catch (RuntimeException e) {
   1474                 Slog.w(TAG, "Unexpected exception", e);
   1475             }
   1476         }
   1477 
   1478         if (initialUserSwitch) {
   1479             InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
   1480                     mSettings.getEnabledInputMethodListLocked(), newUserId,
   1481                     mContext.getBasePackageName());
   1482         }
   1483 
   1484         if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
   1485                 + " selectedIme=" + mSettings.getSelectedInputMethod());
   1486     }
   1487 
   1488     void updateCurrentProfileIds() {
   1489         mSettings.setCurrentProfileIds(
   1490                 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
   1491     }
   1492 
   1493     @Override
   1494     public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
   1495             throws RemoteException {
   1496         try {
   1497             return super.onTransact(code, data, reply, flags);
   1498         } catch (RuntimeException e) {
   1499             // The input method manager only throws security exceptions, so let's
   1500             // log all others.
   1501             if (!(e instanceof SecurityException)) {
   1502                 Slog.wtf(TAG, "Input Method Manager Crash", e);
   1503             }
   1504             throw e;
   1505         }
   1506     }
   1507 
   1508     public void systemRunning(StatusBarManagerService statusBar) {
   1509         synchronized (mMethodMap) {
   1510             if (DEBUG) {
   1511                 Slog.d(TAG, "--- systemReady");
   1512             }
   1513             if (!mSystemReady) {
   1514                 mSystemReady = true;
   1515                 mLastSystemLocales = mRes.getConfiguration().getLocales();
   1516                 final int currentUserId = mSettings.getCurrentUserId();
   1517                 mSettings.switchCurrentUser(currentUserId,
   1518                         !mUserManager.isUserUnlockingOrUnlocked(currentUserId));
   1519                 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
   1520                 mNotificationManager = mContext.getSystemService(NotificationManager.class);
   1521                 mStatusBar = statusBar;
   1522                 if (mStatusBar != null) {
   1523                     mStatusBar.setIconVisibility(mSlotIme, false);
   1524                 }
   1525                 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
   1526                 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
   1527                         com.android.internal.R.bool.show_ongoing_ime_switcher);
   1528                 if (mShowOngoingImeSwitcherForPhones) {
   1529                     mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
   1530                             mHardKeyboardListener);
   1531                 }
   1532 
   1533                 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
   1534                 mSettingsObserver.registerContentObserverLocked(currentUserId);
   1535 
   1536                 final IntentFilter broadcastFilter = new IntentFilter();
   1537                 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
   1538                 broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
   1539                 broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
   1540                 broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
   1541                 broadcastFilter.addAction(ACTION_SHOW_INPUT_METHOD_PICKER);
   1542                 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
   1543 
   1544                 final String defaultImiId = mSettings.getSelectedInputMethod();
   1545                 final boolean imeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
   1546                 buildInputMethodListLocked(!imeSelectedOnBoot /* resetDefaultEnabledIme */);
   1547                 resetDefaultImeLocked(mContext);
   1548                 updateFromSettingsLocked(true);
   1549                 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
   1550                         mSettings.getEnabledInputMethodListLocked(), currentUserId,
   1551                         mContext.getBasePackageName());
   1552 
   1553                 try {
   1554                     startInputInnerLocked();
   1555                 } catch (RuntimeException e) {
   1556                     Slog.w(TAG, "Unexpected exception", e);
   1557                 }
   1558             }
   1559         }
   1560     }
   1561 
   1562     // ---------------------------------------------------------------------------------------
   1563     // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
   1564     // 1) it comes from the system process
   1565     // 2) the calling process' user id is identical to the current user id IMMS thinks.
   1566     private boolean calledFromValidUser() {
   1567         final int uid = Binder.getCallingUid();
   1568         final int userId = UserHandle.getUserId(uid);
   1569         if (DEBUG) {
   1570             Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
   1571                     + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
   1572                     + " calling userId = " + userId + ", foreground user id = "
   1573                     + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
   1574                     + InputMethodUtils.getApiCallStack());
   1575         }
   1576         if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
   1577             return true;
   1578         }
   1579 
   1580         // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
   1581         // foreground user, not for the user of that process. Accordingly InputMethodManagerService
   1582         // must not manage background users' states in any functions.
   1583         // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
   1584         // by a token.
   1585         if (mContext.checkCallingOrSelfPermission(
   1586                 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
   1587                         == PackageManager.PERMISSION_GRANTED) {
   1588             if (DEBUG) {
   1589                 Slog.d(TAG, "--- Access granted because the calling process has "
   1590                         + "the INTERACT_ACROSS_USERS_FULL permission");
   1591             }
   1592             return true;
   1593         }
   1594         // TODO(b/34886274): The semantics of this verification is actually not well-defined.
   1595         Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
   1596                 + Debug.getCallers(10));
   1597         return false;
   1598     }
   1599 
   1600 
   1601     /**
   1602      * Returns true iff the caller is identified to be the current input method with the token.
   1603      * @param token The window token given to the input method when it was started.
   1604      * @return true if and only if non-null valid token is specified.
   1605      */
   1606     private boolean calledWithValidToken(@Nullable IBinder token) {
   1607         if (token == null && Binder.getCallingPid() == Process.myPid()) {
   1608             if (DEBUG) {
   1609                 // TODO(b/34851776): Basically it's the caller's fault if we reach here.
   1610                 Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
   1611             }
   1612             return false;
   1613         }
   1614         if (token == null || token != mCurToken) {
   1615             // TODO(b/34886274): The semantics of this verification is actually not well-defined.
   1616             Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
   1617                     + " uid:" + Binder.getCallingUid() + " token:" + token);
   1618             return false;
   1619         }
   1620         return true;
   1621     }
   1622 
   1623     @GuardedBy("mMethodMap")
   1624     private boolean bindCurrentInputMethodServiceLocked(
   1625             Intent service, ServiceConnection conn, int flags) {
   1626         if (service == null || conn == null) {
   1627             Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
   1628             return false;
   1629         }
   1630         if (mBindInstantServiceAllowed) {
   1631             flags |= Context.BIND_ALLOW_INSTANT;
   1632         }
   1633         return mContext.bindServiceAsUser(service, conn, flags,
   1634                 new UserHandle(mSettings.getCurrentUserId()));
   1635     }
   1636 
   1637     @Override
   1638     public List<InputMethodInfo> getInputMethodList() {
   1639         return getInputMethodList(false /* isVrOnly */);
   1640     }
   1641 
   1642     public List<InputMethodInfo> getVrInputMethodList() {
   1643         return getInputMethodList(true /* isVrOnly */);
   1644     }
   1645 
   1646     private List<InputMethodInfo> getInputMethodList(final boolean isVrOnly) {
   1647         // TODO: Make this work even for non-current users?
   1648         if (!calledFromValidUser()) {
   1649             return Collections.emptyList();
   1650         }
   1651         synchronized (mMethodMap) {
   1652             ArrayList<InputMethodInfo> methodList = new ArrayList<>();
   1653             for (InputMethodInfo info : mMethodList) {
   1654 
   1655                 if (info.isVrOnly() == isVrOnly) {
   1656                     methodList.add(info);
   1657                 }
   1658             }
   1659             return methodList;
   1660         }
   1661     }
   1662 
   1663     @Override
   1664     public List<InputMethodInfo> getEnabledInputMethodList() {
   1665         // TODO: Make this work even for non-current users?
   1666         if (!calledFromValidUser()) {
   1667             return Collections.emptyList();
   1668         }
   1669         synchronized (mMethodMap) {
   1670             return mSettings.getEnabledInputMethodListLocked();
   1671         }
   1672     }
   1673 
   1674     /**
   1675      * @param imiId if null, returns enabled subtypes for the current imi
   1676      * @return enabled subtypes of the specified imi
   1677      */
   1678     @Override
   1679     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
   1680             boolean allowsImplicitlySelectedSubtypes) {
   1681         // TODO: Make this work even for non-current users?
   1682         if (!calledFromValidUser()) {
   1683             return Collections.emptyList();
   1684         }
   1685         synchronized (mMethodMap) {
   1686             final InputMethodInfo imi;
   1687             if (imiId == null && mCurMethodId != null) {
   1688                 imi = mMethodMap.get(mCurMethodId);
   1689             } else {
   1690                 imi = mMethodMap.get(imiId);
   1691             }
   1692             if (imi == null) {
   1693                 return Collections.emptyList();
   1694             }
   1695             return mSettings.getEnabledInputMethodSubtypeListLocked(
   1696                     mContext, imi, allowsImplicitlySelectedSubtypes);
   1697         }
   1698     }
   1699 
   1700     @Override
   1701     public void addClient(IInputMethodClient client,
   1702             IInputContext inputContext, int uid, int pid) {
   1703         if (!calledFromValidUser()) {
   1704             return;
   1705         }
   1706         synchronized (mMethodMap) {
   1707             mClients.put(client.asBinder(), new ClientState(client,
   1708                     inputContext, uid, pid));
   1709         }
   1710     }
   1711 
   1712     @Override
   1713     public void removeClient(IInputMethodClient client) {
   1714         if (!calledFromValidUser()) {
   1715             return;
   1716         }
   1717         synchronized (mMethodMap) {
   1718             ClientState cs = mClients.remove(client.asBinder());
   1719             if (cs != null) {
   1720                 clearClientSessionLocked(cs);
   1721                 if (mCurClient == cs) {
   1722                     if (mBoundToMethod) {
   1723                         mBoundToMethod = false;
   1724                         if (mCurMethod != null) {
   1725                             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
   1726                                     MSG_UNBIND_INPUT, mCurMethod));
   1727                         }
   1728                     }
   1729                     mCurClient = null;
   1730                 }
   1731                 if (mCurFocusedWindowClient == cs) {
   1732                     mCurFocusedWindowClient = null;
   1733                 }
   1734             }
   1735         }
   1736     }
   1737 
   1738     void executeOrSendMessage(IInterface target, Message msg) {
   1739          if (target.asBinder() instanceof Binder) {
   1740              mCaller.sendMessage(msg);
   1741          } else {
   1742              handleMessage(msg);
   1743              msg.recycle();
   1744          }
   1745     }
   1746 
   1747     void unbindCurrentClientLocked(
   1748             /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
   1749         if (mCurClient != null) {
   1750             if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
   1751                     + mCurClient.client.asBinder());
   1752             if (mBoundToMethod) {
   1753                 mBoundToMethod = false;
   1754                 if (mCurMethod != null) {
   1755                     executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
   1756                             MSG_UNBIND_INPUT, mCurMethod));
   1757                 }
   1758             }
   1759 
   1760             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
   1761                     MSG_SET_ACTIVE, 0, 0, mCurClient));
   1762             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
   1763                     MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
   1764             mCurClient.sessionRequested = false;
   1765             mCurClient = null;
   1766 
   1767             hideInputMethodMenuLocked();
   1768         }
   1769     }
   1770 
   1771     private int getImeShowFlags() {
   1772         int flags = 0;
   1773         if (mShowForced) {
   1774             flags |= InputMethod.SHOW_FORCED
   1775                     | InputMethod.SHOW_EXPLICIT;
   1776         } else if (mShowExplicitlyRequested) {
   1777             flags |= InputMethod.SHOW_EXPLICIT;
   1778         }
   1779         return flags;
   1780     }
   1781 
   1782     private int getAppShowFlags() {
   1783         int flags = 0;
   1784         if (mShowForced) {
   1785             flags |= InputMethodManager.SHOW_FORCED;
   1786         } else if (!mShowExplicitlyRequested) {
   1787             flags |= InputMethodManager.SHOW_IMPLICIT;
   1788         }
   1789         return flags;
   1790     }
   1791 
   1792     @GuardedBy("mMethodMap")
   1793     @NonNull
   1794     InputBindResult attachNewInputLocked(
   1795             /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
   1796         if (!mBoundToMethod) {
   1797             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
   1798                     MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
   1799             mBoundToMethod = true;
   1800         }
   1801 
   1802         final Binder startInputToken = new Binder();
   1803         final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
   1804                 !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
   1805                 mCurSeq);
   1806         mStartInputMap.put(startInputToken, info);
   1807         mStartInputHistory.addEntry(info);
   1808 
   1809         final SessionState session = mCurClient.curSession;
   1810         executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
   1811                 MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
   1812                 startInputToken, session, mCurInputContext, mCurAttribute));
   1813         if (mShowRequested) {
   1814             if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
   1815             showCurrentInputLocked(getAppShowFlags(), null);
   1816         }
   1817         return new InputBindResult(InputBindResult.ResultCode.SUCCESS_WITH_IME_SESSION,
   1818                 session.session, (session.channel != null ? session.channel.dup() : null),
   1819                 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
   1820     }
   1821 
   1822     @GuardedBy("mMethodMap")
   1823     @NonNull
   1824     InputBindResult startInputLocked(
   1825             /* @InputMethodClient.StartInputReason */ final int startInputReason,
   1826             IInputMethodClient client, IInputContext inputContext,
   1827             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
   1828             @Nullable EditorInfo attribute, int controlFlags) {
   1829         // If no method is currently selected, do nothing.
   1830         if (mCurMethodId == null) {
   1831             return InputBindResult.NO_IME;
   1832         }
   1833 
   1834         ClientState cs = mClients.get(client.asBinder());
   1835         if (cs == null) {
   1836             throw new IllegalArgumentException("unknown client "
   1837                     + client.asBinder());
   1838         }
   1839 
   1840         if (attribute == null) {
   1841             Slog.w(TAG, "Ignoring startInput with null EditorInfo."
   1842                     + " uid=" + cs.uid + " pid=" + cs.pid);
   1843             return InputBindResult.NULL_EDITOR_INFO;
   1844         }
   1845 
   1846         try {
   1847             if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
   1848                 // Check with the window manager to make sure this client actually
   1849                 // has a window with focus.  If not, reject.  This is thread safe
   1850                 // because if the focus changes some time before or after, the
   1851                 // next client receiving focus that has any interest in input will
   1852                 // be calling through here after that change happens.
   1853                 if (DEBUG) {
   1854                     Slog.w(TAG, "Starting input on non-focused client " + cs.client
   1855                             + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
   1856                 }
   1857                 return InputBindResult.NOT_IME_TARGET_WINDOW;
   1858             }
   1859         } catch (RemoteException e) {
   1860         }
   1861 
   1862         return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
   1863                 controlFlags, startInputReason);
   1864     }
   1865 
   1866     @GuardedBy("mMethodMap")
   1867     @NonNull
   1868     InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
   1869             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
   1870             @NonNull EditorInfo attribute, int controlFlags,
   1871             /* @InputMethodClient.StartInputReason */ final int startInputReason) {
   1872         // If no method is currently selected, do nothing.
   1873         if (mCurMethodId == null) {
   1874             return InputBindResult.NO_IME;
   1875         }
   1876 
   1877         if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
   1878                 attribute.packageName)) {
   1879             Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
   1880                     + " uid=" + cs.uid + " package=" + attribute.packageName);
   1881             return InputBindResult.INVALID_PACKAGE_NAME;
   1882         }
   1883 
   1884         if (mCurClient != cs) {
   1885             // Was the keyguard locked when switching over to the new client?
   1886             mCurClientInKeyguard = isKeyguardLocked();
   1887             // If the client is changing, we need to switch over to the new
   1888             // one.
   1889             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
   1890             if (DEBUG) Slog.v(TAG, "switching to client: client="
   1891                     + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
   1892 
   1893             // If the screen is on, inform the new client it is active
   1894             if (mIsInteractive) {
   1895                 executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
   1896                         MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
   1897             }
   1898         }
   1899 
   1900         // Bump up the sequence for this client and attach it.
   1901         mCurSeq++;
   1902         if (mCurSeq <= 0) mCurSeq = 1;
   1903         mCurClient = cs;
   1904         mCurInputContext = inputContext;
   1905         mCurInputContextMissingMethods = missingMethods;
   1906         mCurAttribute = attribute;
   1907 
   1908         // Check if the input method is changing.
   1909         if (mCurId != null && mCurId.equals(mCurMethodId)) {
   1910             if (cs.curSession != null) {
   1911                 // Fast case: if we are already connected to the input method,
   1912                 // then just return it.
   1913                 return attachNewInputLocked(startInputReason,
   1914                         (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
   1915             }
   1916             if (mHaveConnection) {
   1917                 if (mCurMethod != null) {
   1918                     // Return to client, and we will get back with it when
   1919                     // we have had a session made for it.
   1920                     requestClientSessionLocked(cs);
   1921                     return new InputBindResult(
   1922                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_SESSION,
   1923                             null, null, mCurId, mCurSeq,
   1924                             mCurUserActionNotificationSequenceNumber);
   1925                 } else if (SystemClock.uptimeMillis()
   1926                         < (mLastBindTime+TIME_TO_RECONNECT)) {
   1927                     // In this case we have connected to the service, but
   1928                     // don't yet have its interface.  If it hasn't been too
   1929                     // long since we did the connection, we'll return to
   1930                     // the client and wait to get the service interface so
   1931                     // we can report back.  If it has been too long, we want
   1932                     // to fall through so we can try a disconnect/reconnect
   1933                     // to see if we can get back in touch with the service.
   1934                     return new InputBindResult(
   1935                             InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
   1936                             null, null, mCurId, mCurSeq,
   1937                             mCurUserActionNotificationSequenceNumber);
   1938                 } else {
   1939                     EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
   1940                             mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
   1941                 }
   1942             }
   1943         }
   1944 
   1945         return startInputInnerLocked();
   1946     }
   1947 
   1948     InputBindResult startInputInnerLocked() {
   1949         if (mCurMethodId == null) {
   1950             return InputBindResult.NO_IME;
   1951         }
   1952 
   1953         if (!mSystemReady) {
   1954             // If the system is not yet ready, we shouldn't be running third
   1955             // party code.
   1956             return new InputBindResult(
   1957                     InputBindResult.ResultCode.ERROR_SYSTEM_NOT_READY,
   1958                     null, null, mCurMethodId, mCurSeq,
   1959                     mCurUserActionNotificationSequenceNumber);
   1960         }
   1961 
   1962         InputMethodInfo info = mMethodMap.get(mCurMethodId);
   1963         if (info == null) {
   1964             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
   1965         }
   1966 
   1967         unbindCurrentMethodLocked(true);
   1968 
   1969         mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
   1970         mCurIntent.setComponent(info.getComponent());
   1971         mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
   1972                 com.android.internal.R.string.input_method_binding_label);
   1973         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
   1974                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
   1975         if (bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS)) {
   1976             mLastBindTime = SystemClock.uptimeMillis();
   1977             mHaveConnection = true;
   1978             mCurId = info.getId();
   1979             mCurToken = new Binder();
   1980             try {
   1981                 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
   1982                 mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
   1983             } catch (RemoteException e) {
   1984             }
   1985             return new InputBindResult(
   1986                     InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
   1987                     null, null, mCurId, mCurSeq,
   1988                     mCurUserActionNotificationSequenceNumber);
   1989         }
   1990         mCurIntent = null;
   1991         Slog.w(TAG, "Failure connecting to input method service: " + mCurIntent);
   1992         return InputBindResult.IME_NOT_CONNECTED;
   1993     }
   1994 
   1995     @NonNull
   1996     private InputBindResult startInput(
   1997             /* @InputMethodClient.StartInputReason */ final int startInputReason,
   1998             IInputMethodClient client, IInputContext inputContext,
   1999             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
   2000             @Nullable EditorInfo attribute, int controlFlags) {
   2001         if (!calledFromValidUser()) {
   2002             return InputBindResult.INVALID_USER;
   2003         }
   2004         synchronized (mMethodMap) {
   2005             if (DEBUG) {
   2006                 Slog.v(TAG, "startInput: reason="
   2007                         + InputMethodClient.getStartInputReason(startInputReason)
   2008                         + " client = " + client.asBinder()
   2009                         + " inputContext=" + inputContext
   2010                         + " missingMethods="
   2011                         + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
   2012                         + " attribute=" + attribute
   2013                         + " controlFlags=#" + Integer.toHexString(controlFlags));
   2014             }
   2015             final long ident = Binder.clearCallingIdentity();
   2016             try {
   2017                 return startInputLocked(startInputReason, client, inputContext, missingMethods,
   2018                         attribute, controlFlags);
   2019             } finally {
   2020                 Binder.restoreCallingIdentity(ident);
   2021             }
   2022         }
   2023     }
   2024 
   2025     @Override
   2026     public void finishInput(IInputMethodClient client) {
   2027     }
   2028 
   2029     @Override
   2030     public void onServiceConnected(ComponentName name, IBinder service) {
   2031         synchronized (mMethodMap) {
   2032             if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
   2033                 mCurMethod = IInputMethod.Stub.asInterface(service);
   2034                 if (mCurToken == null) {
   2035                     Slog.w(TAG, "Service connected without a token!");
   2036                     unbindCurrentMethodLocked(false);
   2037                     return;
   2038                 }
   2039                 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
   2040                 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
   2041                         MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
   2042                 if (mCurClient != null) {
   2043                     clearClientSessionLocked(mCurClient);
   2044                     requestClientSessionLocked(mCurClient);
   2045                 }
   2046             }
   2047         }
   2048     }
   2049 
   2050     void onSessionCreated(IInputMethod method, IInputMethodSession session,
   2051             InputChannel channel) {
   2052         synchronized (mMethodMap) {
   2053             if (mCurMethod != null && method != null
   2054                     && mCurMethod.asBinder() == method.asBinder()) {
   2055                 if (mCurClient != null) {
   2056                     clearClientSessionLocked(mCurClient);
   2057                     mCurClient.curSession = new SessionState(mCurClient,
   2058                             method, session, channel);
   2059                     InputBindResult res = attachNewInputLocked(
   2060                             InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
   2061                     if (res.method != null) {
   2062                         executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
   2063                                 MSG_BIND_CLIENT, mCurClient.client, res));
   2064                     }
   2065                     return;
   2066                 }
   2067             }
   2068         }
   2069 
   2070         // Session abandoned.  Close its associated input channel.
   2071         channel.dispose();
   2072     }
   2073 
   2074     void unbindCurrentMethodLocked(boolean savePosition) {
   2075         if (mVisibleBound) {
   2076             mContext.unbindService(mVisibleConnection);
   2077             mVisibleBound = false;
   2078         }
   2079 
   2080         if (mHaveConnection) {
   2081             mContext.unbindService(this);
   2082             mHaveConnection = false;
   2083         }
   2084 
   2085         if (mCurToken != null) {
   2086             try {
   2087                 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
   2088                 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
   2089                     // The current IME is shown. Hence an IME switch (transition) is happening.
   2090                     mWindowManagerInternal.saveLastInputMethodWindowForTransition();
   2091                 }
   2092                 mIWindowManager.removeWindowToken(mCurToken, DEFAULT_DISPLAY);
   2093             } catch (RemoteException e) {
   2094             }
   2095             mCurToken = null;
   2096         }
   2097 
   2098         mCurId = null;
   2099         clearCurMethodLocked();
   2100     }
   2101 
   2102     void resetCurrentMethodAndClient(
   2103             /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
   2104         mCurMethodId = null;
   2105         unbindCurrentMethodLocked(false);
   2106         unbindCurrentClientLocked(unbindClientReason);
   2107     }
   2108 
   2109     void requestClientSessionLocked(ClientState cs) {
   2110         if (!cs.sessionRequested) {
   2111             if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
   2112             InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
   2113             cs.sessionRequested = true;
   2114             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
   2115                     MSG_CREATE_SESSION, mCurMethod, channels[1],
   2116                     new MethodCallback(this, mCurMethod, channels[0])));
   2117         }
   2118     }
   2119 
   2120     void clearClientSessionLocked(ClientState cs) {
   2121         finishSessionLocked(cs.curSession);
   2122         cs.curSession = null;
   2123         cs.sessionRequested = false;
   2124     }
   2125 
   2126     private void finishSessionLocked(SessionState sessionState) {
   2127         if (sessionState != null) {
   2128             if (sessionState.session != null) {
   2129                 try {
   2130                     sessionState.session.finishSession();
   2131                 } catch (RemoteException e) {
   2132                     Slog.w(TAG, "Session failed to close due to remote exception", e);
   2133                     updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
   2134                 }
   2135                 sessionState.session = null;
   2136             }
   2137             if (sessionState.channel != null) {
   2138                 sessionState.channel.dispose();
   2139                 sessionState.channel = null;
   2140             }
   2141         }
   2142     }
   2143 
   2144     void clearCurMethodLocked() {
   2145         if (mCurMethod != null) {
   2146             for (ClientState cs : mClients.values()) {
   2147                 clearClientSessionLocked(cs);
   2148             }
   2149 
   2150             finishSessionLocked(mEnabledSession);
   2151             mEnabledSession = null;
   2152             mCurMethod = null;
   2153         }
   2154         if (mStatusBar != null) {
   2155             mStatusBar.setIconVisibility(mSlotIme, false);
   2156         }
   2157         mInFullscreenMode = false;
   2158     }
   2159 
   2160     @Override
   2161     public void onServiceDisconnected(ComponentName name) {
   2162         // Note that mContext.unbindService(this) does not trigger this.  Hence if we are here the
   2163         // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed),
   2164         // which is irregular but can eventually happen for everyone just by continuing using the
   2165         // device.  Thus it is important to make sure that all the internal states are properly
   2166         // refreshed when this method is called back.  Running
   2167         //    adb install -r <APK that implements the current IME>
   2168         // would be a good way to trigger such a situation.
   2169         synchronized (mMethodMap) {
   2170             if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
   2171                     + " mCurIntent=" + mCurIntent);
   2172             if (mCurMethod != null && mCurIntent != null
   2173                     && name.equals(mCurIntent.getComponent())) {
   2174                 clearCurMethodLocked();
   2175                 // We consider this to be a new bind attempt, since the system
   2176                 // should now try to restart the service for us.
   2177                 mLastBindTime = SystemClock.uptimeMillis();
   2178                 mShowRequested = mInputShown;
   2179                 mInputShown = false;
   2180                 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_DISCONNECT_IME);
   2181             }
   2182         }
   2183     }
   2184 
   2185     @Override
   2186     public void updateStatusIcon(IBinder token, String packageName, int iconId) {
   2187         synchronized (mMethodMap) {
   2188             if (!calledWithValidToken(token)) {
   2189                 return;
   2190             }
   2191             final long ident = Binder.clearCallingIdentity();
   2192             try {
   2193                 if (iconId == 0) {
   2194                     if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
   2195                     if (mStatusBar != null) {
   2196                         mStatusBar.setIconVisibility(mSlotIme, false);
   2197                     }
   2198                 } else if (packageName != null) {
   2199                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
   2200                     CharSequence contentDescription = null;
   2201                     try {
   2202                         // Use PackageManager to load label
   2203                         final PackageManager packageManager = mContext.getPackageManager();
   2204                         contentDescription = packageManager.getApplicationLabel(
   2205                                 mIPackageManager.getApplicationInfo(packageName, 0,
   2206                                         mSettings.getCurrentUserId()));
   2207                     } catch (RemoteException e) {
   2208                         /* ignore */
   2209                     }
   2210                     if (mStatusBar != null) {
   2211                         mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
   2212                                 contentDescription  != null
   2213                                         ? contentDescription.toString() : null);
   2214                         mStatusBar.setIconVisibility(mSlotIme, true);
   2215                     }
   2216                 }
   2217             } finally {
   2218                 Binder.restoreCallingIdentity(ident);
   2219             }
   2220         }
   2221     }
   2222 
   2223     private boolean shouldShowImeSwitcherLocked(int visibility) {
   2224         if (!mShowOngoingImeSwitcherForPhones) return false;
   2225         if (mSwitchingDialog != null) return false;
   2226         if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
   2227                 && mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
   2228         if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
   2229         if (mWindowManagerInternal.isHardKeyboardAvailable()) {
   2230             if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
   2231                 // When physical keyboard is attached, we show the ime switcher (or notification if
   2232                 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
   2233                 // exists in the IME switcher dialog.  Might be OK to remove this condition once
   2234                 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
   2235                 return true;
   2236             }
   2237         } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
   2238             return false;
   2239         }
   2240 
   2241         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
   2242         final int N = imis.size();
   2243         if (N > 2) return true;
   2244         if (N < 1) return false;
   2245         int nonAuxCount = 0;
   2246         int auxCount = 0;
   2247         InputMethodSubtype nonAuxSubtype = null;
   2248         InputMethodSubtype auxSubtype = null;
   2249         for(int i = 0; i < N; ++i) {
   2250             final InputMethodInfo imi = imis.get(i);
   2251             final List<InputMethodSubtype> subtypes =
   2252                     mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
   2253             final int subtypeCount = subtypes.size();
   2254             if (subtypeCount == 0) {
   2255                 ++nonAuxCount;
   2256             } else {
   2257                 for (int j = 0; j < subtypeCount; ++j) {
   2258                     final InputMethodSubtype subtype = subtypes.get(j);
   2259                     if (!subtype.isAuxiliary()) {
   2260                         ++nonAuxCount;
   2261                         nonAuxSubtype = subtype;
   2262                     } else {
   2263                         ++auxCount;
   2264                         auxSubtype = subtype;
   2265                     }
   2266                 }
   2267             }
   2268         }
   2269         if (nonAuxCount > 1 || auxCount > 1) {
   2270             return true;
   2271         } else if (nonAuxCount == 1 && auxCount == 1) {
   2272             if (nonAuxSubtype != null && auxSubtype != null
   2273                     && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
   2274                             || auxSubtype.overridesImplicitlyEnabledSubtype()
   2275                             || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
   2276                     && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
   2277                 return false;
   2278             }
   2279             return true;
   2280         }
   2281         return false;
   2282     }
   2283 
   2284     private boolean isKeyguardLocked() {
   2285         return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
   2286     }
   2287 
   2288     @BinderThread
   2289     @SuppressWarnings("deprecation")
   2290     @Override
   2291     public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
   2292             int backDisposition) {
   2293         if (!calledWithValidToken(token)) {
   2294             return;
   2295         }
   2296 
   2297         final StartInputInfo info;
   2298         synchronized (mMethodMap) {
   2299             info = mStartInputMap.get(startInputToken);
   2300             mImeWindowVis = vis;
   2301             mBackDisposition = backDisposition;
   2302             updateSystemUiLocked(token, vis, backDisposition);
   2303         }
   2304 
   2305         final boolean dismissImeOnBackKeyPressed;
   2306         switch (backDisposition) {
   2307             case InputMethodService.BACK_DISPOSITION_WILL_DISMISS:
   2308                 dismissImeOnBackKeyPressed = true;
   2309                 break;
   2310             case InputMethodService.BACK_DISPOSITION_WILL_NOT_DISMISS:
   2311                 dismissImeOnBackKeyPressed = false;
   2312                 break;
   2313             default:
   2314             case InputMethodService.BACK_DISPOSITION_DEFAULT:
   2315                 dismissImeOnBackKeyPressed = ((vis & InputMethodService.IME_VISIBLE) != 0);
   2316                 break;
   2317         }
   2318         mWindowManagerInternal.updateInputMethodWindowStatus(token,
   2319                 (vis & InputMethodService.IME_VISIBLE) != 0,
   2320                 dismissImeOnBackKeyPressed, info != null ? info.mTargetWindow : null);
   2321     }
   2322 
   2323     private void updateSystemUi(IBinder token, int vis, int backDisposition) {
   2324         synchronized (mMethodMap) {
   2325             updateSystemUiLocked(token, vis, backDisposition);
   2326         }
   2327     }
   2328 
   2329     // Caution! This method is called in this class. Handle multi-user carefully
   2330     private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
   2331         if (!calledWithValidToken(token)) {
   2332             return;
   2333         }
   2334 
   2335         // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
   2336         // all updateSystemUi happens on system previlege.
   2337         final long ident = Binder.clearCallingIdentity();
   2338         try {
   2339             // apply policy for binder calls
   2340             if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
   2341                 vis = 0;
   2342             }
   2343             // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
   2344             final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
   2345             if (mStatusBar != null) {
   2346                 mStatusBar.setImeWindowStatus(token, vis, backDisposition,
   2347                         needsToShowImeSwitcher);
   2348             }
   2349             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
   2350             if (imi != null && needsToShowImeSwitcher) {
   2351                 // Used to load label
   2352                 final CharSequence title = mRes.getText(
   2353                         com.android.internal.R.string.select_input_method);
   2354                 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
   2355                         mContext, imi, mCurrentSubtype);
   2356                 mImeSwitcherNotification.setContentTitle(title)
   2357                         .setContentText(summary)
   2358                         .setContentIntent(mImeSwitchPendingIntent);
   2359                 try {
   2360                     if ((mNotificationManager != null)
   2361                             && !mIWindowManager.hasNavigationBar()) {
   2362                         if (DEBUG) {
   2363                             Slog.d(TAG, "--- show notification: label =  " + summary);
   2364                         }
   2365                         mNotificationManager.notifyAsUser(null,
   2366                                 SystemMessage.NOTE_SELECT_INPUT_METHOD,
   2367                                 mImeSwitcherNotification.build(), UserHandle.ALL);
   2368                         mNotificationShown = true;
   2369                     }
   2370                 } catch (RemoteException e) {
   2371                 }
   2372             } else {
   2373                 if (mNotificationShown && mNotificationManager != null) {
   2374                     if (DEBUG) {
   2375                         Slog.d(TAG, "--- hide notification");
   2376                     }
   2377                     mNotificationManager.cancelAsUser(null,
   2378                             SystemMessage.NOTE_SELECT_INPUT_METHOD, UserHandle.ALL);
   2379                     mNotificationShown = false;
   2380                 }
   2381             }
   2382         } finally {
   2383             Binder.restoreCallingIdentity(ident);
   2384         }
   2385     }
   2386 
   2387     @Override
   2388     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
   2389         if (!calledFromValidUser()) {
   2390             return;
   2391         }
   2392         synchronized (mMethodMap) {
   2393             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
   2394             for (int i = 0; i < spans.length; ++i) {
   2395                 SuggestionSpan ss = spans[i];
   2396                 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
   2397                     mSecureSuggestionSpans.put(ss, currentImi);
   2398                 }
   2399             }
   2400         }
   2401     }
   2402 
   2403     @Override
   2404     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
   2405         if (!calledFromValidUser()) {
   2406             return false;
   2407         }
   2408         synchronized (mMethodMap) {
   2409             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
   2410             // TODO: Do not send the intent if the process of the targetImi is already dead.
   2411             if (targetImi != null) {
   2412                 final String[] suggestions = span.getSuggestions();
   2413                 if (index < 0 || index >= suggestions.length) return false;
   2414                 final String className = span.getNotificationTargetClassName();
   2415                 final Intent intent = new Intent();
   2416                 // Ensures that only a class in the original IME package will receive the
   2417                 // notification.
   2418                 intent.setClassName(targetImi.getPackageName(), className);
   2419                 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
   2420                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
   2421                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
   2422                 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
   2423                 final long ident = Binder.clearCallingIdentity();
   2424                 try {
   2425                     mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
   2426                 } finally {
   2427                     Binder.restoreCallingIdentity(ident);
   2428                 }
   2429                 return true;
   2430             }
   2431         }
   2432         return false;
   2433     }
   2434 
   2435     void updateFromSettingsLocked(boolean enabledMayChange) {
   2436         updateInputMethodsFromSettingsLocked(enabledMayChange);
   2437         updateKeyboardFromSettingsLocked();
   2438     }
   2439 
   2440     void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
   2441         if (enabledMayChange) {
   2442             List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
   2443             for (int i=0; i<enabled.size(); i++) {
   2444                 // We allow the user to select "disabled until used" apps, so if they
   2445                 // are enabling one of those here we now need to make it enabled.
   2446                 InputMethodInfo imm = enabled.get(i);
   2447                 try {
   2448                     ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
   2449                             PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
   2450                             mSettings.getCurrentUserId());
   2451                     if (ai != null && ai.enabledSetting
   2452                             == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
   2453                         if (DEBUG) {
   2454                             Slog.d(TAG, "Update state(" + imm.getId()
   2455                                     + "): DISABLED_UNTIL_USED -> DEFAULT");
   2456                         }
   2457                         mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
   2458                                 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
   2459                                 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
   2460                                 mContext.getBasePackageName());
   2461                     }
   2462                 } catch (RemoteException e) {
   2463                 }
   2464             }
   2465         }
   2466         // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
   2467         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
   2468         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
   2469         // enabled.
   2470         String id = mSettings.getSelectedInputMethod();
   2471         // There is no input method selected, try to choose new applicable input method.
   2472         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
   2473             id = mSettings.getSelectedInputMethod();
   2474         }
   2475         if (!TextUtils.isEmpty(id)) {
   2476             try {
   2477                 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
   2478             } catch (IllegalArgumentException e) {
   2479                 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
   2480                 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
   2481             }
   2482             mShortcutInputMethodsAndSubtypes.clear();
   2483         } else {
   2484             // There is no longer an input method set, so stop any current one.
   2485             resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
   2486         }
   2487         // Here is not the perfect place to reset the switching controller. Ideally
   2488         // mSwitchingController and mSettings should be able to share the same state.
   2489         // TODO: Make sure that mSwitchingController and mSettings are sharing the
   2490         // the same enabled IMEs list.
   2491         mSwitchingController.resetCircularListLocked(mContext);
   2492 
   2493     }
   2494 
   2495     public void updateKeyboardFromSettingsLocked() {
   2496         mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
   2497         if (mSwitchingDialog != null
   2498                 && mSwitchingDialogTitleView != null
   2499                 && mSwitchingDialog.isShowing()) {
   2500             final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
   2501                     com.android.internal.R.id.hard_keyboard_switch);
   2502             hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
   2503         }
   2504     }
   2505 
   2506     /* package */ void setInputMethodLocked(String id, int subtypeId) {
   2507         InputMethodInfo info = mMethodMap.get(id);
   2508         if (info == null) {
   2509             throw new IllegalArgumentException("Unknown id: " + id);
   2510         }
   2511 
   2512         // See if we need to notify a subtype change within the same IME.
   2513         if (id.equals(mCurMethodId)) {
   2514             final int subtypeCount = info.getSubtypeCount();
   2515             if (subtypeCount <= 0) {
   2516                 return;
   2517             }
   2518             final InputMethodSubtype oldSubtype = mCurrentSubtype;
   2519             final InputMethodSubtype newSubtype;
   2520             if (subtypeId >= 0 && subtypeId < subtypeCount) {
   2521                 newSubtype = info.getSubtypeAt(subtypeId);
   2522             } else {
   2523                 // If subtype is null, try to find the most applicable one from
   2524                 // getCurrentInputMethodSubtype.
   2525                 newSubtype = getCurrentInputMethodSubtypeLocked();
   2526             }
   2527             if (newSubtype == null || oldSubtype == null) {
   2528                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
   2529                         + ", new subtype = " + newSubtype);
   2530                 return;
   2531             }
   2532             if (newSubtype != oldSubtype) {
   2533                 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
   2534                 if (mCurMethod != null) {
   2535                     try {
   2536                         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
   2537                         mCurMethod.changeInputMethodSubtype(newSubtype);
   2538                     } catch (RemoteException e) {
   2539                         Slog.w(TAG, "Failed to call changeInputMethodSubtype");
   2540                     }
   2541                 }
   2542             }
   2543             return;
   2544         }
   2545 
   2546         // Changing to a different IME.
   2547         final long ident = Binder.clearCallingIdentity();
   2548         try {
   2549             // Set a subtype to this input method.
   2550             // subtypeId the name of a subtype which will be set.
   2551             setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
   2552             // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
   2553             // because mCurMethodId is stored as a history in
   2554             // setSelectedInputMethodAndSubtypeLocked().
   2555             mCurMethodId = id;
   2556 
   2557             if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
   2558                 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
   2559                 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
   2560                 intent.putExtra("input_method_id", id);
   2561                 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
   2562             }
   2563             unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
   2564         } finally {
   2565             Binder.restoreCallingIdentity(ident);
   2566         }
   2567     }
   2568 
   2569     @Override
   2570     public boolean showSoftInput(IInputMethodClient client, int flags,
   2571             ResultReceiver resultReceiver) {
   2572         if (!calledFromValidUser()) {
   2573             return false;
   2574         }
   2575         int uid = Binder.getCallingUid();
   2576         long ident = Binder.clearCallingIdentity();
   2577         try {
   2578             synchronized (mMethodMap) {
   2579                 if (mCurClient == null || client == null
   2580                         || mCurClient.client.asBinder() != client.asBinder()) {
   2581                     try {
   2582                         // We need to check if this is the current client with
   2583                         // focus in the window manager, to allow this call to
   2584                         // be made before input is started in it.
   2585                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   2586                             Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
   2587                             return false;
   2588                         }
   2589                     } catch (RemoteException e) {
   2590                         return false;
   2591                     }
   2592                 }
   2593 
   2594                 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
   2595                 return showCurrentInputLocked(flags, resultReceiver);
   2596             }
   2597         } finally {
   2598             Binder.restoreCallingIdentity(ident);
   2599         }
   2600     }
   2601 
   2602     boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   2603         mShowRequested = true;
   2604         if (mAccessibilityRequestingNoSoftKeyboard) {
   2605             return false;
   2606         }
   2607 
   2608         if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
   2609             mShowExplicitlyRequested = true;
   2610             mShowForced = true;
   2611         } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
   2612             mShowExplicitlyRequested = true;
   2613         }
   2614 
   2615         if (!mSystemReady) {
   2616             return false;
   2617         }
   2618 
   2619         boolean res = false;
   2620         if (mCurMethod != null) {
   2621             if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
   2622             executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
   2623                     MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
   2624                     resultReceiver));
   2625             mInputShown = true;
   2626             if (mHaveConnection && !mVisibleBound) {
   2627                 bindCurrentInputMethodServiceLocked(
   2628                         mCurIntent, mVisibleConnection, IME_VISIBLE_BIND_FLAGS);
   2629                 mVisibleBound = true;
   2630             }
   2631             res = true;
   2632         } else if (mHaveConnection && SystemClock.uptimeMillis()
   2633                 >= (mLastBindTime+TIME_TO_RECONNECT)) {
   2634             // The client has asked to have the input method shown, but
   2635             // we have been sitting here too long with a connection to the
   2636             // service and no interface received, so let's disconnect/connect
   2637             // to try to prod things along.
   2638             EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
   2639                     SystemClock.uptimeMillis()-mLastBindTime,1);
   2640             Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
   2641             mContext.unbindService(this);
   2642             bindCurrentInputMethodServiceLocked(mCurIntent, this, IME_CONNECTION_BIND_FLAGS);
   2643         } else {
   2644             if (DEBUG) {
   2645                 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
   2646                         + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
   2647             }
   2648         }
   2649 
   2650         return res;
   2651     }
   2652 
   2653     @Override
   2654     public boolean hideSoftInput(IInputMethodClient client, int flags,
   2655             ResultReceiver resultReceiver) {
   2656         if (!calledFromValidUser()) {
   2657             return false;
   2658         }
   2659         int uid = Binder.getCallingUid();
   2660         long ident = Binder.clearCallingIdentity();
   2661         try {
   2662             synchronized (mMethodMap) {
   2663                 if (mCurClient == null || client == null
   2664                         || mCurClient.client.asBinder() != client.asBinder()) {
   2665                     try {
   2666                         // We need to check if this is the current client with
   2667                         // focus in the window manager, to allow this call to
   2668                         // be made before input is started in it.
   2669                         if (!mIWindowManager.inputMethodClientHasFocus(client)) {
   2670                             if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
   2671                                     + uid + ": " + client);
   2672                             return false;
   2673                         }
   2674                     } catch (RemoteException e) {
   2675                         return false;
   2676                     }
   2677                 }
   2678 
   2679                 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
   2680                 return hideCurrentInputLocked(flags, resultReceiver);
   2681             }
   2682         } finally {
   2683             Binder.restoreCallingIdentity(ident);
   2684         }
   2685     }
   2686 
   2687     boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
   2688         if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
   2689                 && (mShowExplicitlyRequested || mShowForced)) {
   2690             if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
   2691             return false;
   2692         }
   2693         if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
   2694             if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
   2695             return false;
   2696         }
   2697 
   2698         // There is a chance that IMM#hideSoftInput() is called in a transient state where
   2699         // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
   2700         // to be updated with the new value sent from IME process.  Even in such a transient state
   2701         // historically we have accepted an incoming call of IMM#hideSoftInput() from the
   2702         // application process as a valid request, and have even promised such a behavior with CTS
   2703         // since Android Eclair.  That's why we need to accept IMM#hideSoftInput() even when only
   2704         // IMMS#InputShown indicates that the software keyboard is shown.
   2705         // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
   2706         final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
   2707                 (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
   2708         boolean res;
   2709         if (shouldHideSoftInput) {
   2710             // The IME will report its visible state again after the following message finally
   2711             // delivered to the IME process as an IPC.  Hence the inconsistency between
   2712             // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
   2713             // the final state.
   2714             executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
   2715                     MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
   2716             res = true;
   2717         } else {
   2718             res = false;
   2719         }
   2720         if (mHaveConnection && mVisibleBound) {
   2721             mContext.unbindService(mVisibleConnection);
   2722             mVisibleBound = false;
   2723         }
   2724         mInputShown = false;
   2725         mShowRequested = false;
   2726         mShowExplicitlyRequested = false;
   2727         mShowForced = false;
   2728         return res;
   2729     }
   2730 
   2731     @NonNull
   2732     @Override
   2733     public InputBindResult startInputOrWindowGainedFocus(
   2734             /* @InputMethodClient.StartInputReason */ final int startInputReason,
   2735             IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
   2736             int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
   2737             /* @InputConnectionInspector.missingMethods */ final int missingMethods,
   2738             int unverifiedTargetSdkVersion) {
   2739         final InputBindResult result;
   2740         if (windowToken != null) {
   2741             result = windowGainedFocus(startInputReason, client, windowToken, controlFlags,
   2742                     softInputMode, windowFlags, attribute, inputContext, missingMethods,
   2743                     unverifiedTargetSdkVersion);
   2744         } else {
   2745             result = startInput(startInputReason, client, inputContext, missingMethods, attribute,
   2746                     controlFlags);
   2747         }
   2748         if (result == null) {
   2749             // This must never happen, but just in case.
   2750             Slog.wtf(TAG, "InputBindResult is @NonNull. startInputReason="
   2751                     + InputMethodClient.getStartInputReason(startInputReason)
   2752                     + " windowFlags=#" + Integer.toHexString(windowFlags)
   2753                     + " editorInfo=" + attribute);
   2754             return InputBindResult.NULL;
   2755         }
   2756         return result;
   2757     }
   2758 
   2759     @NonNull
   2760     private InputBindResult windowGainedFocus(
   2761             /* @InputMethodClient.StartInputReason */ final int startInputReason,
   2762             IInputMethodClient client, IBinder windowToken, int controlFlags,
   2763             /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
   2764             int windowFlags, EditorInfo attribute, IInputContext inputContext,
   2765             /* @InputConnectionInspector.missingMethods */  final int missingMethods,
   2766             int unverifiedTargetSdkVersion) {
   2767         // Needs to check the validity before clearing calling identity
   2768         final boolean calledFromValidUser = calledFromValidUser();
   2769         InputBindResult res = null;
   2770         long ident = Binder.clearCallingIdentity();
   2771         try {
   2772             synchronized (mMethodMap) {
   2773                 if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
   2774                         + InputMethodClient.getStartInputReason(startInputReason)
   2775                         + " client=" + client.asBinder()
   2776                         + " inputContext=" + inputContext
   2777                         + " missingMethods="
   2778                         + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
   2779                         + " attribute=" + attribute
   2780                         + " controlFlags=#" + Integer.toHexString(controlFlags)
   2781                         + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
   2782                         + " windowFlags=#" + Integer.toHexString(windowFlags)
   2783                         + " unverifiedTargetSdkVersion=" + unverifiedTargetSdkVersion);
   2784 
   2785                 ClientState cs = mClients.get(client.asBinder());
   2786                 if (cs == null) {
   2787                     throw new IllegalArgumentException("unknown client "
   2788                             + client.asBinder());
   2789                 }
   2790 
   2791                 try {
   2792                     if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
   2793                         // Check with the window manager to make sure this client actually
   2794                         // has a window with focus.  If not, reject.  This is thread safe
   2795                         // because if the focus changes some time before or after, the
   2796                         // next client receiving focus that has any interest in input will
   2797                         // be calling through here after that change happens.
   2798                         if (DEBUG) {
   2799                             Slog.w(TAG, "Focus gain on non-focused client " + cs.client
   2800                                     + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
   2801                         }
   2802                         return InputBindResult.NOT_IME_TARGET_WINDOW;
   2803                     }
   2804                 } catch (RemoteException e) {
   2805                 }
   2806 
   2807                 if (!calledFromValidUser) {
   2808                     Slog.w(TAG, "A background user is requesting window. Hiding IME.");
   2809                     Slog.w(TAG, "If you want to interect with IME, you need "
   2810                             + "android.permission.INTERACT_ACROSS_USERS_FULL");
   2811                     hideCurrentInputLocked(0, null);
   2812                     return InputBindResult.INVALID_USER;
   2813                 }
   2814 
   2815                 if (mCurFocusedWindow == windowToken) {
   2816                     if (DEBUG) {
   2817                         Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
   2818                                 + " attribute=" + attribute + ", token = " + windowToken);
   2819                     }
   2820                     if (attribute != null) {
   2821                         return startInputUncheckedLocked(cs, inputContext, missingMethods,
   2822                                 attribute, controlFlags, startInputReason);
   2823                     }
   2824                     return new InputBindResult(
   2825                             InputBindResult.ResultCode.SUCCESS_REPORT_WINDOW_FOCUS_ONLY,
   2826                             null, null, null, -1, -1);
   2827                 }
   2828                 mCurFocusedWindow = windowToken;
   2829                 mCurFocusedWindowSoftInputMode = softInputMode;
   2830                 mCurFocusedWindowClient = cs;
   2831 
   2832                 // Should we auto-show the IME even if the caller has not
   2833                 // specified what should be done with it?
   2834                 // We only do this automatically if the window can resize
   2835                 // to accommodate the IME (so what the user sees will give
   2836                 // them good context without input information being obscured
   2837                 // by the IME) or if running on a large screen where there
   2838                 // is more room for the target window + IME.
   2839                 final boolean doAutoShow =
   2840                         (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
   2841                                 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
   2842                         || mRes.getConfiguration().isLayoutSizeAtLeast(
   2843                                 Configuration.SCREENLAYOUT_SIZE_LARGE);
   2844                 final boolean isTextEditor =
   2845                         (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
   2846 
   2847                 // We want to start input before showing the IME, but after closing
   2848                 // it.  We want to do this after closing it to help the IME disappear
   2849                 // more quickly (not get stuck behind it initializing itself for the
   2850                 // new focused input, even if its window wants to hide the IME).
   2851                 boolean didStart = false;
   2852 
   2853                 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
   2854                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
   2855                         if (!isTextEditor || !doAutoShow) {
   2856                             if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
   2857                                 // There is no focus view, and this window will
   2858                                 // be behind any soft input window, so hide the
   2859                                 // soft input window if it is shown.
   2860                                 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
   2861                                 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
   2862                             }
   2863                         } else if (isTextEditor && doAutoShow && (softInputMode &
   2864                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   2865                             // There is a focus view, and we are navigating forward
   2866                             // into the window, so show the input window for the user.
   2867                             // We only do this automatically if the window can resize
   2868                             // to accommodate the IME (so what the user sees will give
   2869                             // them good context without input information being obscured
   2870                             // by the IME) or if running on a large screen where there
   2871                             // is more room for the target window + IME.
   2872                             if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
   2873                             if (attribute != null) {
   2874                                 res = startInputUncheckedLocked(cs, inputContext,
   2875                                         missingMethods, attribute, controlFlags, startInputReason);
   2876                                 didStart = true;
   2877                             }
   2878                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   2879                         }
   2880                         break;
   2881                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
   2882                         // Do nothing.
   2883                         break;
   2884                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
   2885                         if ((softInputMode &
   2886                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   2887                             if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
   2888                             hideCurrentInputLocked(0, null);
   2889                         }
   2890                         break;
   2891                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
   2892                         if (DEBUG) Slog.v(TAG, "Window asks to hide input");
   2893                         hideCurrentInputLocked(0, null);
   2894                         break;
   2895                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
   2896                         if ((softInputMode &
   2897                                 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
   2898                             if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
   2899                             if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
   2900                                     unverifiedTargetSdkVersion, controlFlags)) {
   2901                                 if (attribute != null) {
   2902                                     res = startInputUncheckedLocked(cs, inputContext,
   2903                                             missingMethods, attribute, controlFlags,
   2904                                             startInputReason);
   2905                                     didStart = true;
   2906                                 }
   2907                                 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   2908                             } else {
   2909                                 Slog.e(TAG, "SOFT_INPUT_STATE_VISIBLE is ignored because"
   2910                                         + " there is no focused view that also returns true from"
   2911                                         + " View#onCheckIsTextEditor()");
   2912                             }
   2913                         }
   2914                         break;
   2915                     case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
   2916                         if (DEBUG) Slog.v(TAG, "Window asks to always show input");
   2917                         if (InputMethodUtils.isSoftInputModeStateVisibleAllowed(
   2918                                 unverifiedTargetSdkVersion, controlFlags)) {
   2919                             if (attribute != null) {
   2920                                 res = startInputUncheckedLocked(cs, inputContext, missingMethods,
   2921                                         attribute, controlFlags, startInputReason);
   2922                                 didStart = true;
   2923                             }
   2924                             showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
   2925                         } else {
   2926                             Slog.e(TAG, "SOFT_INPUT_STATE_ALWAYS_VISIBLE is ignored because"
   2927                                     + " there is no focused view that also returns true from"
   2928                                     + " View#onCheckIsTextEditor()");
   2929                         }
   2930                         break;
   2931                 }
   2932 
   2933                 if (!didStart) {
   2934                     if (attribute != null) {
   2935                         if (!DebugFlags.FLAG_OPTIMIZE_START_INPUT.value()
   2936                                 || (controlFlags
   2937                                 & InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0) {
   2938                             res = startInputUncheckedLocked(cs, inputContext, missingMethods,
   2939                                     attribute,
   2940                                     controlFlags, startInputReason);
   2941                         } else {
   2942                             res = InputBindResult.NO_EDITOR;
   2943                         }
   2944                     } else {
   2945                         res = InputBindResult.NULL_EDITOR_INFO;
   2946                     }
   2947                 }
   2948             }
   2949         } finally {
   2950             Binder.restoreCallingIdentity(ident);
   2951         }
   2952 
   2953         return res;
   2954     }
   2955 
   2956     private boolean canShowInputMethodPickerLocked(IInputMethodClient client) {
   2957         final int uid = Binder.getCallingUid();
   2958         if (UserHandle.getAppId(uid) == Process.SYSTEM_UID) {
   2959             return true;
   2960         } else if (mCurFocusedWindowClient != null && client != null
   2961                 && mCurFocusedWindowClient.client.asBinder() == client.asBinder()) {
   2962             return true;
   2963         } else if (mCurIntent != null && InputMethodUtils.checkIfPackageBelongsToUid(
   2964                 mAppOpsManager,
   2965                 uid,
   2966                 mCurIntent.getComponent().getPackageName())) {
   2967             return true;
   2968         } else if (mContext.checkCallingPermission(
   2969                 android.Manifest.permission.WRITE_SECURE_SETTINGS)
   2970                 == PackageManager.PERMISSION_GRANTED) {
   2971             return true;
   2972         }
   2973 
   2974         return false;
   2975     }
   2976 
   2977     @Override
   2978     public void showInputMethodPickerFromClient(
   2979             IInputMethodClient client, int auxiliarySubtypeMode) {
   2980         if (!calledFromValidUser()) {
   2981             return;
   2982         }
   2983         synchronized (mMethodMap) {
   2984             if(!canShowInputMethodPickerLocked(client)) {
   2985                 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
   2986                         + Binder.getCallingUid() + ": " + client);
   2987                 return;
   2988             }
   2989 
   2990             // Always call subtype picker, because subtype picker is a superset of input method
   2991             // picker.
   2992             mHandler.sendMessage(mCaller.obtainMessageI(
   2993                     MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
   2994         }
   2995     }
   2996 
   2997     public boolean isInputMethodPickerShownForTest() {
   2998         synchronized(mMethodMap) {
   2999             if (mSwitchingDialog == null) {
   3000                 return false;
   3001             }
   3002             return mSwitchingDialog.isShowing();
   3003         }
   3004     }
   3005 
   3006     @Override
   3007     public void setInputMethod(IBinder token, String id) {
   3008         if (!calledFromValidUser()) {
   3009             return;
   3010         }
   3011         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
   3012     }
   3013 
   3014     @Override
   3015     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
   3016         if (!calledFromValidUser()) {
   3017             return;
   3018         }
   3019         synchronized (mMethodMap) {
   3020             if (subtype != null) {
   3021                 setInputMethodWithSubtypeIdLocked(token, id,
   3022                         InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
   3023                                 subtype.hashCode()));
   3024             } else {
   3025                 setInputMethod(token, id);
   3026             }
   3027         }
   3028     }
   3029 
   3030     @Override
   3031     public void showInputMethodAndSubtypeEnablerFromClient(
   3032             IInputMethodClient client, String inputMethodId) {
   3033         if (!calledFromValidUser()) {
   3034             return;
   3035         }
   3036         synchronized (mMethodMap) {
   3037             executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
   3038                     MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
   3039         }
   3040     }
   3041 
   3042     @Override
   3043     public boolean switchToPreviousInputMethod(IBinder token) {
   3044         if (!calledFromValidUser()) {
   3045             return false;
   3046         }
   3047         synchronized (mMethodMap) {
   3048             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
   3049             final InputMethodInfo lastImi;
   3050             if (lastIme != null) {
   3051                 lastImi = mMethodMap.get(lastIme.first);
   3052             } else {
   3053                 lastImi = null;
   3054             }
   3055             String targetLastImiId = null;
   3056             int subtypeId = NOT_A_SUBTYPE_ID;
   3057             if (lastIme != null && lastImi != null) {
   3058                 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
   3059                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
   3060                 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
   3061                         : mCurrentSubtype.hashCode();
   3062                 // If the last IME is the same as the current IME and the last subtype is not
   3063                 // defined, there is no need to switch to the last IME.
   3064                 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
   3065                     targetLastImiId = lastIme.first;
   3066                     subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
   3067                 }
   3068             }
   3069 
   3070             if (TextUtils.isEmpty(targetLastImiId)
   3071                     && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
   3072                 // This is a safety net. If the currentSubtype can't be added to the history
   3073                 // and the framework couldn't find the last ime, we will make the last ime be
   3074                 // the most applicable enabled keyboard subtype of the system imes.
   3075                 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
   3076                 if (enabled != null) {
   3077                     final int N = enabled.size();
   3078                     final String locale = mCurrentSubtype == null
   3079                             ? mRes.getConfiguration().locale.toString()
   3080                             : mCurrentSubtype.getLocale();
   3081                     for (int i = 0; i < N; ++i) {
   3082                         final InputMethodInfo imi = enabled.get(i);
   3083                         if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
   3084                             InputMethodSubtype keyboardSubtype =
   3085                                     InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
   3086                                             InputMethodUtils.getSubtypes(imi),
   3087                                             InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
   3088                             if (keyboardSubtype != null) {
   3089                                 targetLastImiId = imi.getId();
   3090                                 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
   3091                                         imi, keyboardSubtype.hashCode());
   3092                                 if(keyboardSubtype.getLocale().equals(locale)) {
   3093                                     break;
   3094                                 }
   3095                             }
   3096                         }
   3097                     }
   3098                 }
   3099             }
   3100 
   3101             if (!TextUtils.isEmpty(targetLastImiId)) {
   3102                 if (DEBUG) {
   3103                     Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
   3104                             + ", from: " + mCurMethodId + ", " + subtypeId);
   3105                 }
   3106                 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
   3107                 return true;
   3108             } else {
   3109                 return false;
   3110             }
   3111         }
   3112     }
   3113 
   3114     @Override
   3115     public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
   3116         if (!calledFromValidUser()) {
   3117             return false;
   3118         }
   3119         synchronized (mMethodMap) {
   3120             if (!calledWithValidToken(token)) {
   3121                 return false;
   3122             }
   3123             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
   3124                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
   3125                     true /* forward */);
   3126             if (nextSubtype == null) {
   3127                 return false;
   3128             }
   3129             setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
   3130                     nextSubtype.mSubtypeId);
   3131             return true;
   3132         }
   3133     }
   3134 
   3135     @Override
   3136     public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
   3137         if (!calledFromValidUser()) {
   3138             return false;
   3139         }
   3140         synchronized (mMethodMap) {
   3141             if (!calledWithValidToken(token)) {
   3142                 return false;
   3143             }
   3144             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
   3145                     false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
   3146                     true /* forward */);
   3147             if (nextSubtype == null) {
   3148                 return false;
   3149             }
   3150             return true;
   3151         }
   3152     }
   3153 
   3154     @Override
   3155     public InputMethodSubtype getLastInputMethodSubtype() {
   3156         if (!calledFromValidUser()) {
   3157             return null;
   3158         }
   3159         synchronized (mMethodMap) {
   3160             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
   3161             // TODO: Handle the case of the last IME with no subtypes
   3162             if (lastIme == null || TextUtils.isEmpty(lastIme.first)
   3163                     || TextUtils.isEmpty(lastIme.second)) return null;
   3164             final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
   3165             if (lastImi == null) return null;
   3166             try {
   3167                 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
   3168                 final int lastSubtypeId =
   3169                         InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
   3170                 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
   3171                     return null;
   3172                 }
   3173                 return lastImi.getSubtypeAt(lastSubtypeId);
   3174             } catch (NumberFormatException e) {
   3175                 return null;
   3176             }
   3177         }
   3178     }
   3179 
   3180     @Override
   3181     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
   3182         if (!calledFromValidUser()) {
   3183             return;
   3184         }
   3185         // By this IPC call, only a process which shares the same uid with the IME can add
   3186         // additional input method subtypes to the IME.
   3187         if (TextUtils.isEmpty(imiId) || subtypes == null) return;
   3188         synchronized (mMethodMap) {
   3189             if (!mSystemReady) {
   3190                 return;
   3191             }
   3192             final InputMethodInfo imi = mMethodMap.get(imiId);
   3193             if (imi == null) return;
   3194             final String[] packageInfos;
   3195             try {
   3196                 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
   3197             } catch (RemoteException e) {
   3198                 Slog.e(TAG, "Failed to get package infos");
   3199                 return;
   3200             }
   3201             if (packageInfos != null) {
   3202                 final int packageNum = packageInfos.length;
   3203                 for (int i = 0; i < packageNum; ++i) {
   3204                     if (packageInfos[i].equals(imi.getPackageName())) {
   3205                         mFileManager.addInputMethodSubtypes(imi, subtypes);
   3206                         final long ident = Binder.clearCallingIdentity();
   3207                         try {
   3208                             buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
   3209                         } finally {
   3210                             Binder.restoreCallingIdentity(ident);
   3211                         }
   3212                         return;
   3213                     }
   3214                 }
   3215             }
   3216         }
   3217         return;
   3218     }
   3219 
   3220     @Override
   3221     public int getInputMethodWindowVisibleHeight() {
   3222         return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
   3223     }
   3224 
   3225     @Override
   3226     public void clearLastInputMethodWindowForTransition(IBinder token) {
   3227         if (!calledFromValidUser()) {
   3228             return;
   3229         }
   3230         synchronized (mMethodMap) {
   3231             if (!calledWithValidToken(token)) {
   3232                 return;
   3233             }
   3234         }
   3235         mWindowManagerInternal.clearLastInputMethodWindowForTransition();
   3236     }
   3237 
   3238     @Override
   3239     public void notifyUserAction(int sequenceNumber) {
   3240         if (DEBUG) {
   3241             Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
   3242         }
   3243         synchronized (mMethodMap) {
   3244             if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
   3245                 if (DEBUG) {
   3246                     Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
   3247                             + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
   3248                             + " actual: " + sequenceNumber);
   3249                 }
   3250                 return;
   3251             }
   3252             final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
   3253             if (imi != null) {
   3254                 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
   3255             }
   3256         }
   3257     }
   3258 
   3259     private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
   3260         synchronized (mMethodMap) {
   3261             setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
   3262         }
   3263     }
   3264 
   3265     private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
   3266         if (token == null) {
   3267             if (mContext.checkCallingOrSelfPermission(
   3268                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
   3269                     != PackageManager.PERMISSION_GRANTED) {
   3270                 throw new SecurityException(
   3271                         "Using null token requires permission "
   3272                         + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   3273             }
   3274         } else if (mCurToken != token) {
   3275             Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
   3276                     + " token: " + token);
   3277             return;
   3278         }
   3279 
   3280         final long ident = Binder.clearCallingIdentity();
   3281         try {
   3282             setInputMethodLocked(id, subtypeId);
   3283         } finally {
   3284             Binder.restoreCallingIdentity(ident);
   3285         }
   3286     }
   3287 
   3288     @Override
   3289     public void hideMySoftInput(IBinder token, int flags) {
   3290         if (!calledFromValidUser()) {
   3291             return;
   3292         }
   3293         synchronized (mMethodMap) {
   3294             if (!calledWithValidToken(token)) {
   3295                 return;
   3296             }
   3297             long ident = Binder.clearCallingIdentity();
   3298             try {
   3299                 hideCurrentInputLocked(flags, null);
   3300             } finally {
   3301                 Binder.restoreCallingIdentity(ident);
   3302             }
   3303         }
   3304     }
   3305 
   3306     @Override
   3307     public void showMySoftInput(IBinder token, int flags) {
   3308         if (!calledFromValidUser()) {
   3309             return;
   3310         }
   3311         synchronized (mMethodMap) {
   3312             if (!calledWithValidToken(token)) {
   3313                 return;
   3314             }
   3315             long ident = Binder.clearCallingIdentity();
   3316             try {
   3317                 showCurrentInputLocked(flags, null);
   3318             } finally {
   3319                 Binder.restoreCallingIdentity(ident);
   3320             }
   3321         }
   3322     }
   3323 
   3324     void setEnabledSessionInMainThread(SessionState session) {
   3325         if (mEnabledSession != session) {
   3326             if (mEnabledSession != null && mEnabledSession.session != null) {
   3327                 try {
   3328                     if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
   3329                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
   3330                 } catch (RemoteException e) {
   3331                 }
   3332             }
   3333             mEnabledSession = session;
   3334             if (mEnabledSession != null && mEnabledSession.session != null) {
   3335                 try {
   3336                     if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
   3337                     mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
   3338                 } catch (RemoteException e) {
   3339                 }
   3340             }
   3341         }
   3342     }
   3343 
   3344     @MainThread
   3345     @Override
   3346     public boolean handleMessage(Message msg) {
   3347         SomeArgs args;
   3348         switch (msg.what) {
   3349             case MSG_SHOW_IM_SUBTYPE_PICKER:
   3350                 final boolean showAuxSubtypes;
   3351                 switch (msg.arg1) {
   3352                     case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
   3353                         // This is undocumented so far, but IMM#showInputMethodPicker() has been
   3354                         // implemented so that auxiliary subtypes will be excluded when the soft
   3355                         // keyboard is invisible.
   3356                         showAuxSubtypes = mInputShown;
   3357                         break;
   3358                     case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
   3359                         showAuxSubtypes = true;
   3360                         break;
   3361                     case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
   3362                         showAuxSubtypes = false;
   3363                         break;
   3364                     default:
   3365                         Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
   3366                         return false;
   3367                 }
   3368                 showInputMethodMenu(showAuxSubtypes);
   3369                 return true;
   3370 
   3371             case MSG_SHOW_IM_SUBTYPE_ENABLER:
   3372                 showInputMethodAndSubtypeEnabler((String)msg.obj);
   3373                 return true;
   3374 
   3375             case MSG_SHOW_IM_CONFIG:
   3376                 showConfigureInputMethods();
   3377                 return true;
   3378 
   3379             // ---------------------------------------------------------
   3380 
   3381             case MSG_UNBIND_INPUT:
   3382                 try {
   3383                     ((IInputMethod)msg.obj).unbindInput();
   3384                 } catch (RemoteException e) {
   3385                     // There is nothing interesting about the method dying.
   3386                 }
   3387                 return true;
   3388             case MSG_BIND_INPUT:
   3389                 args = (SomeArgs)msg.obj;
   3390                 try {
   3391                     ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
   3392                 } catch (RemoteException e) {
   3393                 }
   3394                 args.recycle();
   3395                 return true;
   3396             case MSG_SHOW_SOFT_INPUT:
   3397                 args = (SomeArgs)msg.obj;
   3398                 try {
   3399                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
   3400                             + msg.arg1 + ", " + args.arg2 + ")");
   3401                     ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
   3402                 } catch (RemoteException e) {
   3403                 }
   3404                 args.recycle();
   3405                 return true;
   3406             case MSG_HIDE_SOFT_INPUT:
   3407                 args = (SomeArgs)msg.obj;
   3408                 try {
   3409                     if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
   3410                             + args.arg2 + ")");
   3411                     ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
   3412                 } catch (RemoteException e) {
   3413                 }
   3414                 args.recycle();
   3415                 return true;
   3416             case MSG_HIDE_CURRENT_INPUT_METHOD:
   3417                 synchronized (mMethodMap) {
   3418                     hideCurrentInputLocked(0, null);
   3419                 }
   3420                 return true;
   3421             case MSG_ATTACH_TOKEN:
   3422                 args = (SomeArgs)msg.obj;
   3423                 try {
   3424                     if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
   3425                     ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
   3426                 } catch (RemoteException e) {
   3427                 }
   3428                 args.recycle();
   3429                 return true;
   3430             case MSG_CREATE_SESSION: {
   3431                 args = (SomeArgs)msg.obj;
   3432                 IInputMethod method = (IInputMethod)args.arg1;
   3433                 InputChannel channel = (InputChannel)args.arg2;
   3434                 try {
   3435                     method.createSession(channel, (IInputSessionCallback)args.arg3);
   3436                 } catch (RemoteException e) {
   3437                 } finally {
   3438                     // Dispose the channel if the input method is not local to this process
   3439                     // because the remote proxy will get its own copy when unparceled.
   3440                     if (channel != null && Binder.isProxy(method)) {
   3441                         channel.dispose();
   3442                     }
   3443                 }
   3444                 args.recycle();
   3445                 return true;
   3446             }
   3447             // ---------------------------------------------------------
   3448 
   3449             case MSG_START_INPUT: {
   3450                 final int missingMethods = msg.arg1;
   3451                 final boolean restarting = msg.arg2 != 0;
   3452                 args = (SomeArgs) msg.obj;
   3453                 final IBinder startInputToken = (IBinder) args.arg1;
   3454                 final SessionState session = (SessionState) args.arg2;
   3455                 final IInputContext inputContext = (IInputContext) args.arg3;
   3456                 final EditorInfo editorInfo = (EditorInfo) args.arg4;
   3457                 try {
   3458                     setEnabledSessionInMainThread(session);
   3459                     session.method.startInput(startInputToken, inputContext, missingMethods,
   3460                             editorInfo, restarting);
   3461                 } catch (RemoteException e) {
   3462                 }
   3463                 args.recycle();
   3464                 return true;
   3465             }
   3466 
   3467             // ---------------------------------------------------------
   3468 
   3469             case MSG_UNBIND_CLIENT:
   3470                 try {
   3471                     ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
   3472                 } catch (RemoteException e) {
   3473                     // There is nothing interesting about the last client dying.
   3474                 }
   3475                 return true;
   3476             case MSG_BIND_CLIENT: {
   3477                 args = (SomeArgs)msg.obj;
   3478                 IInputMethodClient client = (IInputMethodClient)args.arg1;
   3479                 InputBindResult res = (InputBindResult)args.arg2;
   3480                 try {
   3481                     client.onBindMethod(res);
   3482                 } catch (RemoteException e) {
   3483                     Slog.w(TAG, "Client died receiving input method " + args.arg2);
   3484                 } finally {
   3485                     // Dispose the channel if the input method is not local to this process
   3486                     // because the remote proxy will get its own copy when unparceled.
   3487                     if (res.channel != null && Binder.isProxy(client)) {
   3488                         res.channel.dispose();
   3489                     }
   3490                 }
   3491                 args.recycle();
   3492                 return true;
   3493             }
   3494             case MSG_SET_ACTIVE:
   3495                 try {
   3496                     ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0);
   3497                 } catch (RemoteException e) {
   3498                     Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
   3499                             + ((ClientState)msg.obj).pid + " uid "
   3500                             + ((ClientState)msg.obj).uid);
   3501                 }
   3502                 return true;
   3503             case MSG_SET_INTERACTIVE:
   3504                 handleSetInteractive(msg.arg1 != 0);
   3505                 return true;
   3506             case MSG_START_VR_INPUT:
   3507                 startVrInputMethodNoCheck((ComponentName) msg.obj);
   3508                 return true;
   3509             case MSG_SWITCH_IME:
   3510                 handleSwitchInputMethod(msg.arg1 != 0);
   3511                 return true;
   3512             case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
   3513                 final int sequenceNumber = msg.arg1;
   3514                 final ClientState clientState = (ClientState)msg.obj;
   3515                 try {
   3516                     clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
   3517                 } catch (RemoteException e) {
   3518                     Slog.w(TAG, "Got RemoteException sending "
   3519                             + "setUserActionNotificationSequenceNumber("
   3520                             + sequenceNumber + ") notification to pid "
   3521                             + clientState.pid + " uid "
   3522                             + clientState.uid);
   3523                 }
   3524                 return true;
   3525             }
   3526             case MSG_REPORT_FULLSCREEN_MODE: {
   3527                 final boolean fullscreen = msg.arg1 != 0;
   3528                 final ClientState clientState = (ClientState)msg.obj;
   3529                 try {
   3530                     clientState.client.reportFullscreenMode(fullscreen);
   3531                 } catch (RemoteException e) {
   3532                     Slog.w(TAG, "Got RemoteException sending "
   3533                             + "reportFullscreen(" + fullscreen + ") notification to pid="
   3534                             + clientState.pid + " uid=" + clientState.uid);
   3535                 }
   3536                 return true;
   3537             }
   3538 
   3539             // --------------------------------------------------------------
   3540             case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
   3541                 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
   3542                 return true;
   3543             case MSG_SYSTEM_UNLOCK_USER:
   3544                 final int userId = msg.arg1;
   3545                 onUnlockUser(userId);
   3546                 return true;
   3547         }
   3548         return false;
   3549     }
   3550 
   3551     private void handleSetInteractive(final boolean interactive) {
   3552         synchronized (mMethodMap) {
   3553             mIsInteractive = interactive;
   3554             updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
   3555 
   3556             // Inform the current client of the change in active status
   3557             if (mCurClient != null && mCurClient.client != null) {
   3558                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
   3559                         MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0,
   3560                         mCurClient));
   3561             }
   3562         }
   3563     }
   3564 
   3565     private void handleSwitchInputMethod(final boolean forwardDirection) {
   3566         synchronized (mMethodMap) {
   3567             final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
   3568                     false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
   3569             if (nextSubtype == null) {
   3570                 return;
   3571             }
   3572             setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
   3573             final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
   3574             if (newInputMethodInfo == null) {
   3575                 return;
   3576             }
   3577             final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
   3578                     newInputMethodInfo, mCurrentSubtype);
   3579             if (!TextUtils.isEmpty(toastText)) {
   3580                 if (mSubtypeSwitchedByShortCutToast == null) {
   3581                     mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText,
   3582                             Toast.LENGTH_SHORT);
   3583                 } else {
   3584                     mSubtypeSwitchedByShortCutToast.setText(toastText);
   3585                 }
   3586                 mSubtypeSwitchedByShortCutToast.show();
   3587             }
   3588         }
   3589     }
   3590 
   3591     private boolean chooseNewDefaultIMELocked() {
   3592         final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
   3593                 mSettings.getEnabledInputMethodListLocked());
   3594         if (imi != null) {
   3595             if (DEBUG) {
   3596                 Slog.d(TAG, "New default IME was selected: " + imi.getId());
   3597             }
   3598             resetSelectedInputMethodAndSubtypeLocked(imi.getId());
   3599             return true;
   3600         }
   3601 
   3602         return false;
   3603     }
   3604 
   3605     @PackageManager.ResolveInfoFlags
   3606     private int getComponentMatchingFlags(@PackageManager.ResolveInfoFlags int baseFlags) {
   3607         synchronized (mMethodMap) {
   3608             if (mBindInstantServiceAllowed) {
   3609                 baseFlags |= PackageManager.MATCH_INSTANT;
   3610             }
   3611             return baseFlags;
   3612         }
   3613     }
   3614 
   3615     @GuardedBy("mMethodMap")
   3616     void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
   3617         if (DEBUG) {
   3618             Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
   3619                     + " \n ------ caller=" + Debug.getCallers(10));
   3620         }
   3621         if (!mSystemReady) {
   3622             Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
   3623             return;
   3624         }
   3625         mMethodList.clear();
   3626         mMethodMap.clear();
   3627         mMethodMapUpdateCount++;
   3628         mMyPackageMonitor.clearKnownImePackageNamesLocked();
   3629 
   3630         // Use for queryIntentServicesAsUser
   3631         final PackageManager pm = mContext.getPackageManager();
   3632 
   3633         // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
   3634         // behavior of PackageManager is exactly what we want.  It by default picks up appropriate
   3635         // services depending on the unlock state for the specified user.
   3636         final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
   3637                 new Intent(InputMethod.SERVICE_INTERFACE),
   3638                 getComponentMatchingFlags(PackageManager.GET_META_DATA
   3639                         | PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS),
   3640                 mSettings.getCurrentUserId());
   3641 
   3642         final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
   3643                 mFileManager.getAllAdditionalInputMethodSubtypes();
   3644         for (int i = 0; i < services.size(); ++i) {
   3645             ResolveInfo ri = services.get(i);
   3646             ServiceInfo si = ri.serviceInfo;
   3647             final String imeId = InputMethodInfo.computeId(ri);
   3648             if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
   3649                 Slog.w(TAG, "Skipping input method " + imeId
   3650                         + ": it does not require the permission "
   3651                         + android.Manifest.permission.BIND_INPUT_METHOD);
   3652                 continue;
   3653             }
   3654 
   3655             if (DEBUG) Slog.d(TAG, "Checking " + imeId);
   3656 
   3657             final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
   3658             try {
   3659                 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
   3660                 mMethodList.add(p);
   3661                 final String id = p.getId();
   3662                 mMethodMap.put(id, p);
   3663 
   3664                 if (DEBUG) {
   3665                     Slog.d(TAG, "Found an input method " + p);
   3666                 }
   3667             } catch (Exception e) {
   3668                 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
   3669             }
   3670         }
   3671 
   3672         // Construct the set of possible IME packages for onPackageChanged() to avoid false
   3673         // negatives when the package state remains to be the same but only the component state is
   3674         // changed.
   3675         {
   3676             // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
   3677             // of this query is to avoid false negatives.  PackageManager.MATCH_ALL could be more
   3678             // conservative, but it seems we cannot use it for now (Issue 35176630).
   3679             final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
   3680                     new Intent(InputMethod.SERVICE_INTERFACE),
   3681                     getComponentMatchingFlags(PackageManager.MATCH_DISABLED_COMPONENTS),
   3682                     mSettings.getCurrentUserId());
   3683             final int N = allInputMethodServices.size();
   3684             for (int i = 0; i < N; ++i) {
   3685                 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
   3686                 if (android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
   3687                     mMyPackageMonitor.addKnownImePackageNameLocked(si.packageName);
   3688                 }
   3689             }
   3690         }
   3691 
   3692         boolean reenableMinimumNonAuxSystemImes = false;
   3693         // TODO: The following code should find better place to live.
   3694         if (!resetDefaultEnabledIme) {
   3695             boolean enabledImeFound = false;
   3696             boolean enabledNonAuxImeFound = false;
   3697             final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
   3698             final int N = enabledImes.size();
   3699             for (int i = 0; i < N; ++i) {
   3700                 final InputMethodInfo imi = enabledImes.get(i);
   3701                 if (mMethodList.contains(imi)) {
   3702                     enabledImeFound = true;
   3703                     if (!imi.isAuxiliaryIme()) {
   3704                         enabledNonAuxImeFound = true;
   3705                         break;
   3706                     }
   3707                 }
   3708             }
   3709             if (!enabledImeFound) {
   3710                 if (DEBUG) {
   3711                     Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
   3712                 }
   3713                 resetDefaultEnabledIme = true;
   3714                 resetSelectedInputMethodAndSubtypeLocked("");
   3715             } else if (!enabledNonAuxImeFound) {
   3716                 if (DEBUG) {
   3717                     Slog.i(TAG, "All the enabled non-Aux IMEs are gone. Do partial reset.");
   3718                 }
   3719                 reenableMinimumNonAuxSystemImes = true;
   3720             }
   3721         }
   3722 
   3723         if (resetDefaultEnabledIme || reenableMinimumNonAuxSystemImes) {
   3724             final ArrayList<InputMethodInfo> defaultEnabledIme =
   3725                     InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList,
   3726                             reenableMinimumNonAuxSystemImes);
   3727             final int N = defaultEnabledIme.size();
   3728             for (int i = 0; i < N; ++i) {
   3729                 final InputMethodInfo imi =  defaultEnabledIme.get(i);
   3730                 if (DEBUG) {
   3731                     Slog.d(TAG, "--- enable ime = " + imi);
   3732                 }
   3733                 setInputMethodEnabledLocked(imi.getId(), true);
   3734             }
   3735         }
   3736 
   3737         final String defaultImiId = mSettings.getSelectedInputMethod();
   3738         if (!TextUtils.isEmpty(defaultImiId)) {
   3739             if (!mMethodMap.containsKey(defaultImiId)) {
   3740                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
   3741                 if (chooseNewDefaultIMELocked()) {
   3742                     updateInputMethodsFromSettingsLocked(true);
   3743                 }
   3744             } else {
   3745                 // Double check that the default IME is certainly enabled.
   3746                 setInputMethodEnabledLocked(defaultImiId, true);
   3747             }
   3748         }
   3749         // Here is not the perfect place to reset the switching controller. Ideally
   3750         // mSwitchingController and mSettings should be able to share the same state.
   3751         // TODO: Make sure that mSwitchingController and mSettings are sharing the
   3752         // the same enabled IMEs list.
   3753         mSwitchingController.resetCircularListLocked(mContext);
   3754     }
   3755 
   3756     // ----------------------------------------------------------------------
   3757 
   3758     private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
   3759         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
   3760         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
   3761                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
   3762                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
   3763         if (!TextUtils.isEmpty(inputMethodId)) {
   3764             intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
   3765         }
   3766         final int userId;
   3767         synchronized (mMethodMap) {
   3768             userId = mSettings.getCurrentUserId();
   3769         }
   3770         mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
   3771     }
   3772 
   3773     private void showConfigureInputMethods() {
   3774         Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
   3775         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
   3776                 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
   3777                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
   3778         mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
   3779     }
   3780 
   3781     private boolean isScreenLocked() {
   3782         return mKeyguardManager != null
   3783                 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
   3784     }
   3785 
   3786     private void showInputMethodMenu(boolean showAuxSubtypes) {
   3787         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
   3788 
   3789         final boolean isScreenLocked = isScreenLocked();
   3790 
   3791         final String lastInputMethodId = mSettings.getSelectedInputMethod();
   3792         int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
   3793         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
   3794 
   3795         synchronized (mMethodMap) {
   3796             final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
   3797                     mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
   3798                             mContext);
   3799             if (immis == null || immis.size() == 0) {
   3800                 return;
   3801             }
   3802 
   3803             hideInputMethodMenuLocked();
   3804 
   3805             final List<ImeSubtypeListItem> imList =
   3806                     mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
   3807                             showAuxSubtypes, isScreenLocked);
   3808 
   3809             if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
   3810                 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
   3811                 if (currentSubtype != null) {
   3812                     final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
   3813                     lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
   3814                             currentImi, currentSubtype.hashCode());
   3815                 }
   3816             }
   3817 
   3818             final int N = imList.size();
   3819             mIms = new InputMethodInfo[N];
   3820             mSubtypeIds = new int[N];
   3821             int checkedItem = 0;
   3822             for (int i = 0; i < N; ++i) {
   3823                 final ImeSubtypeListItem item = imList.get(i);
   3824                 mIms[i] = item.mImi;
   3825                 mSubtypeIds[i] = item.mSubtypeId;
   3826                 if (mIms[i].getId().equals(lastInputMethodId)) {
   3827                     int subtypeId = mSubtypeIds[i];
   3828                     if ((subtypeId == NOT_A_SUBTYPE_ID)
   3829                             || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
   3830                             || (subtypeId == lastInputMethodSubtypeId)) {
   3831                         checkedItem = i;
   3832                     }
   3833                 }
   3834             }
   3835 
   3836             final Context settingsContext = new ContextThemeWrapper(
   3837                     ActivityThread.currentActivityThread().getSystemUiContext(),
   3838                     com.android.internal.R.style.Theme_DeviceDefault_Settings);
   3839 
   3840             mDialogBuilder = new AlertDialog.Builder(settingsContext);
   3841             mDialogBuilder.setOnCancelListener(new OnCancelListener() {
   3842                 @Override
   3843                 public void onCancel(DialogInterface dialog) {
   3844                     hideInputMethodMenu();
   3845                 }
   3846             });
   3847 
   3848             final Context dialogContext = mDialogBuilder.getContext();
   3849             final TypedArray a = dialogContext.obtainStyledAttributes(null,
   3850                     com.android.internal.R.styleable.DialogPreference,
   3851                     com.android.internal.R.attr.alertDialogStyle, 0);
   3852             final Drawable dialogIcon = a.getDrawable(
   3853                     com.android.internal.R.styleable.DialogPreference_dialogIcon);
   3854             a.recycle();
   3855 
   3856             mDialogBuilder.setIcon(dialogIcon);
   3857 
   3858             final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
   3859             final View tv = inflater.inflate(
   3860                     com.android.internal.R.layout.input_method_switch_dialog_title, null);
   3861             mDialogBuilder.setCustomTitle(tv);
   3862 
   3863             // Setup layout for a toggle switch of the hardware keyboard
   3864             mSwitchingDialogTitleView = tv;
   3865             mSwitchingDialogTitleView
   3866                     .findViewById(com.android.internal.R.id.hard_keyboard_section)
   3867                     .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
   3868                             ? View.VISIBLE : View.GONE);
   3869             final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
   3870                     com.android.internal.R.id.hard_keyboard_switch);
   3871             hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
   3872             hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
   3873                 @Override
   3874                 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
   3875                     mSettings.setShowImeWithHardKeyboard(isChecked);
   3876                     // Ensure that the input method dialog is dismissed when changing
   3877                     // the hardware keyboard state.
   3878                     hideInputMethodMenu();
   3879                 }
   3880             });
   3881 
   3882             final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
   3883                     com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
   3884             final OnClickListener choiceListener = new OnClickListener() {
   3885                 @Override
   3886                 public void onClick(final DialogInterface dialog, final int which) {
   3887                     synchronized (mMethodMap) {
   3888                         if (mIms == null || mIms.length <= which || mSubtypeIds == null
   3889                                 || mSubtypeIds.length <= which) {
   3890                             return;
   3891                         }
   3892                         final InputMethodInfo im = mIms[which];
   3893                         int subtypeId = mSubtypeIds[which];
   3894                         adapter.mCheckedItem = which;
   3895                         adapter.notifyDataSetChanged();
   3896                         hideInputMethodMenu();
   3897                         if (im != null) {
   3898                             if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
   3899                                 subtypeId = NOT_A_SUBTYPE_ID;
   3900                             }
   3901                             setInputMethodLocked(im.getId(), subtypeId);
   3902                         }
   3903                     }
   3904                 }
   3905             };
   3906             mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
   3907 
   3908             mSwitchingDialog = mDialogBuilder.create();
   3909             mSwitchingDialog.setCanceledOnTouchOutside(true);
   3910             final Window w = mSwitchingDialog.getWindow();
   3911             final WindowManager.LayoutParams attrs = w.getAttributes();
   3912             w.setType(TYPE_INPUT_METHOD_DIALOG);
   3913             // Use an alternate token for the dialog for that window manager can group the token
   3914             // with other IME windows based on type vs. grouping based on whichever token happens
   3915             // to get selected by the system later on.
   3916             attrs.token = mSwitchingDialogToken;
   3917             attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
   3918             attrs.setTitle("Select input method");
   3919             w.setAttributes(attrs);
   3920             updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
   3921             mSwitchingDialog.show();
   3922         }
   3923     }
   3924 
   3925     private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
   3926         private final LayoutInflater mInflater;
   3927         private final int mTextViewResourceId;
   3928         private final List<ImeSubtypeListItem> mItemsList;
   3929         public int mCheckedItem;
   3930         public ImeSubtypeListAdapter(Context context, int textViewResourceId,
   3931                 List<ImeSubtypeListItem> itemsList, int checkedItem) {
   3932             super(context, textViewResourceId, itemsList);
   3933 
   3934             mTextViewResourceId = textViewResourceId;
   3935             mItemsList = itemsList;
   3936             mCheckedItem = checkedItem;
   3937             mInflater = context.getSystemService(LayoutInflater.class);
   3938         }
   3939 
   3940         @Override
   3941         public View getView(int position, View convertView, ViewGroup parent) {
   3942             final View view = convertView != null ? convertView
   3943                     : mInflater.inflate(mTextViewResourceId, null);
   3944             if (position < 0 || position >= mItemsList.size()) return view;
   3945             final ImeSubtypeListItem item = mItemsList.get(position);
   3946             final CharSequence imeName = item.mImeName;
   3947             final CharSequence subtypeName = item.mSubtypeName;
   3948             final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
   3949             final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
   3950             if (TextUtils.isEmpty(subtypeName)) {
   3951                 firstTextView.setText(imeName);
   3952                 secondTextView.setVisibility(View.GONE);
   3953             } else {
   3954                 firstTextView.setText(subtypeName);
   3955                 secondTextView.setText(imeName);
   3956                 secondTextView.setVisibility(View.VISIBLE);
   3957             }
   3958             final RadioButton radioButton =
   3959                     (RadioButton)view.findViewById(com.android.internal.R.id.radio);
   3960             radioButton.setChecked(position == mCheckedItem);
   3961             return view;
   3962         }
   3963     }
   3964 
   3965     void hideInputMethodMenu() {
   3966         synchronized (mMethodMap) {
   3967             hideInputMethodMenuLocked();
   3968         }
   3969     }
   3970 
   3971     void hideInputMethodMenuLocked() {
   3972         if (DEBUG) Slog.v(TAG, "Hide switching menu");
   3973 
   3974         if (mSwitchingDialog != null) {
   3975             mSwitchingDialog.dismiss();
   3976             mSwitchingDialog = null;
   3977             mSwitchingDialogTitleView = null;
   3978         }
   3979 
   3980         updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
   3981         mDialogBuilder = null;
   3982         mIms = null;
   3983     }
   3984 
   3985     // ----------------------------------------------------------------------
   3986 
   3987     boolean setInputMethodEnabledLocked(String id, boolean enabled) {
   3988         // Make sure this is a valid input method.
   3989         InputMethodInfo imm = mMethodMap.get(id);
   3990         if (imm == null) {
   3991             throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
   3992         }
   3993 
   3994         List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
   3995                 .getEnabledInputMethodsAndSubtypeListLocked();
   3996 
   3997         if (enabled) {
   3998             for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
   3999                 if (pair.first.equals(id)) {
   4000                     // We are enabling this input method, but it is already enabled.
   4001                     // Nothing to do. The previous state was enabled.
   4002                     return true;
   4003                 }
   4004             }
   4005             mSettings.appendAndPutEnabledInputMethodLocked(id, false);
   4006             // Previous state was disabled.
   4007             return false;
   4008         } else {
   4009             StringBuilder builder = new StringBuilder();
   4010             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
   4011                     builder, enabledInputMethodsList, id)) {
   4012                 // Disabled input method is currently selected, switch to another one.
   4013                 final String selId = mSettings.getSelectedInputMethod();
   4014                 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
   4015                     Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
   4016                     resetSelectedInputMethodAndSubtypeLocked("");
   4017                 }
   4018                 // Previous state was enabled.
   4019                 return true;
   4020             } else {
   4021                 // We are disabling the input method but it is already disabled.
   4022                 // Nothing to do.  The previous state was disabled.
   4023                 return false;
   4024             }
   4025         }
   4026     }
   4027 
   4028     private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
   4029             boolean setSubtypeOnly) {
   4030         // Updates to InputMethod are transient in VR mode. Its not included in history.
   4031         final boolean isVrInput = imi != null && imi.isVrOnly();
   4032         if (!isVrInput) {
   4033             // Update the history of InputMethod and Subtype
   4034             mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
   4035         }
   4036 
   4037         mCurUserActionNotificationSequenceNumber =
   4038                 Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
   4039         if (DEBUG) {
   4040             Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
   4041                     + mCurUserActionNotificationSequenceNumber);
   4042         }
   4043 
   4044         if (mCurClient != null && mCurClient.client != null) {
   4045             executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
   4046                     MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
   4047                     mCurUserActionNotificationSequenceNumber, mCurClient));
   4048         }
   4049 
   4050         if (isVrInput) {
   4051             // Updates to InputMethod are transient in VR mode. Any changes to Settings are skipped.
   4052             return;
   4053         }
   4054 
   4055         // Set Subtype here
   4056         if (imi == null || subtypeId < 0) {
   4057             mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
   4058             mCurrentSubtype = null;
   4059         } else {
   4060             if (subtypeId < imi.getSubtypeCount()) {
   4061                 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
   4062                 mSettings.putSelectedSubtype(subtype.hashCode());
   4063                 mCurrentSubtype = subtype;
   4064             } else {
   4065                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
   4066                 // If the subtype is not specified, choose the most applicable one
   4067                 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
   4068             }
   4069         }
   4070 
   4071         if (!setSubtypeOnly) {
   4072             // Set InputMethod here
   4073             mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
   4074         }
   4075     }
   4076 
   4077     private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
   4078         InputMethodInfo imi = mMethodMap.get(newDefaultIme);
   4079         int lastSubtypeId = NOT_A_SUBTYPE_ID;
   4080         // newDefaultIme is empty when there is no candidate for the selected IME.
   4081         if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
   4082             String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
   4083             if (subtypeHashCode != null) {
   4084                 try {
   4085                     lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
   4086                             imi, Integer.parseInt(subtypeHashCode));
   4087                 } catch (NumberFormatException e) {
   4088                     Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
   4089                 }
   4090             }
   4091         }
   4092         setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
   4093     }
   4094 
   4095     // If there are no selected shortcuts, tries finding the most applicable ones.
   4096     private Pair<InputMethodInfo, InputMethodSubtype>
   4097             findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
   4098         List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
   4099         InputMethodInfo mostApplicableIMI = null;
   4100         InputMethodSubtype mostApplicableSubtype = null;
   4101         boolean foundInSystemIME = false;
   4102 
   4103         // Search applicable subtype for each InputMethodInfo
   4104         for (InputMethodInfo imi: imis) {
   4105             final String imiId = imi.getId();
   4106             if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
   4107                 continue;
   4108             }
   4109             InputMethodSubtype subtype = null;
   4110             final List<InputMethodSubtype> enabledSubtypes =
   4111                     mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
   4112             // 1. Search by the current subtype's locale from enabledSubtypes.
   4113             if (mCurrentSubtype != null) {
   4114                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4115                         mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
   4116             }
   4117             // 2. Search by the system locale from enabledSubtypes.
   4118             // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
   4119             if (subtype == null) {
   4120                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4121                         mRes, enabledSubtypes, mode, null, true);
   4122             }
   4123             final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
   4124                     InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
   4125             final ArrayList<InputMethodSubtype> subtypesForSearch =
   4126                     overridingImplicitlyEnabledSubtypes.isEmpty()
   4127                             ? InputMethodUtils.getSubtypes(imi)
   4128                             : overridingImplicitlyEnabledSubtypes;
   4129             // 4. Search by the current subtype's locale from all subtypes.
   4130             if (subtype == null && mCurrentSubtype != null) {
   4131                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4132                         mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
   4133             }
   4134             // 5. Search by the system locale from all subtypes.
   4135             // 6. Search the first enabled subtype matched with mode from all subtypes.
   4136             if (subtype == null) {
   4137                 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4138                         mRes, subtypesForSearch, mode, null, true);
   4139             }
   4140             if (subtype != null) {
   4141                 if (imiId.equals(mCurMethodId)) {
   4142                     // The current input method is the most applicable IME.
   4143                     mostApplicableIMI = imi;
   4144                     mostApplicableSubtype = subtype;
   4145                     break;
   4146                 } else if (!foundInSystemIME) {
   4147                     // The system input method is 2nd applicable IME.
   4148                     mostApplicableIMI = imi;
   4149                     mostApplicableSubtype = subtype;
   4150                     if ((imi.getServiceInfo().applicationInfo.flags
   4151                             & ApplicationInfo.FLAG_SYSTEM) != 0) {
   4152                         foundInSystemIME = true;
   4153                     }
   4154                 }
   4155             }
   4156         }
   4157         if (DEBUG) {
   4158             if (mostApplicableIMI != null) {
   4159                 Slog.w(TAG, "Most applicable shortcut input method was:"
   4160                         + mostApplicableIMI.getId());
   4161                 if (mostApplicableSubtype != null) {
   4162                     Slog.w(TAG, "Most applicable shortcut input method subtype was:"
   4163                             + "," + mostApplicableSubtype.getMode() + ","
   4164                             + mostApplicableSubtype.getLocale());
   4165                 }
   4166             }
   4167         }
   4168         if (mostApplicableIMI != null) {
   4169             return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
   4170         } else {
   4171             return null;
   4172         }
   4173     }
   4174 
   4175     /**
   4176      * @return Return the current subtype of this input method.
   4177      */
   4178     @Override
   4179     public InputMethodSubtype getCurrentInputMethodSubtype() {
   4180         // TODO: Make this work even for non-current users?
   4181         if (!calledFromValidUser()) {
   4182             return null;
   4183         }
   4184         synchronized (mMethodMap) {
   4185             return getCurrentInputMethodSubtypeLocked();
   4186         }
   4187     }
   4188 
   4189     private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
   4190         if (mCurMethodId == null) {
   4191             return null;
   4192         }
   4193         final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
   4194         final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
   4195         if (imi == null || imi.getSubtypeCount() == 0) {
   4196             return null;
   4197         }
   4198         if (!subtypeIsSelected || mCurrentSubtype == null
   4199                 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
   4200             int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
   4201             if (subtypeId == NOT_A_SUBTYPE_ID) {
   4202                 // If there are no selected subtypes, the framework will try to find
   4203                 // the most applicable subtype from explicitly or implicitly enabled
   4204                 // subtypes.
   4205                 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
   4206                         mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
   4207                 // If there is only one explicitly or implicitly enabled subtype,
   4208                 // just returns it.
   4209                 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
   4210                     mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
   4211                 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
   4212                     mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4213                             mRes, explicitlyOrImplicitlyEnabledSubtypes,
   4214                             InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
   4215                     if (mCurrentSubtype == null) {
   4216                         mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
   4217                                 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
   4218                                 true);
   4219                     }
   4220                 }
   4221             } else {
   4222                 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
   4223             }
   4224         }
   4225         return mCurrentSubtype;
   4226     }
   4227 
   4228     // TODO: We should change the return type from List to List<Parcelable>
   4229     @SuppressWarnings("rawtypes")
   4230     @Override
   4231     public List getShortcutInputMethodsAndSubtypes() {
   4232         synchronized (mMethodMap) {
   4233             ArrayList<Object> ret = new ArrayList<>();
   4234             if (mShortcutInputMethodsAndSubtypes.size() == 0) {
   4235                 // If there are no selected shortcut subtypes, the framework will try to find
   4236                 // the most applicable subtype from all subtypes whose mode is
   4237                 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
   4238                 Pair<InputMethodInfo, InputMethodSubtype> info =
   4239                     findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
   4240                             InputMethodUtils.SUBTYPE_MODE_VOICE);
   4241                 if (info != null) {
   4242                     ret.add(info.first);
   4243                     ret.add(info.second);
   4244                 }
   4245                 return ret;
   4246             }
   4247             for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
   4248                 ret.add(imi);
   4249                 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
   4250                     ret.add(subtype);
   4251                 }
   4252             }
   4253             return ret;
   4254         }
   4255     }
   4256 
   4257     @Override
   4258     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
   4259         // TODO: Make this work even for non-current users?
   4260         if (!calledFromValidUser()) {
   4261             return false;
   4262         }
   4263         synchronized (mMethodMap) {
   4264             if (subtype != null && mCurMethodId != null) {
   4265                 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
   4266                 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
   4267                 if (subtypeId != NOT_A_SUBTYPE_ID) {
   4268                     setInputMethodLocked(mCurMethodId, subtypeId);
   4269                     return true;
   4270                 }
   4271             }
   4272             return false;
   4273         }
   4274     }
   4275 
   4276     // TODO: Cache the state for each user and reset when the cached user is removed.
   4277     private static class InputMethodFileManager {
   4278         private static final String SYSTEM_PATH = "system";
   4279         private static final String INPUT_METHOD_PATH = "inputmethod";
   4280         private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
   4281         private static final String NODE_SUBTYPES = "subtypes";
   4282         private static final String NODE_SUBTYPE = "subtype";
   4283         private static final String NODE_IMI = "imi";
   4284         private static final String ATTR_ID = "id";
   4285         private static final String ATTR_LABEL = "label";
   4286         private static final String ATTR_ICON = "icon";
   4287         private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
   4288         private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
   4289         private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
   4290         private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
   4291         private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
   4292         private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
   4293         private static final String ATTR_IS_ASCII_CAPABLE = "isAsciiCapable";
   4294         private final AtomicFile mAdditionalInputMethodSubtypeFile;
   4295         private final HashMap<String, InputMethodInfo> mMethodMap;
   4296         private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
   4297                 new HashMap<>();
   4298         public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
   4299             if (methodMap == null) {
   4300                 throw new NullPointerException("methodMap is null");
   4301             }
   4302             mMethodMap = methodMap;
   4303             final File systemDir = userId == UserHandle.USER_SYSTEM
   4304                     ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
   4305                     : Environment.getUserSystemDirectory(userId);
   4306             final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
   4307             if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
   4308                 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
   4309             }
   4310             final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
   4311             mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile, "input-subtypes");
   4312             if (!subtypeFile.exists()) {
   4313                 // If "subtypes.xml" doesn't exist, create a blank file.
   4314                 writeAdditionalInputMethodSubtypes(
   4315                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
   4316             } else {
   4317                 readAdditionalInputMethodSubtypes(
   4318                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
   4319             }
   4320         }
   4321 
   4322         private void deleteAllInputMethodSubtypes(String imiId) {
   4323             synchronized (mMethodMap) {
   4324                 mAdditionalSubtypesMap.remove(imiId);
   4325                 writeAdditionalInputMethodSubtypes(
   4326                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
   4327             }
   4328         }
   4329 
   4330         public void addInputMethodSubtypes(
   4331                 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
   4332             synchronized (mMethodMap) {
   4333                 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
   4334                 final int N = additionalSubtypes.length;
   4335                 for (int i = 0; i < N; ++i) {
   4336                     final InputMethodSubtype subtype = additionalSubtypes[i];
   4337                     if (!subtypes.contains(subtype)) {
   4338                         subtypes.add(subtype);
   4339                     } else {
   4340                         Slog.w(TAG, "Duplicated subtype definition found: "
   4341                                 + subtype.getLocale() + ", " + subtype.getMode());
   4342                     }
   4343                 }
   4344                 mAdditionalSubtypesMap.put(imi.getId(), subtypes);
   4345                 writeAdditionalInputMethodSubtypes(
   4346                         mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
   4347             }
   4348         }
   4349 
   4350         public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
   4351             synchronized (mMethodMap) {
   4352                 return mAdditionalSubtypesMap;
   4353             }
   4354         }
   4355 
   4356         private static void writeAdditionalInputMethodSubtypes(
   4357                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
   4358                 HashMap<String, InputMethodInfo> methodMap) {
   4359             // Safety net for the case that this function is called before methodMap is set.
   4360             final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
   4361             FileOutputStream fos = null;
   4362             try {
   4363                 fos = subtypesFile.startWrite();
   4364                 final XmlSerializer out = new FastXmlSerializer();
   4365                 out.setOutput(fos, StandardCharsets.UTF_8.name());
   4366                 out.startDocument(null, true);
   4367                 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
   4368                 out.startTag(null, NODE_SUBTYPES);
   4369                 for (String imiId : allSubtypes.keySet()) {
   4370                     if (isSetMethodMap && !methodMap.containsKey(imiId)) {
   4371                         Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
   4372                         continue;
   4373                     }
   4374                     out.startTag(null, NODE_IMI);
   4375                     out.attribute(null, ATTR_ID, imiId);
   4376                     final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
   4377                     final int N = subtypesList.size();
   4378                     for (int i = 0; i < N; ++i) {
   4379                         final InputMethodSubtype subtype = subtypesList.get(i);
   4380                         out.startTag(null, NODE_SUBTYPE);
   4381                         if (subtype.hasSubtypeId()) {
   4382                             out.attribute(null, ATTR_IME_SUBTYPE_ID,
   4383                                     String.valueOf(subtype.getSubtypeId()));
   4384                         }
   4385                         out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
   4386                         out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
   4387                         out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
   4388                         out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
   4389                                 subtype.getLanguageTag());
   4390                         out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
   4391                         out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
   4392                         out.attribute(null, ATTR_IS_AUXILIARY,
   4393                                 String.valueOf(subtype.isAuxiliary() ? 1 : 0));
   4394                         out.attribute(null, ATTR_IS_ASCII_CAPABLE,
   4395                                 String.valueOf(subtype.isAsciiCapable() ? 1 : 0));
   4396                         out.endTag(null, NODE_SUBTYPE);
   4397                     }
   4398                     out.endTag(null, NODE_IMI);
   4399                 }
   4400                 out.endTag(null, NODE_SUBTYPES);
   4401                 out.endDocument();
   4402                 subtypesFile.finishWrite(fos);
   4403             } catch (java.io.IOException e) {
   4404                 Slog.w(TAG, "Error writing subtypes", e);
   4405                 if (fos != null) {
   4406                     subtypesFile.failWrite(fos);
   4407                 }
   4408             }
   4409         }
   4410 
   4411         private static void readAdditionalInputMethodSubtypes(
   4412                 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
   4413             if (allSubtypes == null || subtypesFile == null) return;
   4414             allSubtypes.clear();
   4415             try (final FileInputStream fis = subtypesFile.openRead()) {
   4416                 final XmlPullParser parser = Xml.newPullParser();
   4417                 parser.setInput(fis, StandardCharsets.UTF_8.name());
   4418                 int type = parser.getEventType();
   4419                 // Skip parsing until START_TAG
   4420                 while ((type = parser.next()) != XmlPullParser.START_TAG
   4421                         && type != XmlPullParser.END_DOCUMENT) {}
   4422                 String firstNodeName = parser.getName();
   4423                 if (!NODE_SUBTYPES.equals(firstNodeName)) {
   4424                     throw new XmlPullParserException("Xml doesn't start with subtypes");
   4425                 }
   4426                 final int depth =parser.getDepth();
   4427                 String currentImiId = null;
   4428                 ArrayList<InputMethodSubtype> tempSubtypesArray = null;
   4429                 while (((type = parser.next()) != XmlPullParser.END_TAG
   4430                         || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
   4431                     if (type != XmlPullParser.START_TAG)
   4432                         continue;
   4433                     final String nodeName = parser.getName();
   4434                     if (NODE_IMI.equals(nodeName)) {
   4435                         currentImiId = parser.getAttributeValue(null, ATTR_ID);
   4436                         if (TextUtils.isEmpty(currentImiId)) {
   4437                             Slog.w(TAG, "Invalid imi id found in subtypes.xml");
   4438                             continue;
   4439                         }
   4440                         tempSubtypesArray = new ArrayList<>();
   4441                         allSubtypes.put(currentImiId, tempSubtypesArray);
   4442                     } else if (NODE_SUBTYPE.equals(nodeName)) {
   4443                         if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
   4444                             Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
   4445                             continue;
   4446                         }
   4447                         final int icon = Integer.parseInt(
   4448                                 parser.getAttributeValue(null, ATTR_ICON));
   4449                         final int label = Integer.parseInt(
   4450                                 parser.getAttributeValue(null, ATTR_LABEL));
   4451                         final String imeSubtypeLocale =
   4452                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
   4453                         final String languageTag =
   4454                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
   4455                         final String imeSubtypeMode =
   4456                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
   4457                         final String imeSubtypeExtraValue =
   4458                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
   4459                         final boolean isAuxiliary = "1".equals(String.valueOf(
   4460                                 parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
   4461                         final boolean isAsciiCapable = "1".equals(String.valueOf(
   4462                                 parser.getAttributeValue(null, ATTR_IS_ASCII_CAPABLE)));
   4463                         final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder()
   4464                                 .setSubtypeNameResId(label)
   4465                                 .setSubtypeIconResId(icon)
   4466                                 .setSubtypeLocale(imeSubtypeLocale)
   4467                                 .setLanguageTag(languageTag)
   4468                                 .setSubtypeMode(imeSubtypeMode)
   4469                                 .setSubtypeExtraValue(imeSubtypeExtraValue)
   4470                                 .setIsAuxiliary(isAuxiliary)
   4471                                 .setIsAsciiCapable(isAsciiCapable);
   4472                         final String subtypeIdString =
   4473                                 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_ID);
   4474                         if (subtypeIdString != null) {
   4475                             builder.setSubtypeId(Integer.parseInt(subtypeIdString));
   4476                         }
   4477                         tempSubtypesArray.add(builder.build());
   4478                     }
   4479                 }
   4480             } catch (XmlPullParserException | IOException | NumberFormatException e) {
   4481                 Slog.w(TAG, "Error reading subtypes", e);
   4482                 return;
   4483             }
   4484         }
   4485     }
   4486 
   4487     private static final class LocalServiceImpl implements InputMethodManagerInternal {
   4488         @NonNull
   4489         private final Handler mHandler;
   4490 
   4491         LocalServiceImpl(@NonNull final Handler handler) {
   4492             mHandler = handler;
   4493         }
   4494 
   4495         @Override
   4496         public void setInteractive(boolean interactive) {
   4497             // Do everything in handler so as not to block the caller.
   4498             mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INTERACTIVE,
   4499                     interactive ? 1 : 0, 0));
   4500         }
   4501 
   4502         @Override
   4503         public void switchInputMethod(boolean forwardDirection) {
   4504             // Do everything in handler so as not to block the caller.
   4505             mHandler.sendMessage(mHandler.obtainMessage(MSG_SWITCH_IME,
   4506                     forwardDirection ? 1 : 0, 0));
   4507         }
   4508 
   4509         @Override
   4510         public void hideCurrentInputMethod() {
   4511             mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
   4512             mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
   4513         }
   4514 
   4515         @Override
   4516         public void startVrInputMethodNoCheck(@Nullable ComponentName componentName) {
   4517             mHandler.sendMessage(mHandler.obtainMessage(MSG_START_VR_INPUT, componentName));
   4518         }
   4519     }
   4520 
   4521     private static String imeWindowStatusToString(final int imeWindowVis) {
   4522         final StringBuilder sb = new StringBuilder();
   4523         boolean first = true;
   4524         if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
   4525             sb.append("Active");
   4526             first = false;
   4527         }
   4528         if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
   4529             if (!first) {
   4530                 sb.append("|");
   4531             }
   4532             sb.append("Visible");
   4533         }
   4534         return sb.toString();
   4535     }
   4536 
   4537     @Override
   4538     public IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
   4539             @Nullable Uri contentUri, @Nullable String packageName) {
   4540         if (!calledFromValidUser()) {
   4541             return null;
   4542         }
   4543 
   4544         if (token == null) {
   4545             throw new NullPointerException("token");
   4546         }
   4547         if (packageName == null) {
   4548             throw new NullPointerException("packageName");
   4549         }
   4550         if (contentUri == null) {
   4551             throw new NullPointerException("contentUri");
   4552         }
   4553         final String contentUriScheme = contentUri.getScheme();
   4554         if (!"content".equals(contentUriScheme)) {
   4555             throw new InvalidParameterException("contentUri must have content scheme");
   4556         }
   4557 
   4558         synchronized (mMethodMap) {
   4559             final int uid = Binder.getCallingUid();
   4560             if (mCurMethodId == null) {
   4561                 return null;
   4562             }
   4563             if (mCurToken != token) {
   4564                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
   4565                         + " token=" + token);
   4566                 return null;
   4567             }
   4568             // We cannot simply distinguish a bad IME that reports an arbitrary package name from
   4569             // an unfortunate IME whose internal state is already obsolete due to the asynchronous
   4570             // nature of our system.  Let's compare it with our internal record.
   4571             if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
   4572                 Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
   4573                     + mCurAttribute.packageName + " packageName=" + packageName);
   4574                 return null;
   4575             }
   4576             // This user ID can never bee spoofed.
   4577             final int imeUserId = UserHandle.getUserId(uid);
   4578             // This user ID can never bee spoofed.
   4579             final int appUserId = UserHandle.getUserId(mCurClient.uid);
   4580             // This user ID may be invalid if "contentUri" embedded an invalid user ID.
   4581             final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
   4582                     imeUserId);
   4583             final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
   4584             // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
   4585             // actually has the right to grant a read permission for "contentUriWithoutUserId" that
   4586             // is claimed to belong to "contentUriOwnerUserId".  For example, specifying random
   4587             // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
   4588             // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
   4589             // actually allowed to "uid", which is guaranteed to be the IME's one.
   4590             return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
   4591                     packageName, contentUriOwnerUserId, appUserId);
   4592         }
   4593     }
   4594 
   4595     @Override
   4596     public void reportFullscreenMode(IBinder token, boolean fullscreen) {
   4597         if (!calledFromValidUser()) {
   4598             return;
   4599         }
   4600         synchronized (mMethodMap) {
   4601             if (!calledWithValidToken(token)) {
   4602                 return;
   4603             }
   4604             if (mCurClient != null && mCurClient.client != null) {
   4605                 mInFullscreenMode = fullscreen;
   4606                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
   4607                         MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
   4608             }
   4609         }
   4610     }
   4611 
   4612     @Override
   4613     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
   4614         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
   4615 
   4616         IInputMethod method;
   4617         ClientState client;
   4618         ClientState focusedWindowClient;
   4619 
   4620         final Printer p = new PrintWriterPrinter(pw);
   4621 
   4622         synchronized (mMethodMap) {
   4623             p.println("Current Input Method Manager state:");
   4624             int N = mMethodList.size();
   4625             p.println("  Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount
   4626                     + " mBindInstantServiceAllowed=" + mBindInstantServiceAllowed);
   4627             for (int i=0; i<N; i++) {
   4628                 InputMethodInfo info = mMethodList.get(i);
   4629                 p.println("  InputMethod #" + i + ":");
   4630                 info.dump(p, "    ");
   4631             }
   4632             p.println("  Clients:");
   4633             for (ClientState ci : mClients.values()) {
   4634                 p.println("  Client " + ci + ":");
   4635                 p.println("    client=" + ci.client);
   4636                 p.println("    inputContext=" + ci.inputContext);
   4637                 p.println("    sessionRequested=" + ci.sessionRequested);
   4638                 p.println("    curSession=" + ci.curSession);
   4639             }
   4640             p.println("  mCurMethodId=" + mCurMethodId);
   4641             client = mCurClient;
   4642             p.println("  mCurClient=" + client + " mCurSeq=" + mCurSeq);
   4643             p.println("  mCurFocusedWindow=" + mCurFocusedWindow
   4644                     + " softInputMode=" +
   4645                     InputMethodClient.softInputModeToString(mCurFocusedWindowSoftInputMode)
   4646                     + " client=" + mCurFocusedWindowClient);
   4647             focusedWindowClient = mCurFocusedWindowClient;
   4648             p.println("  mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
   4649                     + " mBoundToMethod=" + mBoundToMethod);
   4650             p.println("  mCurToken=" + mCurToken);
   4651             p.println("  mCurIntent=" + mCurIntent);
   4652             method = mCurMethod;
   4653             p.println("  mCurMethod=" + mCurMethod);
   4654             p.println("  mEnabledSession=" + mEnabledSession);
   4655             p.println("  mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
   4656             p.println("  mShowRequested=" + mShowRequested
   4657                     + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
   4658                     + " mShowForced=" + mShowForced
   4659                     + " mInputShown=" + mInputShown);
   4660             p.println("  mInFullscreenMode=" + mInFullscreenMode);
   4661             p.println("  mCurUserActionNotificationSequenceNumber="
   4662                     + mCurUserActionNotificationSequenceNumber);
   4663             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
   4664             p.println("  mSettingsObserver=" + mSettingsObserver);
   4665             p.println("  mSwitchingController:");
   4666             mSwitchingController.dump(p);
   4667             p.println("  mSettings:");
   4668             mSettings.dumpLocked(p, "    ");
   4669 
   4670             p.println("  mStartInputHistory:");
   4671             mStartInputHistory.dump(pw, "   ");
   4672         }
   4673 
   4674         p.println(" ");
   4675         if (client != null) {
   4676             pw.flush();
   4677             try {
   4678                 TransferPipe.dumpAsync(client.client.asBinder(), fd, args);
   4679             } catch (IOException | RemoteException e) {
   4680                 p.println("Failed to dump input method client: " + e);
   4681             }
   4682         } else {
   4683             p.println("No input method client.");
   4684         }
   4685 
   4686         if (focusedWindowClient != null && client != focusedWindowClient) {
   4687             p.println(" ");
   4688             p.println("Warning: Current input method client doesn't match the last focused. "
   4689                     + "window.");
   4690             p.println("Dumping input method client in the last focused window just in case.");
   4691             p.println(" ");
   4692             pw.flush();
   4693             try {
   4694                 TransferPipe.dumpAsync(focusedWindowClient.client.asBinder(), fd, args);
   4695             } catch (IOException | RemoteException e) {
   4696                 p.println("Failed to dump input method client in focused window: " + e);
   4697             }
   4698         }
   4699 
   4700         p.println(" ");
   4701         if (method != null) {
   4702             pw.flush();
   4703             try {
   4704                 TransferPipe.dumpAsync(method.asBinder(), fd, args);
   4705             } catch (IOException | RemoteException e) {
   4706                 p.println("Failed to dump input method service: " + e);
   4707             }
   4708         } else {
   4709             p.println("No input method service.");
   4710         }
   4711     }
   4712 
   4713     @BinderThread
   4714     @Override
   4715     public void onShellCommand(@Nullable FileDescriptor in, @Nullable FileDescriptor out,
   4716             @Nullable FileDescriptor err,
   4717             @NonNull String[] args, @Nullable ShellCallback callback,
   4718             @NonNull ResultReceiver resultReceiver) throws RemoteException {
   4719         new ShellCommandImpl(this).exec(
   4720                 this, in, out, err, args, callback, resultReceiver);
   4721     }
   4722 
   4723     private static final class ShellCommandImpl extends ShellCommand {
   4724         @NonNull
   4725         final InputMethodManagerService mService;
   4726 
   4727         ShellCommandImpl(InputMethodManagerService service) {
   4728             mService = service;
   4729         }
   4730 
   4731         @BinderThread
   4732         @ShellCommandResult
   4733         @Override
   4734         public int onCommand(@Nullable String cmd) {
   4735             if ("refresh_debug_properties".equals(cmd)) {
   4736                 return refreshDebugProperties();
   4737             }
   4738             if ("set-bind-instant-service-allowed".equals(cmd)) {
   4739                 return setBindInstantServiceAllowed();
   4740             }
   4741 
   4742             // For existing "adb shell ime <command>".
   4743             if ("ime".equals(cmd)) {
   4744                 final String imeCommand = getNextArg();
   4745                 if (imeCommand == null || "help".equals(imeCommand) || "-h".equals(imeCommand)) {
   4746                     onImeCommandHelp();
   4747                     return ShellCommandResult.SUCCESS;
   4748                 }
   4749                 switch (imeCommand) {
   4750                     case "list":
   4751                         return mService.handleShellCommandListInputMethods(this);
   4752                     case "enable":
   4753                         return mService.handleShellCommandEnableDisableInputMethod(this, true);
   4754                     case "disable":
   4755                         return mService.handleShellCommandEnableDisableInputMethod(this, false);
   4756                     case "set":
   4757                         return mService.handleShellCommandSetInputMethod(this);
   4758                     case "reset":
   4759                         return mService.handleShellCommandResetInputMethod(this);
   4760                     default:
   4761                         getOutPrintWriter().println("Unknown command: " + imeCommand);
   4762                         return ShellCommandResult.FAILURE;
   4763                 }
   4764             }
   4765 
   4766             return handleDefaultCommands(cmd);
   4767         }
   4768 
   4769         @BinderThread
   4770         @ShellCommandResult
   4771         private int setBindInstantServiceAllowed() {
   4772             return mService.handleSetBindInstantServiceAllowed(this);
   4773         }
   4774 
   4775         @BinderThread
   4776         @ShellCommandResult
   4777         private int refreshDebugProperties() {
   4778             DebugFlags.FLAG_OPTIMIZE_START_INPUT.refresh();
   4779             return ShellCommandResult.SUCCESS;
   4780         }
   4781 
   4782         @BinderThread
   4783         @Override
   4784         public void onHelp() {
   4785             try (PrintWriter pw = getOutPrintWriter()) {
   4786                 pw.println("InputMethodManagerService commands:");
   4787                 pw.println("  help");
   4788                 pw.println("    Prints this help text.");
   4789                 pw.println("  dump [options]");
   4790                 pw.println("    Synonym of dumpsys.");
   4791                 pw.println("  ime <command> [options]");
   4792                 pw.println("    Manipulate IMEs.  Run \"ime help\" for details.");
   4793                 pw.println("  set-bind-instant-service-allowed true|false ");
   4794                 pw.println("    Set whether binding to services provided by instant apps is "
   4795                         + "allowed.");
   4796             }
   4797         }
   4798 
   4799         private void onImeCommandHelp() {
   4800             try (IndentingPrintWriter pw =
   4801                          new IndentingPrintWriter(getOutPrintWriter(), "  ", 100)) {
   4802                 pw.println("ime <command>:");
   4803                 pw.increaseIndent();
   4804 
   4805                 pw.println("list [-a] [-s]");
   4806                 pw.increaseIndent();
   4807                 pw.println("prints all enabled input methods.");
   4808                 pw.increaseIndent();
   4809                 pw.println("-a: see all input methods");
   4810                 pw.println("-s: only a single summary line of each");
   4811                 pw.decreaseIndent();
   4812                 pw.decreaseIndent();
   4813 
   4814                 pw.println("enable <ID>");
   4815                 pw.increaseIndent();
   4816                 pw.println("allows the given input method ID to be used.");
   4817                 pw.decreaseIndent();
   4818 
   4819                 pw.println("disable <ID>");
   4820                 pw.increaseIndent();
   4821                 pw.println("disallows the given input method ID to be used.");
   4822                 pw.decreaseIndent();
   4823 
   4824                 pw.println("set <ID>");
   4825                 pw.increaseIndent();
   4826                 pw.println("switches to the given input method ID.");
   4827                 pw.decreaseIndent();
   4828 
   4829                 pw.println("reset");
   4830                 pw.increaseIndent();
   4831                 pw.println("reset currently selected/enabled IMEs to the default ones as if "
   4832                         + "the device is initially booted with the current locale.");
   4833                 pw.decreaseIndent();
   4834 
   4835                 pw.decreaseIndent();
   4836             }
   4837         }
   4838     }
   4839 
   4840     // ----------------------------------------------------------------------
   4841     // Shell command handlers:
   4842 
   4843     /**
   4844      * Handles {@code adb shell cmd input_method set-bind-instant-service-allowed}.
   4845      *
   4846      * @param shellCommand {@link ShellCommand} object that is handling this command.
   4847      * @return Exit code of the command.
   4848      */
   4849     @BinderThread
   4850     @RequiresPermission(android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE)
   4851     @ShellCommandResult
   4852     private int handleSetBindInstantServiceAllowed(@NonNull ShellCommand shellCommand) {
   4853         final String allowedString = shellCommand.getNextArgRequired();
   4854         if (allowedString == null) {
   4855             shellCommand.getErrPrintWriter().println("Error: no true/false specified");
   4856             return ShellCommandResult.FAILURE;
   4857         }
   4858         final boolean allowed = Boolean.parseBoolean(allowedString);
   4859         synchronized (mMethodMap) {
   4860             if (mContext.checkCallingOrSelfPermission(
   4861                     android.Manifest.permission.MANAGE_BIND_INSTANT_SERVICE)
   4862                     != PackageManager.PERMISSION_GRANTED) {
   4863                 shellCommand.getErrPrintWriter().print(
   4864                         "Caller must have MANAGE_BIND_INSTANT_SERVICE permission");
   4865                 return ShellCommandResult.FAILURE;
   4866             }
   4867 
   4868             if (mBindInstantServiceAllowed == allowed) {
   4869                 // Nothing to do.
   4870                 return ShellCommandResult.SUCCESS;
   4871             }
   4872             mBindInstantServiceAllowed = allowed;
   4873 
   4874             // Rebuild everything.
   4875             final long ident = Binder.clearCallingIdentity();
   4876             try {
   4877                 // Reset the current IME
   4878                 resetSelectedInputMethodAndSubtypeLocked(null);
   4879                 // Also reset the settings of the current IME
   4880                 mSettings.putSelectedInputMethod(null);
   4881                 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
   4882                 updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
   4883             } finally {
   4884                 Binder.restoreCallingIdentity(ident);
   4885             }
   4886         }
   4887         return ShellCommandResult.SUCCESS;
   4888     }
   4889 
   4890     /**
   4891      * Handles {@code adb shell ime list}.
   4892      * @param shellCommand {@link ShellCommand} object that is handling this command.
   4893      * @return Exit code of the command.
   4894      */
   4895     @BinderThread
   4896     @ShellCommandResult
   4897     private int handleShellCommandListInputMethods(@NonNull ShellCommand shellCommand) {
   4898         boolean all = false;
   4899         boolean brief = false;
   4900         while (true) {
   4901             final String nextOption = shellCommand.getNextOption();
   4902             if (nextOption == null) {
   4903                 break;
   4904             }
   4905             switch (nextOption) {
   4906                 case "-a":
   4907                     all = true;
   4908                     break;
   4909                 case "-s":
   4910                     brief = true;
   4911                     break;
   4912             }
   4913         }
   4914         final List<InputMethodInfo> methods = all ?
   4915                 getInputMethodList() : getEnabledInputMethodList();
   4916         final PrintWriter pr = shellCommand.getOutPrintWriter();
   4917         final Printer printer = x -> pr.println(x);
   4918         final int N = methods.size();
   4919         for (int i = 0; i < N; ++i) {
   4920             if (brief) {
   4921                 pr.println(methods.get(i).getId());
   4922             } else {
   4923                 pr.print(methods.get(i).getId()); pr.println(":");
   4924                 methods.get(i).dump(printer, "  ");
   4925             }
   4926         }
   4927         return ShellCommandResult.SUCCESS;
   4928     }
   4929 
   4930     /**
   4931      * Handles {@code adb shell ime enable} and {@code adb shell ime disable}.
   4932      * @param shellCommand {@link ShellCommand} object that is handling this command.
   4933      * @param enabled {@code true} if the command was {@code adb shell ime enable}.
   4934      * @return Exit code of the command.
   4935      */
   4936     @BinderThread
   4937     @ShellCommandResult
   4938     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
   4939     private int handleShellCommandEnableDisableInputMethod(
   4940             @NonNull ShellCommand shellCommand, boolean enabled) {
   4941         if (!calledFromValidUser()) {
   4942             shellCommand.getErrPrintWriter().print(
   4943                     "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL");
   4944             return ShellCommandResult.FAILURE;
   4945         }
   4946         final String id = shellCommand.getNextArgRequired();
   4947 
   4948         final boolean previouslyEnabled;
   4949         synchronized (mMethodMap) {
   4950             if (mContext.checkCallingOrSelfPermission(
   4951                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
   4952                     != PackageManager.PERMISSION_GRANTED) {
   4953                 shellCommand.getErrPrintWriter().print(
   4954                         "Caller must have WRITE_SECURE_SETTINGS permission");
   4955                 throw new SecurityException(
   4956                         "Requires permission "
   4957                                 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   4958             }
   4959 
   4960             final long ident = Binder.clearCallingIdentity();
   4961             try {
   4962                 previouslyEnabled = setInputMethodEnabledLocked(id, enabled);
   4963             } finally {
   4964                 Binder.restoreCallingIdentity(ident);
   4965             }
   4966         }
   4967         final PrintWriter pr = shellCommand.getOutPrintWriter();
   4968         pr.print("Input method ");
   4969         pr.print(id);
   4970         pr.print(": ");
   4971         pr.print((enabled == previouslyEnabled) ? "already " : "now ");
   4972         pr.println(enabled ? "enabled" : "disabled");
   4973         return ShellCommandResult.SUCCESS;
   4974     }
   4975 
   4976     /**
   4977      * Handles {@code adb shell ime set}.
   4978      * @param shellCommand {@link ShellCommand} object that is handling this command.
   4979      * @return Exit code of the command.
   4980      */
   4981     @BinderThread
   4982     @ShellCommandResult
   4983     private int handleShellCommandSetInputMethod(@NonNull ShellCommand shellCommand) {
   4984         final String id = shellCommand.getNextArgRequired();
   4985         setInputMethod(null, id);
   4986         final PrintWriter pr = shellCommand.getOutPrintWriter();
   4987         pr.print("Input method ");
   4988         pr.print(id);
   4989         pr.println("  selected");
   4990         return ShellCommandResult.SUCCESS;
   4991     }
   4992 
   4993     /**
   4994      * Handles {@code adb shell ime reset-ime}.
   4995      * @param shellCommand {@link ShellCommand} object that is handling this command.
   4996      * @return Exit code of the command.
   4997      */
   4998     @BinderThread
   4999     @ShellCommandResult
   5000     @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
   5001     private int handleShellCommandResetInputMethod(@NonNull ShellCommand shellCommand) {
   5002         if (!calledFromValidUser()) {
   5003             shellCommand.getErrPrintWriter().print(
   5004                     "Must be called from the foreground user or with INTERACT_ACROSS_USERS_FULL");
   5005             return ShellCommandResult.FAILURE;
   5006         }
   5007         synchronized (mMethodMap) {
   5008             if (mContext.checkCallingOrSelfPermission(
   5009                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
   5010                     != PackageManager.PERMISSION_GRANTED) {
   5011                 shellCommand.getErrPrintWriter().print(
   5012                         "Caller must have WRITE_SECURE_SETTINGS permission");
   5013                 throw new SecurityException(
   5014                         "Requires permission "
   5015                                 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
   5016             }
   5017             final String nextIme;
   5018             final List<InputMethodInfo> nextEnabledImes;
   5019             final long ident = Binder.clearCallingIdentity();
   5020             try {
   5021                 synchronized (mMethodMap) {
   5022                     hideCurrentInputLocked(0, null);
   5023                     unbindCurrentMethodLocked(false);
   5024                     // Reset the current IME
   5025                     resetSelectedInputMethodAndSubtypeLocked(null);
   5026                     // Also reset the settings of the current IME
   5027                     mSettings.putSelectedInputMethod(null);
   5028                     // Disable all enabled IMEs.
   5029                     {
   5030                         final ArrayList<InputMethodInfo> enabledImes =
   5031                                 mSettings.getEnabledInputMethodListLocked();
   5032                         final int N = enabledImes.size();
   5033                         for (int i = 0; i < N; ++i) {
   5034                             setInputMethodEnabledLocked(enabledImes.get(i).getId(), false);
   5035                         }
   5036                     }
   5037                     // Re-enable with default enabled IMEs.
   5038                     {
   5039                         final ArrayList<InputMethodInfo> defaultEnabledIme =
   5040                                 InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList);
   5041                         final int N = defaultEnabledIme.size();
   5042                         for (int i = 0; i < N; ++i) {
   5043                             setInputMethodEnabledLocked(defaultEnabledIme.get(i).getId(), true);
   5044                         }
   5045                     }
   5046                     updateInputMethodsFromSettingsLocked(true /* enabledMayChange */);
   5047                     InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
   5048                             mSettings.getEnabledInputMethodListLocked(),
   5049                             mSettings.getCurrentUserId(),
   5050                             mContext.getBasePackageName());
   5051                     nextIme = mSettings.getSelectedInputMethod();
   5052                     nextEnabledImes = getEnabledInputMethodList();
   5053                 }
   5054             } finally {
   5055                 Binder.restoreCallingIdentity(ident);
   5056             }
   5057             final PrintWriter pr = shellCommand.getOutPrintWriter();
   5058             pr.println("Reset current and enabled IMEs");
   5059             pr.println("Newly selected IME:");
   5060             pr.print("  "); pr.println(nextIme);
   5061             pr.println("Newly enabled IMEs:");
   5062             {
   5063                 final int N = nextEnabledImes.size();
   5064                 for (int i = 0; i < N; ++i) {
   5065                     pr.print("  ");
   5066                     pr.println(nextEnabledImes.get(i).getId());
   5067                 }
   5068             }
   5069             return ShellCommandResult.SUCCESS;
   5070         }
   5071     }
   5072 }
   5073