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