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