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
7 * http://www.apache.org/licenses/LICENSE-2.0
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
16 package com.android.server;
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;
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.os.HandlerCaller;
32 import com.android.internal.os.SomeArgs;
33 import com.android.internal.os.TransferPipe;
34 import com.android.internal.util.FastXmlSerializer;
35 import com.android.internal.view.IInputContext;
36 import com.android.internal.view.IInputMethod;
37 import com.android.internal.view.IInputMethodClient;
38 import com.android.internal.view.IInputMethodManager;
39 import com.android.internal.view.IInputMethodSession;
40 import com.android.internal.view.IInputSessionCallback;
41 import com.android.internal.view.InputBindResult;
42 import com.android.internal.view.InputMethodClient;
43 import com.android.server.statusbar.StatusBarManagerService;
45 import org.xmlpull.v1.XmlPullParser;
46 import org.xmlpull.v1.XmlPullParserException;
47 import org.xmlpull.v1.XmlSerializer;
49 import android.annotation.IntDef;
50 import android.annotation.NonNull;
51 import android.annotation.Nullable;
52 import android.annotation.UserIdInt;
53 import android.app.ActivityManager;
54 import android.app.ActivityManagerInternal;
55 import android.app.AlertDialog;
56 import android.app.AppGlobals;
57 import android.app.AppOpsManager;
58 import android.app.KeyguardManager;
59 import android.app.Notification;
60 import android.app.NotificationManager;
61 import android.app.PendingIntent;
62 import android.content.BroadcastReceiver;
63 import android.content.ComponentName;
64 import android.content.ContentProvider;
65 import android.content.ContentResolver;
66 import android.content.Context;
67 import android.content.DialogInterface;
68 import android.content.DialogInterface.OnCancelListener;
69 import android.content.DialogInterface.OnClickListener;
70 import android.content.Intent;
71 import android.content.IntentFilter;
72 import android.content.ServiceConnection;
73 import android.content.pm.ApplicationInfo;
74 import android.content.pm.IPackageManager;
75 import android.content.pm.PackageManager;
76 import android.content.pm.ResolveInfo;
77 import android.content.pm.ServiceInfo;
78 import android.content.res.Configuration;
79 import android.content.res.Resources;
80 import android.content.res.TypedArray;
81 import android.database.ContentObserver;
82 import android.graphics.drawable.Drawable;
83 import android.hardware.input.InputManagerInternal;
84 import android.inputmethodservice.InputMethodService;
85 import android.net.Uri;
86 import android.os.Binder;
87 import android.os.Bundle;
88 import android.os.Debug;
89 import android.os.Environment;
90 import android.os.Handler;
91 import android.os.IBinder;
92 import android.os.IInterface;
93 import android.os.Message;
94 import android.os.LocaleList;
95 import android.os.Parcel;
96 import android.os.Process;
97 import android.os.RemoteException;
98 import android.os.ResultReceiver;
99 import android.os.ServiceManager;
100 import android.os.SystemClock;
101 import android.os.UserHandle;
102 import android.os.UserManager;
103 import android.provider.Settings;
104 import android.text.TextUtils;
105 import android.text.style.SuggestionSpan;
106 import android.util.ArrayMap;
107 import android.util.ArraySet;
108 import android.util.AtomicFile;
109 import android.util.EventLog;
110 import android.util.LruCache;
111 import android.util.Pair;
112 import android.util.PrintWriterPrinter;
113 import android.util.Printer;
114 import android.util.Slog;
115 import android.util.Xml;
116 import android.view.ContextThemeWrapper;
117 import android.view.IWindowManager;
118 import android.view.InputChannel;
119 import android.view.LayoutInflater;
120 import android.view.View;
121 import android.view.ViewGroup;
122 import android.view.Window;
123 import android.view.WindowManager;
124 import android.view.WindowManagerInternal;
125 import android.view.inputmethod.EditorInfo;
126 import android.view.inputmethod.InputBinding;
127 import android.view.inputmethod.InputConnection;
128 import android.view.inputmethod.InputConnectionInspector;
129 import android.view.inputmethod.InputMethod;
130 import android.view.inputmethod.InputMethodInfo;
131 import android.view.inputmethod.InputMethodManager;
132 import android.view.inputmethod.InputMethodManagerInternal;
133 import android.view.inputmethod.InputMethodSubtype;
134 import android.view.inputmethod.InputMethodSubtype.InputMethodSubtypeBuilder;
135 import android.widget.ArrayAdapter;
136 import android.widget.CompoundButton;
137 import android.widget.CompoundButton.OnCheckedChangeListener;
138 import android.widget.RadioButton;
139 import android.widget.Switch;
140 import android.widget.TextView;
141 import android.widget.Toast;
144 import java.io.FileDescriptor;
145 import java.io.FileInputStream;
146 import java.io.FileOutputStream;
147 import java.io.IOException;
148 import java.io.PrintWriter;
149 import java.lang.annotation.Retention;
150 import java.nio.charset.StandardCharsets;
151 import java.security.InvalidParameterException;
152 import java.text.SimpleDateFormat;
153 import java.util.ArrayList;
154 import java.util.Collections;
155 import java.util.Date;
156 import java.util.HashMap;
157 import java.util.List;
158 import java.util.Locale;
159 import java.util.WeakHashMap;
160 import java.util.concurrent.atomic.AtomicInteger;
163 * This class provides a system service that manages input methods.
165 public class InputMethodManagerService extends IInputMethodManager.Stub
166 implements ServiceConnection, Handler.Callback {
167 static final boolean DEBUG = false;
168 static final boolean DEBUG_RESTORE = DEBUG || false;
169 static final String TAG = "InputMethodManagerService";
171 static final int MSG_SHOW_IM_SUBTYPE_PICKER = 1;
172 static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 2;
173 static final int MSG_SHOW_IM_CONFIG = 3;
175 static final int MSG_UNBIND_INPUT = 1000;
176 static final int MSG_BIND_INPUT = 1010;
177 static final int MSG_SHOW_SOFT_INPUT = 1020;
178 static final int MSG_HIDE_SOFT_INPUT = 1030;
179 static final int MSG_HIDE_CURRENT_INPUT_METHOD = 1035;
180 static final int MSG_ATTACH_TOKEN = 1040;
181 static final int MSG_CREATE_SESSION = 1050;
183 static final int MSG_START_INPUT = 2000;
185 static final int MSG_UNBIND_CLIENT = 3000;
186 static final int MSG_BIND_CLIENT = 3010;
187 static final int MSG_SET_ACTIVE = 3020;
188 static final int MSG_SET_INTERACTIVE = 3030;
189 static final int MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER = 3040;
190 static final int MSG_REPORT_FULLSCREEN_MODE = 3045;
191 static final int MSG_SWITCH_IME = 3050;
193 static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
195 static final int MSG_SYSTEM_UNLOCK_USER = 5000;
197 static final long TIME_TO_RECONNECT = 3 * 1000;
199 static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
201 private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
202 private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
205 @IntDef({HardKeyboardBehavior.WIRELESS_AFFORDANCE, HardKeyboardBehavior.WIRED_AFFORDANCE})
206 private @interface HardKeyboardBehavior {
207 int WIRELESS_AFFORDANCE = 0;
208 int WIRED_AFFORDANCE = 1;
211 final Context mContext;
212 final Resources mRes;
213 final Handler mHandler;
214 final InputMethodSettings mSettings;
215 final SettingsObserver mSettingsObserver;
216 final IWindowManager mIWindowManager;
217 final WindowManagerInternal mWindowManagerInternal;
218 final HandlerCaller mCaller;
219 final boolean mHasFeature;
220 private InputMethodFileManager mFileManager;
221 private final HardKeyboardListener mHardKeyboardListener;
222 private final AppOpsManager mAppOpsManager;
223 private final UserManager mUserManager;
225 final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1, -1);
227 // All known input methods. mMethodMap also serves as the global
228 // lock for this class.
229 final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
230 final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<>();
231 private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
232 new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
233 private final InputMethodSubtypeSwitchingController mSwitchingController;
236 * Tracks how many times {@link #mMethodMap} was updated.
238 @GuardedBy("mMethodMap")
239 private int mMethodMapUpdateCount = 0;
241 // Used to bring IME service up to visible adjustment while it is being shown.
242 final ServiceConnection mVisibleConnection = new ServiceConnection() {
243 @Override public void onServiceConnected(ComponentName name, IBinder service) {
246 @Override public void onServiceDisconnected(ComponentName name) {
249 boolean mVisibleBound = false;
251 // Ongoing notification
252 private NotificationManager mNotificationManager;
253 private KeyguardManager mKeyguardManager;
254 private @Nullable StatusBarManagerService mStatusBar;
255 private Notification.Builder mImeSwitcherNotification;
256 private PendingIntent mImeSwitchPendingIntent;
257 private boolean mShowOngoingImeSwitcherForPhones;
258 private boolean mNotificationShown;
260 static class SessionState {
261 final ClientState client;
262 final IInputMethod method;
264 IInputMethodSession session;
265 InputChannel channel;
268 public String toString() {
269 return "SessionState{uid " + client.uid + " pid " + client.pid
270 + " method " + Integer.toHexString(
271 System.identityHashCode(method))
272 + " session " + Integer.toHexString(
273 System.identityHashCode(session))
274 + " channel " + channel
278 SessionState(ClientState _client, IInputMethod _method,
279 IInputMethodSession _session, InputChannel _channel) {
287 static final class ClientState {
288 final IInputMethodClient client;
289 final IInputContext inputContext;
292 final InputBinding binding;
294 boolean sessionRequested;
295 SessionState curSession;
298 public String toString() {
299 return "ClientState{" + Integer.toHexString(
300 System.identityHashCode(this)) + " uid " + uid
301 + " pid " + pid + "}";
304 ClientState(IInputMethodClient _client, IInputContext _inputContext,
305 int _uid, int _pid) {
307 inputContext = _inputContext;
310 binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
314 final HashMap<IBinder, ClientState> mClients = new HashMap<>();
317 * Set once the system is ready to run third party code.
319 boolean mSystemReady;
322 * Id obtained with {@link InputMethodInfo#getId()} for the currently selected input method.
323 * method. This is to be synchronized with the secure settings keyed with
324 * {@link Settings.Secure#DEFAULT_INPUT_METHOD}.
326 * <p>This can be transiently {@code null} when the system is re-initializing input method
327 * settings, e.g., the system locale is just changed.</p>
329 * <p>Note that {@link #mCurId} is used to track which IME is being connected to
330 * {@link InputMethodManagerService}.</p>
338 * The current binding sequence number, incremented every time there is
339 * a new bind performed.
344 * The client that is currently bound to an input method.
346 ClientState mCurClient;
349 * The last window token that we confirmed to be focused. This is always updated upon reports
350 * from the input method client. If the window state is already changed before the report is
351 * handled, this field just keeps the last value.
353 IBinder mCurFocusedWindow;
356 * {@link WindowManager.LayoutParams#softInputMode} of {@link #mCurFocusedWindow}.
358 * @see #mCurFocusedWindow
360 int mCurFocusedWindowSoftInputMode;
363 * The client by which {@link #mCurFocusedWindow} was reported. Used only for debugging.
365 ClientState mCurFocusedWindowClient;
368 * The input context last provided by the current client.
370 IInputContext mCurInputContext;
373 * The missing method flags for the input context last provided by the current client.
375 * @see android.view.inputmethod.InputConnectionInspector.MissingMethodFlags
377 int mCurInputContextMissingMethods;
380 * The attributes last provided by the current client.
382 EditorInfo mCurAttribute;
385 * Id obtained with {@link InputMethodInfo#getId()} for the input method that we are currently
386 * connected to or in the process of connecting to.
388 * <p>This can be {@code null} when no input method is connected.</p>
396 * The current subtype of the current input method.
398 private InputMethodSubtype mCurrentSubtype;
400 // This list contains the pairs of InputMethodInfo and InputMethodSubtype.
401 private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
402 mShortcutInputMethodsAndSubtypes = new HashMap<>();
404 // Was the keyguard locked when this client became current?
405 private boolean mCurClientInKeyguard;
408 * Set to true if our ServiceConnection is currently actively bound to
409 * a service (whether or not we have gotten its IBinder back yet).
411 boolean mHaveConnection;
414 * Set if the client has asked for the input method to be shown.
416 boolean mShowRequested;
419 * Set if we were explicitly told to show the input method.
421 boolean mShowExplicitlyRequested;
424 * Set if we were forced to be shown.
429 * Set if we last told the input method to show itself.
434 * {@code true} if the current input method is in fullscreen mode.
436 boolean mInFullscreenMode;
439 * The Intent used to connect to the current input method.
444 * The token we have made for the currently active input method, to
445 * identify it in the future.
450 * If non-null, this is the input method service we are currently connected
453 IInputMethod mCurMethod;
456 * Time that we last initiated a bind to the input method, to determine
457 * if we should try to disconnect and reconnect to it.
462 * Have we called mCurMethod.bindInput()?
464 boolean mBoundToMethod;
467 * Currently enabled session. Only touched by service thread, not
468 * protected by a lock.
470 SessionState mEnabledSession;
473 * True if the device is currently interactive with user. The value is true initially.
475 boolean mIsInteractive = true;
477 int mCurUserActionNotificationSequenceNumber = 0;
479 int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
482 * A set of status bits regarding the active IME.
484 * <p>This value is a combination of following two bits:</p>
486 * <dt>{@link InputMethodService#IME_ACTIVE}</dt>
488 * If this bit is ON, connected IME is ready to accept touch/key events.
490 * <dt>{@link InputMethodService#IME_VISIBLE}</dt>
492 * If this bit is ON, some of IME view, e.g. software input, candidate view, is visible.
495 * <em>Do not update this value outside of setImeWindowStatus.</em>
499 private AlertDialog.Builder mDialogBuilder;
500 private AlertDialog mSwitchingDialog;
501 private IBinder mSwitchingDialogToken = new Binder();
502 private View mSwitchingDialogTitleView;
503 private Toast mSubtypeSwitchedByShortCutToast;
504 private InputMethodInfo[] mIms;
505 private int[] mSubtypeIds;
506 private LocaleList mLastSystemLocales;
507 private boolean mShowImeWithHardKeyboard;
508 private boolean mAccessibilityRequestingNoSoftKeyboard;
509 private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
510 private final IPackageManager mIPackageManager;
511 private final String mSlotIme;
512 @HardKeyboardBehavior
513 private final int mHardKeyboardBehavior;
516 * Internal state snapshot when {@link #MSG_START_INPUT} message is about to be posted to the
517 * internal message queue. Any subsequent state change inside {@link InputMethodManagerService}
518 * will not affect those tasks that are already posted.
520 * <p>Posting {@link #MSG_START_INPUT} message basically means that
521 * {@link InputMethodService#doStartInput(InputConnection, EditorInfo, boolean)} will be called
522 * back in the current IME process shortly, which will also affect what the current IME starts
523 * receiving from {@link InputMethodService#getCurrentInputConnection()}. In other words, this
524 * snapshot will be taken every time when {@link InputMethodManagerService} is initiating a new
525 * logical input session between the client application and the current IME.</p>
527 * <p>Be careful to not keep strong references to this object forever, which can prevent
528 * {@link StartInputInfo#mImeToken} and {@link StartInputInfo#mTargetWindow} from being GC-ed.
531 private static class StartInputInfo {
532 private static final AtomicInteger sSequenceNumber = new AtomicInteger(0);
534 final int mSequenceNumber;
535 final long mTimestamp;
536 final long mWallTime;
538 final IBinder mImeToken;
541 // @InputMethodClient.StartInputReason
542 final int mStartInputReason;
543 final boolean mRestarting;
545 final IBinder mTargetWindow;
547 final EditorInfo mEditorInfo;
548 final int mTargetWindowSoftInputMode;
549 final int mClientBindSequenceNumber;
551 StartInputInfo(@NonNull IBinder imeToken, @NonNull String imeId,
552 /* @InputMethodClient.StartInputReason */ int startInputReason, boolean restarting,
553 @Nullable IBinder targetWindow, @NonNull EditorInfo editorInfo,
554 int targetWindowSoftInputMode, int clientBindSequenceNumber) {
555 mSequenceNumber = sSequenceNumber.getAndIncrement();
556 mTimestamp = SystemClock.uptimeMillis();
557 mWallTime = System.currentTimeMillis();
558 mImeToken = imeToken;
560 mStartInputReason = startInputReason;
561 mRestarting = restarting;
562 mTargetWindow = targetWindow;
563 mEditorInfo = editorInfo;
564 mTargetWindowSoftInputMode = targetWindowSoftInputMode;
565 mClientBindSequenceNumber = clientBindSequenceNumber;
569 @GuardedBy("mMethodMap")
570 private final WeakHashMap<IBinder, StartInputInfo> mStartInputMap = new WeakHashMap<>();
573 * A ring buffer to store the history of {@link StartInputInfo}.
575 private static final class StartInputHistory {
577 * Entry size for non low-RAM devices.
579 * <p>TODO: Consider to follow what other system services have been doing to manage
580 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
582 private final static int ENTRY_SIZE_FOR_HIGH_RAM_DEVICE = 16;
585 * Entry size for non low-RAM devices.
587 * <p>TODO: Consider to follow what other system services have been doing to manage
588 * constants (e.g. {@link android.provider.Settings.Global#ACTIVITY_MANAGER_CONSTANTS}).</p>
590 private final static int ENTRY_SIZE_FOR_LOW_RAM_DEVICE = 5;
592 private static int getEntrySize() {
593 if (ActivityManager.isLowRamDeviceStatic()) {
594 return ENTRY_SIZE_FOR_LOW_RAM_DEVICE;
596 return ENTRY_SIZE_FOR_HIGH_RAM_DEVICE;
601 * Backing store for the ring bugger.
603 private final Entry[] mEntries = new Entry[getEntrySize()];
606 * An index of {@link #mEntries}, to which next {@link #addEntry(StartInputInfo)} should
609 private int mNextIndex = 0;
612 * Recyclable entry to store the information in {@link StartInputInfo}.
614 private static final class Entry {
619 String mImeTokenString;
622 /* @InputMethodClient.StartInputReason */
623 int mStartInputReason;
626 String mTargetWindowString;
628 EditorInfo mEditorInfo;
629 int mTargetWindowSoftInputMode;
630 int mClientBindSequenceNumber;
632 Entry(@NonNull StartInputInfo original) {
636 void set(@NonNull StartInputInfo original) {
637 mSequenceNumber = original.mSequenceNumber;
638 mTimestamp = original.mTimestamp;
639 mWallTime = original.mWallTime;
640 // Intentionally convert to String so as not to keep a strong reference to a Binder
642 mImeTokenString = String.valueOf(original.mImeToken);
643 mImeId = original.mImeId;
644 mStartInputReason = original.mStartInputReason;
645 mRestarting = original.mRestarting;
646 // Intentionally convert to String so as not to keep a strong reference to a Binder
648 mTargetWindowString = String.valueOf(original.mTargetWindow);
649 mEditorInfo = original.mEditorInfo;
650 mTargetWindowSoftInputMode = original.mTargetWindowSoftInputMode;
651 mClientBindSequenceNumber = original.mClientBindSequenceNumber;
656 * Add a new entry and discard the oldest entry as needed.
657 * @param info {@lin StartInputInfo} to be added.
659 void addEntry(@NonNull StartInputInfo info) {
660 final int index = mNextIndex;
661 if (mEntries[index] == null) {
662 mEntries[index] = new Entry(info);
664 mEntries[index].set(info);
666 mNextIndex = (mNextIndex + 1) % mEntries.length;
669 void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
670 final SimpleDateFormat dataFormat =
671 new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS", Locale.US);
673 for (int i = 0; i < mEntries.length; ++i) {
674 final Entry entry = mEntries[(i + mNextIndex) % mEntries.length];
679 pw.println("StartInput #" + entry.mSequenceNumber + ":");
682 pw.println(" time=" + dataFormat.format(new Date(entry.mWallTime))
683 + " (timestamp=" + entry.mTimestamp + ")"
685 + InputMethodClient.getStartInputReason(entry.mStartInputReason)
686 + " restarting=" + entry.mRestarting);
689 pw.println(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
692 pw.println(" targetWin=" + entry.mTargetWindowString
693 + " [" + entry.mEditorInfo.packageName + "]"
694 + " clientBindSeq=" + entry.mClientBindSequenceNumber);
697 pw.println(" softInputMode=" + InputMethodClient.softInputModeToString(
698 entry.mTargetWindowSoftInputMode));
701 pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
702 + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
703 + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
704 + " fieldName=" + entry.mEditorInfo.fieldName
705 + " actionId=" + entry.mEditorInfo.actionId
706 + " actionLabel=" + entry.mEditorInfo.actionLabel);
711 @GuardedBy("mMethodMap")
713 private final StartInputHistory mStartInputHistory = new StartInputHistory();
715 class SettingsObserver extends ContentObserver {
717 boolean mRegistered = false;
719 String mLastEnabled = "";
722 * <em>This constructor must be called within the lock.</em>
724 SettingsObserver(Handler handler) {
728 public void registerContentObserverLocked(@UserIdInt int userId) {
729 if (mRegistered && mUserId == userId) {
732 ContentResolver resolver = mContext.getContentResolver();
734 mContext.getContentResolver().unregisterContentObserver(this);
737 if (mUserId != userId) {
741 resolver.registerContentObserver(Settings.Secure.getUriFor(
742 Settings.Secure.DEFAULT_INPUT_METHOD), false, this, userId);
743 resolver.registerContentObserver(Settings.Secure.getUriFor(
744 Settings.Secure.ENABLED_INPUT_METHODS), false, this, userId);
745 resolver.registerContentObserver(Settings.Secure.getUriFor(
746 Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this, userId);
747 resolver.registerContentObserver(Settings.Secure.getUriFor(
748 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD), false, this, userId);
749 resolver.registerContentObserver(Settings.Secure.getUriFor(
750 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE), false, this, userId);
754 @Override public void onChange(boolean selfChange, Uri uri) {
755 final Uri showImeUri = Settings.Secure.getUriFor(
756 Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD);
757 final Uri accessibilityRequestingNoImeUri = Settings.Secure.getUriFor(
758 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
759 synchronized (mMethodMap) {
760 if (showImeUri.equals(uri)) {
761 updateKeyboardFromSettingsLocked();
762 } else if (accessibilityRequestingNoImeUri.equals(uri)) {
763 mAccessibilityRequestingNoSoftKeyboard = Settings.Secure.getIntForUser(
764 mContext.getContentResolver(),
765 Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE,
767 if (mAccessibilityRequestingNoSoftKeyboard) {
768 final boolean showRequested = mShowRequested;
769 hideCurrentInputLocked(0, null);
770 mShowRequested = showRequested;
771 } else if (mShowRequested) {
772 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
775 boolean enabledChanged = false;
776 String newEnabled = mSettings.getEnabledInputMethodsStr();
777 if (!mLastEnabled.equals(newEnabled)) {
778 mLastEnabled = newEnabled;
779 enabledChanged = true;
781 updateInputMethodsFromSettingsLocked(enabledChanged);
787 public String toString() {
788 return "SettingsObserver{mUserId=" + mUserId + " mRegistered=" + mRegistered
789 + " mLastEnabled=" + mLastEnabled + "}";
793 class ImmsBroadcastReceiver extends BroadcastReceiver {
795 public void onReceive(Context context, Intent intent) {
796 final String action = intent.getAction();
797 if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
798 hideInputMethodMenu();
799 // No need to update mIsInteractive
801 } else if (Intent.ACTION_USER_ADDED.equals(action)
802 || Intent.ACTION_USER_REMOVED.equals(action)) {
803 updateCurrentProfileIds();
805 } else if (Intent.ACTION_SETTING_RESTORED.equals(action)) {
806 final String name = intent.getStringExtra(Intent.EXTRA_SETTING_NAME);
807 if (Settings.Secure.ENABLED_INPUT_METHODS.equals(name)) {
808 final String prevValue = intent.getStringExtra(
809 Intent.EXTRA_SETTING_PREVIOUS_VALUE);
810 final String newValue = intent.getStringExtra(
811 Intent.EXTRA_SETTING_NEW_VALUE);
812 restoreEnabledInputMethods(mContext, prevValue, newValue);
814 } else if (Intent.ACTION_LOCALE_CHANGED.equals(action)) {
815 onActionLocaleChanged();
817 Slog.w(TAG, "Unexpected intent " + intent);
823 * Handles {@link Intent#ACTION_LOCALE_CHANGED}.
825 * <p>Note: For historical reasons, {@link Intent#ACTION_LOCALE_CHANGED} has been sent to all
826 * the users. We should ignore this event if this is about any background user's locale.</p>
828 * <p>Caution: This method must not be called when system is not ready.</p>
830 void onActionLocaleChanged() {
831 synchronized (mMethodMap) {
832 final LocaleList possibleNewLocale = mRes.getConfiguration().getLocales();
833 if (possibleNewLocale != null && possibleNewLocale.equals(mLastSystemLocales)) {
836 buildInputMethodListLocked(true);
837 // If the locale is changed, needs to reset the default ime
838 resetDefaultImeLocked(mContext);
839 updateFromSettingsLocked(true);
840 mLastSystemLocales = possibleNewLocale;
844 // Apply the results of a restore operation to the set of enabled IMEs. Note that this
845 // does not attempt to validate on the fly with any installed device policy, so must only
846 // be run in the context of initial device setup.
848 // TODO: Move this method to InputMethodUtils with adding unit tests.
849 static void restoreEnabledInputMethods(Context context, String prevValue, String newValue) {
851 Slog.i(TAG, "Restoring enabled input methods:");
852 Slog.i(TAG, "prev=" + prevValue);
853 Slog.i(TAG, " new=" + newValue);
855 // 'new' is the just-restored state, 'prev' is what was in settings prior to the restore
856 ArrayMap<String, ArraySet<String>> prevMap =
857 InputMethodUtils.parseInputMethodsAndSubtypesString(prevValue);
858 ArrayMap<String, ArraySet<String>> newMap =
859 InputMethodUtils.parseInputMethodsAndSubtypesString(newValue);
861 // Merge the restored ime+subtype enabled states into the live state
862 for (ArrayMap.Entry<String, ArraySet<String>> entry : newMap.entrySet()) {
863 final String imeId = entry.getKey();
864 ArraySet<String> prevSubtypes = prevMap.get(imeId);
865 if (prevSubtypes == null) {
866 prevSubtypes = new ArraySet<>(2);
867 prevMap.put(imeId, prevSubtypes);
869 prevSubtypes.addAll(entry.getValue());
872 final String mergedImesAndSubtypesString =
873 InputMethodUtils.buildInputMethodsAndSubtypesString(prevMap);
875 Slog.i(TAG, "Merged IME string:");
876 Slog.i(TAG, " " + mergedImesAndSubtypesString);
878 Settings.Secure.putString(context.getContentResolver(),
879 Settings.Secure.ENABLED_INPUT_METHODS, mergedImesAndSubtypesString);
882 final class MyPackageMonitor extends PackageMonitor {
884 * Set of packages to be monitored.
886 * <p>No need to include packages because of direct-boot unaware IMEs since we always rescan
887 * all the packages when the user is unlocked, and direct-boot awareness will not be changed
888 * dynamically unless the entire package is updated, which also always triggers package
891 @GuardedBy("mMethodMap")
892 private ArraySet<String> mPackagesToMonitorComponentChange = new ArraySet<>();
894 @GuardedBy("mMethodMap")
895 void clearPackagesToMonitorComponentChangeLocked() {
896 mPackagesToMonitorComponentChange.clear();
899 @GuardedBy("mMethodMap")
900 final void addPackageToMonitorComponentChangeLocked(@NonNull String packageName) {
901 mPackagesToMonitorComponentChange.add(packageName);
904 private boolean isChangingPackagesOfCurrentUser() {
905 final int userId = getChangingUserId();
906 final boolean retval = userId == mSettings.getCurrentUserId();
909 Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
916 public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
917 if (!isChangingPackagesOfCurrentUser()) {
920 synchronized (mMethodMap) {
921 String curInputMethodId = mSettings.getSelectedInputMethod();
922 final int N = mMethodList.size();
923 if (curInputMethodId != null) {
924 for (int i=0; i<N; i++) {
925 InputMethodInfo imi = mMethodList.get(i);
926 if (imi.getId().equals(curInputMethodId)) {
927 for (String pkg : packages) {
928 if (imi.getPackageName().equals(pkg)) {
932 resetSelectedInputMethodAndSubtypeLocked("");
933 chooseNewDefaultIMELocked();
945 public boolean onPackageChanged(String packageName, int uid, String[] components) {
946 // If this package is in the watch list, we want to check it.
947 synchronized (mMethodMap) {
948 return mPackagesToMonitorComponentChange.contains(packageName);
953 public void onSomePackagesChanged() {
954 if (!isChangingPackagesOfCurrentUser()) {
957 synchronized (mMethodMap) {
958 InputMethodInfo curIm = null;
959 String curInputMethodId = mSettings.getSelectedInputMethod();
960 final int N = mMethodList.size();
961 if (curInputMethodId != null) {
962 for (int i=0; i<N; i++) {
963 InputMethodInfo imi = mMethodList.get(i);
964 final String imiId = imi.getId();
965 if (imiId.equals(curInputMethodId)) {
969 int change = isPackageDisappearing(imi.getPackageName());
970 if (isPackageModified(imi.getPackageName())) {
971 mFileManager.deleteAllInputMethodSubtypes(imiId);
973 if (change == PACKAGE_TEMPORARY_CHANGE
974 || change == PACKAGE_PERMANENT_CHANGE) {
975 Slog.i(TAG, "Input method uninstalled, disabling: "
976 + imi.getComponent());
977 setInputMethodEnabledLocked(imi.getId(), false);
982 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
984 boolean changed = false;
987 int change = isPackageDisappearing(curIm.getPackageName());
988 if (change == PACKAGE_TEMPORARY_CHANGE
989 || change == PACKAGE_PERMANENT_CHANGE) {
990 ServiceInfo si = null;
992 si = mIPackageManager.getServiceInfo(
993 curIm.getComponent(), 0, mSettings.getCurrentUserId());
994 } catch (RemoteException ex) {
997 // Uh oh, current input method is no longer around!
998 // Pick another one...
999 Slog.i(TAG, "Current input method removed: " + curInputMethodId);
1000 updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
1001 if (!chooseNewDefaultIMELocked()) {
1004 Slog.i(TAG, "Unsetting current input method");
1005 resetSelectedInputMethodAndSubtypeLocked("");
1011 if (curIm == null) {
1012 // We currently don't have a default input method... is
1013 // one now available?
1014 changed = chooseNewDefaultIMELocked();
1015 } else if (!changed && isPackageModified(curIm.getPackageName())) {
1016 // Even if the current input method is still available, mCurrentSubtype could
1017 // be obsolete when the package is modified in practice.
1022 updateFromSettingsLocked(false);
1028 private static final class MethodCallback extends IInputSessionCallback.Stub {
1029 private final InputMethodManagerService mParentIMMS;
1030 private final IInputMethod mMethod;
1031 private final InputChannel mChannel;
1033 MethodCallback(InputMethodManagerService imms, IInputMethod method,
1034 InputChannel channel) {
1041 public void sessionCreated(IInputMethodSession session) {
1042 long ident = Binder.clearCallingIdentity();
1044 mParentIMMS.onSessionCreated(mMethod, session, mChannel);
1046 Binder.restoreCallingIdentity(ident);
1051 private class HardKeyboardListener
1052 implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
1054 public void onHardKeyboardStatusChange(boolean available) {
1055 mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
1056 available ? 1 : 0));
1059 public void handleHardKeyboardStatusChange(boolean available) {
1061 Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
1063 synchronized(mMethodMap) {
1064 if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
1065 && mSwitchingDialog.isShowing()) {
1066 mSwitchingDialogTitleView.findViewById(
1067 com.android.internal.R.id.hard_keyboard_section).setVisibility(
1068 available ? View.VISIBLE : View.GONE);
1074 public static final class Lifecycle extends SystemService {
1075 private InputMethodManagerService mService;
1077 public Lifecycle(Context context) {
1079 mService = new InputMethodManagerService(context);
1083 public void onStart() {
1084 LocalServices.addService(InputMethodManagerInternal.class,
1085 new LocalServiceImpl(mService.mHandler));
1086 publishBinderService(Context.INPUT_METHOD_SERVICE, mService);
1090 public void onSwitchUser(@UserIdInt int userHandle) {
1091 // Called on ActivityManager thread.
1092 // TODO: Dispatch this to a worker thread as needed.
1093 mService.onSwitchUser(userHandle);
1097 public void onBootPhase(int phase) {
1098 // Called on ActivityManager thread.
1099 // TODO: Dispatch this to a worker thread as needed.
1100 if (phase == SystemService.PHASE_ACTIVITY_MANAGER_READY) {
1101 StatusBarManagerService statusBarService = (StatusBarManagerService) ServiceManager
1102 .getService(Context.STATUS_BAR_SERVICE);
1103 mService.systemRunning(statusBarService);
1108 public void onUnlockUser(final @UserIdInt int userHandle) {
1109 // Called on ActivityManager thread.
1110 mService.mHandler.sendMessage(mService.mHandler.obtainMessage(MSG_SYSTEM_UNLOCK_USER,
1111 userHandle /* arg1 */, 0 /* arg2 */));
1115 void onUnlockUser(@UserIdInt int userId) {
1116 synchronized(mMethodMap) {
1117 final int currentUserId = mSettings.getCurrentUserId();
1119 Slog.d(TAG, "onUnlockUser: userId=" + userId + " curUserId=" + currentUserId);
1121 if (userId != currentUserId) {
1124 mSettings.switchCurrentUser(currentUserId, !mSystemReady);
1126 // We need to rebuild IMEs.
1127 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
1128 updateInputMethodsFromSettingsLocked(true /* enabledChanged */);
1133 void onSwitchUser(@UserIdInt int userId) {
1134 synchronized (mMethodMap) {
1135 switchUserLocked(userId);
1139 public InputMethodManagerService(Context context) {
1140 mIPackageManager = AppGlobals.getPackageManager();
1142 mRes = context.getResources();
1143 mHandler = new Handler(this);
1144 // Note: SettingsObserver doesn't register observers in its constructor.
1145 mSettingsObserver = new SettingsObserver(mHandler);
1146 mIWindowManager = IWindowManager.Stub.asInterface(
1147 ServiceManager.getService(Context.WINDOW_SERVICE));
1148 mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
1149 mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
1151 public void executeMessage(Message msg) {
1154 }, true /*asyncHandler*/);
1155 mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
1156 mUserManager = mContext.getSystemService(UserManager.class);
1157 mHardKeyboardListener = new HardKeyboardListener();
1158 mHasFeature = context.getPackageManager().hasSystemFeature(
1159 PackageManager.FEATURE_INPUT_METHODS);
1160 mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
1161 mHardKeyboardBehavior = mContext.getResources().getInteger(
1162 com.android.internal.R.integer.config_externalHardKeyboardBehavior);
1164 Bundle extras = new Bundle();
1165 extras.putBoolean(Notification.EXTRA_ALLOW_DURING_SETUP, true);
1166 mImeSwitcherNotification = new Notification.Builder(mContext)
1167 .setSmallIcon(com.android.internal.R.drawable.ic_notification_ime_default)
1171 .setCategory(Notification.CATEGORY_SYSTEM)
1172 .setColor(com.android.internal.R.color.system_notification_accent_color);
1174 Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
1175 mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
1177 mShowOngoingImeSwitcherForPhones = false;
1179 mNotificationShown = false;
1182 userId = ActivityManager.getService().getCurrentUser().id;
1183 } catch (RemoteException e) {
1184 Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
1187 // mSettings should be created before buildInputMethodListLocked
1188 mSettings = new InputMethodSettings(
1189 mRes, context.getContentResolver(), mMethodMap, mMethodList, userId, !mSystemReady);
1191 updateCurrentProfileIds();
1192 mFileManager = new InputMethodFileManager(mMethodMap, userId);
1193 mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
1194 mSettings, context);
1197 private void resetDefaultImeLocked(Context context) {
1198 // Do not reset the default (current) IME when it is a 3rd-party IME
1199 if (mCurMethodId != null && !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
1202 final List<InputMethodInfo> suitableImes = InputMethodUtils.getDefaultEnabledImes(
1203 context, mSettings.getEnabledInputMethodListLocked());
1204 if (suitableImes.isEmpty()) {
1205 Slog.i(TAG, "No default found");
1208 final InputMethodInfo defIm = suitableImes.get(0);
1210 Slog.i(TAG, "Default found, using " + defIm.getId());
1212 setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
1215 private void switchUserLocked(int newUserId) {
1216 if (DEBUG) Slog.d(TAG, "Switching user stage 1/3. newUserId=" + newUserId
1217 + " currentUserId=" + mSettings.getCurrentUserId());
1219 // ContentObserver should be registered again when the user is changed
1220 mSettingsObserver.registerContentObserverLocked(newUserId);
1222 // If the system is not ready or the device is not yed unlocked by the user, then we use
1223 // copy-on-write settings.
1224 final boolean useCopyOnWriteSettings =
1225 !mSystemReady || !mUserManager.isUserUnlockingOrUnlocked(newUserId);
1226 mSettings.switchCurrentUser(newUserId, useCopyOnWriteSettings);
1227 updateCurrentProfileIds();
1228 // InputMethodFileManager should be reset when the user is changed
1229 mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
1230 final String defaultImiId = mSettings.getSelectedInputMethod();
1232 if (DEBUG) Slog.d(TAG, "Switching user stage 2/3. newUserId=" + newUserId
1233 + " defaultImiId=" + defaultImiId);
1235 // For secondary users, the list of enabled IMEs may not have been updated since the
1236 // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
1237 // not be empty even if the IME has been uninstalled by the primary user.
1238 // Even in such cases, IMMS works fine because it will find the most applicable
1239 // IME for that user.
1240 final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
1241 mLastSystemLocales = mRes.getConfiguration().getLocales();
1243 // TODO: Is it really possible that switchUserLocked() happens before system ready?
1245 hideCurrentInputLocked(0, null);
1246 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_USER);
1247 buildInputMethodListLocked(initialUserSwitch);
1248 if (TextUtils.isEmpty(mSettings.getSelectedInputMethod())) {
1249 // This is the first time of the user switch and
1250 // set the current ime to the proper one.
1251 resetDefaultImeLocked(mContext);
1253 updateFromSettingsLocked(true);
1255 startInputInnerLocked();
1256 } catch (RuntimeException e) {
1257 Slog.w(TAG, "Unexpected exception", e);
1261 if (initialUserSwitch) {
1262 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1263 mSettings.getEnabledInputMethodListLocked(), newUserId,
1264 mContext.getBasePackageName());
1267 if (DEBUG) Slog.d(TAG, "Switching user stage 3/3. newUserId=" + newUserId
1268 + " selectedIme=" + mSettings.getSelectedInputMethod());
1271 void updateCurrentProfileIds() {
1272 mSettings.setCurrentProfileIds(
1273 mUserManager.getProfileIdsWithDisabled(mSettings.getCurrentUserId()));
1277 public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
1278 throws RemoteException {
1280 return super.onTransact(code, data, reply, flags);
1281 } catch (RuntimeException e) {
1282 // The input method manager only throws security exceptions, so let's
1284 if (!(e instanceof SecurityException)) {
1285 Slog.wtf(TAG, "Input Method Manager Crash", e);
1291 public void systemRunning(StatusBarManagerService statusBar) {
1292 synchronized (mMethodMap) {
1294 Slog.d(TAG, "--- systemReady");
1296 if (!mSystemReady) {
1297 mSystemReady = true;
1298 mLastSystemLocales = mRes.getConfiguration().getLocales();
1299 final int currentUserId = mSettings.getCurrentUserId();
1300 mSettings.switchCurrentUser(currentUserId,
1301 !mUserManager.isUserUnlockingOrUnlocked(currentUserId));
1302 mKeyguardManager = mContext.getSystemService(KeyguardManager.class);
1303 mNotificationManager = mContext.getSystemService(NotificationManager.class);
1304 mStatusBar = statusBar;
1305 if (mStatusBar != null) {
1306 mStatusBar.setIconVisibility(mSlotIme, false);
1308 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
1309 mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
1310 com.android.internal.R.bool.show_ongoing_ime_switcher);
1311 if (mShowOngoingImeSwitcherForPhones) {
1312 mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
1313 mHardKeyboardListener);
1316 mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
1317 mSettingsObserver.registerContentObserverLocked(currentUserId);
1319 final IntentFilter broadcastFilter = new IntentFilter();
1320 broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
1321 broadcastFilter.addAction(Intent.ACTION_USER_ADDED);
1322 broadcastFilter.addAction(Intent.ACTION_USER_REMOVED);
1323 broadcastFilter.addAction(Intent.ACTION_SETTING_RESTORED);
1324 broadcastFilter.addAction(Intent.ACTION_LOCALE_CHANGED);
1325 mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
1327 buildInputMethodListLocked(true /* resetDefaultEnabledIme */);
1328 resetDefaultImeLocked(mContext);
1329 updateFromSettingsLocked(true);
1330 InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mIPackageManager,
1331 mSettings.getEnabledInputMethodListLocked(), currentUserId,
1332 mContext.getBasePackageName());
1335 startInputInnerLocked();
1336 } catch (RuntimeException e) {
1337 Slog.w(TAG, "Unexpected exception", e);
1343 // ---------------------------------------------------------------------------------------
1344 // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
1345 // 1) it comes from the system process
1346 // 2) the calling process' user id is identical to the current user id IMMS thinks.
1347 private boolean calledFromValidUser() {
1348 final int uid = Binder.getCallingUid();
1349 final int userId = UserHandle.getUserId(uid);
1351 Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
1352 + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
1353 + " calling userId = " + userId + ", foreground user id = "
1354 + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
1355 + InputMethodUtils.getApiCallStack());
1357 if (uid == Process.SYSTEM_UID || mSettings.isCurrentProfile(userId)) {
1361 // Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
1362 // foreground user, not for the user of that process. Accordingly InputMethodManagerService
1363 // must not manage background users' states in any functions.
1364 // Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
1366 if (mContext.checkCallingOrSelfPermission(
1367 android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
1368 == PackageManager.PERMISSION_GRANTED) {
1370 Slog.d(TAG, "--- Access granted because the calling process has "
1371 + "the INTERACT_ACROSS_USERS_FULL permission");
1375 // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1376 Slog.w(TAG, "--- IPC called from background users. Ignore. callers="
1377 + Debug.getCallers(10));
1383 * Returns true iff the caller is identified to be the current input method with the token.
1384 * @param token The window token given to the input method when it was started.
1385 * @return true if and only if non-null valid token is specified.
1387 private boolean calledWithValidToken(@Nullable IBinder token) {
1388 if (token == null && Binder.getCallingPid() == Process.myPid()) {
1390 // TODO(b/34851776): Basically it's the caller's fault if we reach here.
1391 Slog.d(TAG, "Bug 34851776 is detected callers=" + Debug.getCallers(10));
1395 if (token == null || token != mCurToken) {
1396 // TODO(b/34886274): The semantics of this verification is actually not well-defined.
1397 Slog.e(TAG, "Ignoring " + Debug.getCaller() + " due to an invalid token."
1398 + " uid:" + Binder.getCallingUid() + " token:" + token);
1404 private boolean bindCurrentInputMethodService(
1405 Intent service, ServiceConnection conn, int flags) {
1406 if (service == null || conn == null) {
1407 Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
1410 return mContext.bindServiceAsUser(service, conn, flags,
1411 new UserHandle(mSettings.getCurrentUserId()));
1415 public List<InputMethodInfo> getInputMethodList() {
1416 // TODO: Make this work even for non-current users?
1417 if (!calledFromValidUser()) {
1418 return Collections.emptyList();
1420 synchronized (mMethodMap) {
1421 return new ArrayList<>(mMethodList);
1426 public List<InputMethodInfo> getEnabledInputMethodList() {
1427 // TODO: Make this work even for non-current users?
1428 if (!calledFromValidUser()) {
1429 return Collections.emptyList();
1431 synchronized (mMethodMap) {
1432 return mSettings.getEnabledInputMethodListLocked();
1437 * @param imiId if null, returns enabled subtypes for the current imi
1438 * @return enabled subtypes of the specified imi
1441 public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
1442 boolean allowsImplicitlySelectedSubtypes) {
1443 // TODO: Make this work even for non-current users?
1444 if (!calledFromValidUser()) {
1445 return Collections.emptyList();
1447 synchronized (mMethodMap) {
1448 final InputMethodInfo imi;
1449 if (imiId == null && mCurMethodId != null) {
1450 imi = mMethodMap.get(mCurMethodId);
1452 imi = mMethodMap.get(imiId);
1455 return Collections.emptyList();
1457 return mSettings.getEnabledInputMethodSubtypeListLocked(
1458 mContext, imi, allowsImplicitlySelectedSubtypes);
1463 public void addClient(IInputMethodClient client,
1464 IInputContext inputContext, int uid, int pid) {
1465 if (!calledFromValidUser()) {
1468 synchronized (mMethodMap) {
1469 mClients.put(client.asBinder(), new ClientState(client,
1470 inputContext, uid, pid));
1475 public void removeClient(IInputMethodClient client) {
1476 if (!calledFromValidUser()) {
1479 synchronized (mMethodMap) {
1480 ClientState cs = mClients.remove(client.asBinder());
1482 clearClientSessionLocked(cs);
1483 if (mCurClient == cs) {
1486 if (mCurFocusedWindowClient == cs) {
1487 mCurFocusedWindowClient = null;
1493 void executeOrSendMessage(IInterface target, Message msg) {
1494 if (target.asBinder() instanceof Binder) {
1495 mCaller.sendMessage(msg);
1502 void unbindCurrentClientLocked(
1503 /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1504 if (mCurClient != null) {
1505 if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client="
1506 + mCurClient.client.asBinder());
1507 if (mBoundToMethod) {
1508 mBoundToMethod = false;
1509 if (mCurMethod != null) {
1510 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
1511 MSG_UNBIND_INPUT, mCurMethod));
1515 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1516 MSG_SET_ACTIVE, 0, 0, mCurClient));
1517 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
1518 MSG_UNBIND_CLIENT, mCurSeq, unbindClientReason, mCurClient.client));
1519 mCurClient.sessionRequested = false;
1522 hideInputMethodMenuLocked();
1526 private int getImeShowFlags() {
1529 flags |= InputMethod.SHOW_FORCED
1530 | InputMethod.SHOW_EXPLICIT;
1531 } else if (mShowExplicitlyRequested) {
1532 flags |= InputMethod.SHOW_EXPLICIT;
1537 private int getAppShowFlags() {
1540 flags |= InputMethodManager.SHOW_FORCED;
1541 } else if (!mShowExplicitlyRequested) {
1542 flags |= InputMethodManager.SHOW_IMPLICIT;
1547 InputBindResult attachNewInputLocked(
1548 /* @InputMethodClient.StartInputReason */ final int startInputReason, boolean initial) {
1549 if (!mBoundToMethod) {
1550 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1551 MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
1552 mBoundToMethod = true;
1555 final Binder startInputToken = new Binder();
1556 final StartInputInfo info = new StartInputInfo(mCurToken, mCurId, startInputReason,
1557 !initial, mCurFocusedWindow, mCurAttribute, mCurFocusedWindowSoftInputMode,
1559 mStartInputMap.put(startInputToken, info);
1560 mStartInputHistory.addEntry(info);
1562 final SessionState session = mCurClient.curSession;
1563 executeOrSendMessage(session.method, mCaller.obtainMessageIIOOOO(
1564 MSG_START_INPUT, mCurInputContextMissingMethods, initial ? 0 : 1 /* restarting */,
1565 startInputToken, session, mCurInputContext, mCurAttribute));
1566 if (mShowRequested) {
1567 if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
1568 showCurrentInputLocked(getAppShowFlags(), null);
1570 return new InputBindResult(session.session,
1571 (session.channel != null ? session.channel.dup() : null),
1572 mCurId, mCurSeq, mCurUserActionNotificationSequenceNumber);
1575 InputBindResult startInputLocked(
1576 /* @InputMethodClient.StartInputReason */ final int startInputReason,
1577 IInputMethodClient client, IInputContext inputContext,
1578 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1579 @Nullable EditorInfo attribute, int controlFlags) {
1580 // If no method is currently selected, do nothing.
1581 if (mCurMethodId == null) {
1585 ClientState cs = mClients.get(client.asBinder());
1587 throw new IllegalArgumentException("unknown client "
1588 + client.asBinder());
1591 if (attribute == null) {
1592 Slog.w(TAG, "Ignoring startInput with null EditorInfo."
1593 + " uid=" + cs.uid + " pid=" + cs.pid);
1598 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
1599 // Check with the window manager to make sure this client actually
1600 // has a window with focus. If not, reject. This is thread safe
1601 // because if the focus changes some time before or after, the
1602 // next client receiving focus that has any interest in input will
1603 // be calling through here after that change happens.
1605 Slog.w(TAG, "Starting input on non-focused client " + cs.client
1606 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
1610 } catch (RemoteException e) {
1613 return startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
1614 controlFlags, startInputReason);
1617 InputBindResult startInputUncheckedLocked(@NonNull ClientState cs, IInputContext inputContext,
1618 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1619 @NonNull EditorInfo attribute, int controlFlags,
1620 /* @InputMethodClient.StartInputReason */ final int startInputReason) {
1621 // If no method is currently selected, do nothing.
1622 if (mCurMethodId == null) {
1626 if (!InputMethodUtils.checkIfPackageBelongsToUid(mAppOpsManager, cs.uid,
1627 attribute.packageName)) {
1628 Slog.e(TAG, "Rejecting this client as it reported an invalid package name."
1629 + " uid=" + cs.uid + " package=" + attribute.packageName);
1633 if (mCurClient != cs) {
1634 // Was the keyguard locked when switching over to the new client?
1635 mCurClientInKeyguard = isKeyguardLocked();
1636 // If the client is changing, we need to switch over to the new
1638 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_CLIENT);
1639 if (DEBUG) Slog.v(TAG, "switching to client: client="
1640 + cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
1642 // If the screen is on, inform the new client it is active
1643 if (mIsInteractive) {
1644 executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
1645 MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, cs));
1649 // Bump up the sequence for this client and attach it.
1651 if (mCurSeq <= 0) mCurSeq = 1;
1653 mCurInputContext = inputContext;
1654 mCurInputContextMissingMethods = missingMethods;
1655 mCurAttribute = attribute;
1657 // Check if the input method is changing.
1658 if (mCurId != null && mCurId.equals(mCurMethodId)) {
1659 if (cs.curSession != null) {
1660 // Fast case: if we are already connected to the input method,
1661 // then just return it.
1662 return attachNewInputLocked(startInputReason,
1663 (controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
1665 if (mHaveConnection) {
1666 if (mCurMethod != null) {
1667 // Return to client, and we will get back with it when
1668 // we have had a session made for it.
1669 requestClientSessionLocked(cs);
1670 return new InputBindResult(null, null, mCurId, mCurSeq,
1671 mCurUserActionNotificationSequenceNumber);
1672 } else if (SystemClock.uptimeMillis()
1673 < (mLastBindTime+TIME_TO_RECONNECT)) {
1674 // In this case we have connected to the service, but
1675 // don't yet have its interface. If it hasn't been too
1676 // long since we did the connection, we'll return to
1677 // the client and wait to get the service interface so
1678 // we can report back. If it has been too long, we want
1679 // to fall through so we can try a disconnect/reconnect
1680 // to see if we can get back in touch with the service.
1681 return new InputBindResult(null, null, mCurId, mCurSeq,
1682 mCurUserActionNotificationSequenceNumber);
1684 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
1685 mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
1690 return startInputInnerLocked();
1693 InputBindResult startInputInnerLocked() {
1694 if (mCurMethodId == null) {
1698 if (!mSystemReady) {
1699 // If the system is not yet ready, we shouldn't be running third
1701 return new InputBindResult(null, null, mCurMethodId, mCurSeq,
1702 mCurUserActionNotificationSequenceNumber);
1705 InputMethodInfo info = mMethodMap.get(mCurMethodId);
1707 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
1710 unbindCurrentMethodLocked(true);
1712 mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
1713 mCurIntent.setComponent(info.getComponent());
1714 mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
1715 com.android.internal.R.string.input_method_binding_label);
1716 mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
1717 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
1718 if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
1719 | Context.BIND_NOT_VISIBLE | Context.BIND_NOT_FOREGROUND
1720 | Context.BIND_SHOWING_UI)) {
1721 mLastBindTime = SystemClock.uptimeMillis();
1722 mHaveConnection = true;
1723 mCurId = info.getId();
1724 mCurToken = new Binder();
1726 if (DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
1727 mIWindowManager.addWindowToken(mCurToken, TYPE_INPUT_METHOD, DEFAULT_DISPLAY);
1728 } catch (RemoteException e) {
1730 return new InputBindResult(null, null, mCurId, mCurSeq,
1731 mCurUserActionNotificationSequenceNumber);
1734 Slog.w(TAG, "Failure connecting to input method service: "
1740 private InputBindResult startInput(
1741 /* @InputMethodClient.StartInputReason */ final int startInputReason,
1742 IInputMethodClient client, IInputContext inputContext,
1743 /* @InputConnectionInspector.missingMethods */ final int missingMethods,
1744 @Nullable EditorInfo attribute, int controlFlags) {
1745 if (!calledFromValidUser()) {
1748 synchronized (mMethodMap) {
1750 Slog.v(TAG, "startInput: reason="
1751 + InputMethodClient.getStartInputReason(startInputReason)
1752 + " client = " + client.asBinder()
1753 + " inputContext=" + inputContext
1754 + " missingMethods="
1755 + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
1756 + " attribute=" + attribute
1757 + " controlFlags=#" + Integer.toHexString(controlFlags));
1759 final long ident = Binder.clearCallingIdentity();
1761 return startInputLocked(startInputReason, client, inputContext, missingMethods,
1762 attribute, controlFlags);
1764 Binder.restoreCallingIdentity(ident);
1770 public void finishInput(IInputMethodClient client) {
1774 public void onServiceConnected(ComponentName name, IBinder service) {
1775 synchronized (mMethodMap) {
1776 if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
1777 mCurMethod = IInputMethod.Stub.asInterface(service);
1778 if (mCurToken == null) {
1779 Slog.w(TAG, "Service connected without a token!");
1780 unbindCurrentMethodLocked(false);
1783 if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
1784 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
1785 MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
1786 if (mCurClient != null) {
1787 clearClientSessionLocked(mCurClient);
1788 requestClientSessionLocked(mCurClient);
1794 void onSessionCreated(IInputMethod method, IInputMethodSession session,
1795 InputChannel channel) {
1796 synchronized (mMethodMap) {
1797 if (mCurMethod != null && method != null
1798 && mCurMethod.asBinder() == method.asBinder()) {
1799 if (mCurClient != null) {
1800 clearClientSessionLocked(mCurClient);
1801 mCurClient.curSession = new SessionState(mCurClient,
1802 method, session, channel);
1803 InputBindResult res = attachNewInputLocked(
1804 InputMethodClient.START_INPUT_REASON_SESSION_CREATED_BY_IME, true);
1805 if (res.method != null) {
1806 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
1807 MSG_BIND_CLIENT, mCurClient.client, res));
1814 // Session abandoned. Close its associated input channel.
1818 void unbindCurrentMethodLocked(boolean savePosition) {
1819 if (mVisibleBound) {
1820 mContext.unbindService(mVisibleConnection);
1821 mVisibleBound = false;
1824 if (mHaveConnection) {
1825 mContext.unbindService(this);
1826 mHaveConnection = false;
1829 if (mCurToken != null) {
1831 if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
1832 if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
1833 // The current IME is shown. Hence an IME switch (transition) is happening.
1834 mWindowManagerInternal.saveLastInputMethodWindowForTransition();
1836 mIWindowManager.removeWindowToken(mCurToken, DEFAULT_DISPLAY);
1837 } catch (RemoteException e) {
1843 clearCurMethodLocked();
1846 void resetCurrentMethodAndClient(
1847 /* @InputMethodClient.UnbindReason */ final int unbindClientReason) {
1848 mCurMethodId = null;
1849 unbindCurrentMethodLocked(false);
1850 unbindCurrentClientLocked(unbindClientReason);
1853 void requestClientSessionLocked(ClientState cs) {
1854 if (!cs.sessionRequested) {
1855 if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
1856 InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
1857 cs.sessionRequested = true;
1858 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
1859 MSG_CREATE_SESSION, mCurMethod, channels[1],
1860 new MethodCallback(this, mCurMethod, channels[0])));
1864 void clearClientSessionLocked(ClientState cs) {
1865 finishSessionLocked(cs.curSession);
1866 cs.curSession = null;
1867 cs.sessionRequested = false;
1870 private void finishSessionLocked(SessionState sessionState) {
1871 if (sessionState != null) {
1872 if (sessionState.session != null) {
1874 sessionState.session.finishSession();
1875 } catch (RemoteException e) {
1876 Slog.w(TAG, "Session failed to close due to remote exception", e);
1877 updateSystemUiLocked(mCurToken, 0 /* vis */, mBackDisposition);
1879 sessionState.session = null;
1881 if (sessionState.channel != null) {
1882 sessionState.channel.dispose();
1883 sessionState.channel = null;
1888 void clearCurMethodLocked() {
1889 if (mCurMethod != null) {
1890 for (ClientState cs : mClients.values()) {
1891 clearClientSessionLocked(cs);
1894 finishSessionLocked(mEnabledSession);
1895 mEnabledSession = null;
1898 if (mStatusBar != null) {
1899 mStatusBar.setIconVisibility(mSlotIme, false);
1901 mInFullscreenMode = false;
1905 public void onServiceDisconnected(ComponentName name) {
1906 // Note that mContext.unbindService(this) does not trigger this. Hence if we are here the
1907 // disconnection is not intended by IMMS (e.g. triggered because the current IMS crashed),
1908 // which is irregular but can eventually happen for everyone just by continuing using the
1909 // device. Thus it is important to make sure that all the internal states are properly
1910 // refreshed when this method is called back. Running
1911 // adb install -r <APK that implements the current IME>
1912 // would be a good way to trigger such a situation.
1913 synchronized (mMethodMap) {
1914 if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
1915 + " mCurIntent=" + mCurIntent);
1916 if (mCurMethod != null && mCurIntent != null
1917 && name.equals(mCurIntent.getComponent())) {
1918 clearCurMethodLocked();
1919 // We consider this to be a new bind attempt, since the system
1920 // should now try to restart the service for us.
1921 mLastBindTime = SystemClock.uptimeMillis();
1922 mShowRequested = mInputShown;
1923 mInputShown = false;
1924 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_DISCONNECT_IME);
1930 public void updateStatusIcon(IBinder token, String packageName, int iconId) {
1931 synchronized (mMethodMap) {
1932 if (!calledWithValidToken(token)) {
1935 final long ident = Binder.clearCallingIdentity();
1938 if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
1939 if (mStatusBar != null) {
1940 mStatusBar.setIconVisibility(mSlotIme, false);
1942 } else if (packageName != null) {
1943 if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
1944 CharSequence contentDescription = null;
1946 // Use PackageManager to load label
1947 final PackageManager packageManager = mContext.getPackageManager();
1948 contentDescription = packageManager.getApplicationLabel(
1949 mIPackageManager.getApplicationInfo(packageName, 0,
1950 mSettings.getCurrentUserId()));
1951 } catch (RemoteException e) {
1954 if (mStatusBar != null) {
1955 mStatusBar.setIcon(mSlotIme, packageName, iconId, 0,
1956 contentDescription != null
1957 ? contentDescription.toString() : null);
1958 mStatusBar.setIconVisibility(mSlotIme, true);
1962 Binder.restoreCallingIdentity(ident);
1967 private boolean shouldShowImeSwitcherLocked(int visibility) {
1968 if (!mShowOngoingImeSwitcherForPhones) return false;
1969 if (mSwitchingDialog != null) return false;
1970 if (isScreenLocked()) return false;
1971 if ((visibility & InputMethodService.IME_ACTIVE) == 0) return false;
1972 if (mWindowManagerInternal.isHardKeyboardAvailable()) {
1973 if (mHardKeyboardBehavior == HardKeyboardBehavior.WIRELESS_AFFORDANCE) {
1974 // When physical keyboard is attached, we show the ime switcher (or notification if
1975 // NavBar is not available) because SHOW_IME_WITH_HARD_KEYBOARD settings currently
1976 // exists in the IME switcher dialog. Might be OK to remove this condition once
1977 // SHOW_IME_WITH_HARD_KEYBOARD settings finds a good place to live.
1980 } else if ((visibility & InputMethodService.IME_VISIBLE) == 0) {
1984 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
1985 final int N = imis.size();
1986 if (N > 2) return true;
1987 if (N < 1) return false;
1988 int nonAuxCount = 0;
1990 InputMethodSubtype nonAuxSubtype = null;
1991 InputMethodSubtype auxSubtype = null;
1992 for(int i = 0; i < N; ++i) {
1993 final InputMethodInfo imi = imis.get(i);
1994 final List<InputMethodSubtype> subtypes =
1995 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
1996 final int subtypeCount = subtypes.size();
1997 if (subtypeCount == 0) {
2000 for (int j = 0; j < subtypeCount; ++j) {
2001 final InputMethodSubtype subtype = subtypes.get(j);
2002 if (!subtype.isAuxiliary()) {
2004 nonAuxSubtype = subtype;
2007 auxSubtype = subtype;
2012 if (nonAuxCount > 1 || auxCount > 1) {
2014 } else if (nonAuxCount == 1 && auxCount == 1) {
2015 if (nonAuxSubtype != null && auxSubtype != null
2016 && (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
2017 || auxSubtype.overridesImplicitlyEnabledSubtype()
2018 || nonAuxSubtype.overridesImplicitlyEnabledSubtype())
2019 && nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
2027 private boolean isKeyguardLocked() {
2028 return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
2031 @SuppressWarnings("deprecation")
2033 public void setImeWindowStatus(IBinder token, IBinder startInputToken, int vis,
2034 int backDisposition) {
2035 if (startInputToken == null) {
2036 throw new InvalidParameterException("startInputToken cannot be null");
2039 if (!calledWithValidToken(token)) {
2043 final StartInputInfo info;
2044 synchronized (mMethodMap) {
2045 info = mStartInputMap.get(startInputToken);
2047 throw new InvalidParameterException("Unknown startInputToken=" + startInputToken);
2049 mImeWindowVis = vis;
2050 mBackDisposition = backDisposition;
2051 updateSystemUiLocked(token, vis, backDisposition);
2053 mWindowManagerInternal.updateInputMethodWindowStatus(info.mImeToken,
2054 (vis & InputMethodService.IME_VISIBLE) != 0, info.mTargetWindow);
2057 private void updateSystemUi(IBinder token, int vis, int backDisposition) {
2058 synchronized (mMethodMap) {
2059 updateSystemUiLocked(token, vis, backDisposition);
2063 // Caution! This method is called in this class. Handle multi-user carefully
2064 private void updateSystemUiLocked(IBinder token, int vis, int backDisposition) {
2065 if (!calledWithValidToken(token)) {
2069 // TODO: Move this clearing calling identity block to setImeWindowStatus after making sure
2070 // all updateSystemUi happens on system previlege.
2071 final long ident = Binder.clearCallingIdentity();
2073 // apply policy for binder calls
2074 if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
2077 // mImeWindowVis should be updated before calling shouldShowImeSwitcherLocked().
2078 final boolean needsToShowImeSwitcher = shouldShowImeSwitcherLocked(vis);
2079 if (mStatusBar != null) {
2080 mStatusBar.setImeWindowStatus(token, vis, backDisposition,
2081 needsToShowImeSwitcher);
2083 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2084 if (imi != null && needsToShowImeSwitcher) {
2085 // Used to load label
2086 final CharSequence title = mRes.getText(
2087 com.android.internal.R.string.select_input_method);
2088 final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
2089 mContext, imi, mCurrentSubtype);
2090 mImeSwitcherNotification.setContentTitle(title)
2091 .setContentText(summary)
2092 .setContentIntent(mImeSwitchPendingIntent);
2094 if ((mNotificationManager != null)
2095 && !mIWindowManager.hasNavigationBar()) {
2097 Slog.d(TAG, "--- show notification: label = " + summary);
2099 mNotificationManager.notifyAsUser(null,
2100 com.android.internal.R.string.select_input_method,
2101 mImeSwitcherNotification.build(), UserHandle.ALL);
2102 mNotificationShown = true;
2104 } catch (RemoteException e) {
2107 if (mNotificationShown && mNotificationManager != null) {
2109 Slog.d(TAG, "--- hide notification");
2111 mNotificationManager.cancelAsUser(null,
2112 com.android.internal.R.string.select_input_method, UserHandle.ALL);
2113 mNotificationShown = false;
2117 Binder.restoreCallingIdentity(ident);
2122 public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
2123 if (!calledFromValidUser()) {
2126 synchronized (mMethodMap) {
2127 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
2128 for (int i = 0; i < spans.length; ++i) {
2129 SuggestionSpan ss = spans[i];
2130 if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
2131 mSecureSuggestionSpans.put(ss, currentImi);
2138 public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
2139 if (!calledFromValidUser()) {
2142 synchronized (mMethodMap) {
2143 final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
2144 // TODO: Do not send the intent if the process of the targetImi is already dead.
2145 if (targetImi != null) {
2146 final String[] suggestions = span.getSuggestions();
2147 if (index < 0 || index >= suggestions.length) return false;
2148 final String className = span.getNotificationTargetClassName();
2149 final Intent intent = new Intent();
2150 // Ensures that only a class in the original IME package will receive the
2152 intent.setClassName(targetImi.getPackageName(), className);
2153 intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
2154 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
2155 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
2156 intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
2157 final long ident = Binder.clearCallingIdentity();
2159 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2161 Binder.restoreCallingIdentity(ident);
2169 void updateFromSettingsLocked(boolean enabledMayChange) {
2170 updateInputMethodsFromSettingsLocked(enabledMayChange);
2171 updateKeyboardFromSettingsLocked();
2174 void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
2175 if (enabledMayChange) {
2176 List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2177 for (int i=0; i<enabled.size(); i++) {
2178 // We allow the user to select "disabled until used" apps, so if they
2179 // are enabling one of those here we now need to make it enabled.
2180 InputMethodInfo imm = enabled.get(i);
2182 ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
2183 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
2184 mSettings.getCurrentUserId());
2185 if (ai != null && ai.enabledSetting
2186 == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
2188 Slog.d(TAG, "Update state(" + imm.getId()
2189 + "): DISABLED_UNTIL_USED -> DEFAULT");
2191 mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
2192 PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
2193 PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
2194 mContext.getBasePackageName());
2196 } catch (RemoteException e) {
2200 // We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
2201 // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
2202 // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
2204 String id = mSettings.getSelectedInputMethod();
2205 // There is no input method selected, try to choose new applicable input method.
2206 if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
2207 id = mSettings.getSelectedInputMethod();
2209 if (!TextUtils.isEmpty(id)) {
2211 setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
2212 } catch (IllegalArgumentException e) {
2213 Slog.w(TAG, "Unknown input method from prefs: " + id, e);
2214 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_SWITCH_IME_FAILED);
2216 mShortcutInputMethodsAndSubtypes.clear();
2218 // There is no longer an input method set, so stop any current one.
2219 resetCurrentMethodAndClient(InputMethodClient.UNBIND_REASON_NO_IME);
2221 // Here is not the perfect place to reset the switching controller. Ideally
2222 // mSwitchingController and mSettings should be able to share the same state.
2223 // TODO: Make sure that mSwitchingController and mSettings are sharing the
2224 // the same enabled IMEs list.
2225 mSwitchingController.resetCircularListLocked(mContext);
2229 public void updateKeyboardFromSettingsLocked() {
2230 mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
2231 if (mSwitchingDialog != null
2232 && mSwitchingDialogTitleView != null
2233 && mSwitchingDialog.isShowing()) {
2234 final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
2235 com.android.internal.R.id.hard_keyboard_switch);
2236 hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
2240 private void notifyInputMethodSubtypeChanged(final int userId,
2241 @Nullable final InputMethodInfo inputMethodInfo,
2242 @Nullable final InputMethodSubtype subtype) {
2243 final InputManagerInternal inputManagerInternal =
2244 LocalServices.getService(InputManagerInternal.class);
2245 if (inputManagerInternal != null) {
2246 inputManagerInternal.onInputMethodSubtypeChanged(userId, inputMethodInfo, subtype);
2250 /* package */ void setInputMethodLocked(String id, int subtypeId) {
2251 InputMethodInfo info = mMethodMap.get(id);
2253 throw new IllegalArgumentException("Unknown id: " + id);
2256 // See if we need to notify a subtype change within the same IME.
2257 if (id.equals(mCurMethodId)) {
2258 final int subtypeCount = info.getSubtypeCount();
2259 if (subtypeCount <= 0) {
2262 final InputMethodSubtype oldSubtype = mCurrentSubtype;
2263 final InputMethodSubtype newSubtype;
2264 if (subtypeId >= 0 && subtypeId < subtypeCount) {
2265 newSubtype = info.getSubtypeAt(subtypeId);
2267 // If subtype is null, try to find the most applicable one from
2268 // getCurrentInputMethodSubtype.
2269 newSubtype = getCurrentInputMethodSubtypeLocked();
2271 if (newSubtype == null || oldSubtype == null) {
2272 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
2273 + ", new subtype = " + newSubtype);
2276 if (newSubtype != oldSubtype) {
2277 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
2278 if (mCurMethod != null) {
2280 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
2281 mCurMethod.changeInputMethodSubtype(newSubtype);
2282 } catch (RemoteException e) {
2283 Slog.w(TAG, "Failed to call changeInputMethodSubtype");
2287 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info, newSubtype);
2292 // Changing to a different IME.
2293 final long ident = Binder.clearCallingIdentity();
2295 // Set a subtype to this input method.
2296 // subtypeId the name of a subtype which will be set.
2297 setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
2298 // mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
2299 // because mCurMethodId is stored as a history in
2300 // setSelectedInputMethodAndSubtypeLocked().
2303 if (LocalServices.getService(ActivityManagerInternal.class).isSystemReady()) {
2304 Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
2305 intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
2306 intent.putExtra("input_method_id", id);
2307 mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
2309 unbindCurrentClientLocked(InputMethodClient.UNBIND_REASON_SWITCH_IME);
2311 Binder.restoreCallingIdentity(ident);
2314 notifyInputMethodSubtypeChanged(mSettings.getCurrentUserId(), info,
2315 getCurrentInputMethodSubtypeLocked());
2319 public boolean showSoftInput(IInputMethodClient client, int flags,
2320 ResultReceiver resultReceiver) {
2321 if (!calledFromValidUser()) {
2324 int uid = Binder.getCallingUid();
2325 long ident = Binder.clearCallingIdentity();
2327 synchronized (mMethodMap) {
2328 if (mCurClient == null || client == null
2329 || mCurClient.client.asBinder() != client.asBinder()) {
2331 // We need to check if this is the current client with
2332 // focus in the window manager, to allow this call to
2333 // be made before input is started in it.
2334 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2335 Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
2338 } catch (RemoteException e) {
2343 if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
2344 return showCurrentInputLocked(flags, resultReceiver);
2347 Binder.restoreCallingIdentity(ident);
2351 boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2352 mShowRequested = true;
2353 if (mAccessibilityRequestingNoSoftKeyboard) {
2357 if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
2358 mShowExplicitlyRequested = true;
2360 } else if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
2361 mShowExplicitlyRequested = true;
2364 if (!mSystemReady) {
2368 boolean res = false;
2369 if (mCurMethod != null) {
2370 if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
2371 executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
2372 MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
2375 if (mHaveConnection && !mVisibleBound) {
2376 bindCurrentInputMethodService(
2377 mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE
2378 | Context.BIND_TREAT_LIKE_ACTIVITY
2379 | Context.BIND_FOREGROUND_SERVICE);
2380 mVisibleBound = true;
2383 } else if (mHaveConnection && SystemClock.uptimeMillis()
2384 >= (mLastBindTime+TIME_TO_RECONNECT)) {
2385 // The client has asked to have the input method shown, but
2386 // we have been sitting here too long with a connection to the
2387 // service and no interface received, so let's disconnect/connect
2388 // to try to prod things along.
2389 EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
2390 SystemClock.uptimeMillis()-mLastBindTime,1);
2391 Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
2392 mContext.unbindService(this);
2393 bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
2394 | Context.BIND_NOT_VISIBLE);
2397 Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
2398 + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
2406 public boolean hideSoftInput(IInputMethodClient client, int flags,
2407 ResultReceiver resultReceiver) {
2408 if (!calledFromValidUser()) {
2411 int uid = Binder.getCallingUid();
2412 long ident = Binder.clearCallingIdentity();
2414 synchronized (mMethodMap) {
2415 if (mCurClient == null || client == null
2416 || mCurClient.client.asBinder() != client.asBinder()) {
2418 // We need to check if this is the current client with
2419 // focus in the window manager, to allow this call to
2420 // be made before input is started in it.
2421 if (!mIWindowManager.inputMethodClientHasFocus(client)) {
2422 if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
2423 + uid + ": " + client);
2426 } catch (RemoteException e) {
2431 if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
2432 return hideCurrentInputLocked(flags, resultReceiver);
2435 Binder.restoreCallingIdentity(ident);
2439 boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
2440 if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
2441 && (mShowExplicitlyRequested || mShowForced)) {
2442 if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
2445 if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
2446 if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
2450 // There is a chance that IMM#hideSoftInput() is called in a transient state where
2451 // IMMS#InputShown is already updated to be true whereas IMMS#mImeWindowVis is still waiting
2452 // to be updated with the new value sent from IME process. Even in such a transient state
2453 // historically we have accepted an incoming call of IMM#hideSoftInput() from the
2454 // application process as a valid request, and have even promised such a behavior with CTS
2455 // since Android Eclair. That's why we need to accept IMM#hideSoftInput() even when only
2456 // IMMS#InputShown indicates that the software keyboard is shown.
2457 // TODO: Clean up, IMMS#mInputShown, IMMS#mImeWindowVis and mShowRequested.
2458 final boolean shouldHideSoftInput = (mCurMethod != null) && (mInputShown ||
2459 (mImeWindowVis & InputMethodService.IME_ACTIVE) != 0);
2461 if (shouldHideSoftInput) {
2462 // The IME will report its visible state again after the following message finally
2463 // delivered to the IME process as an IPC. Hence the inconsistency between
2464 // IMMS#mInputShown and IMMS#mImeWindowVis should be resolved spontaneously in
2466 executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
2467 MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
2472 if (mHaveConnection && mVisibleBound) {
2473 mContext.unbindService(mVisibleConnection);
2474 mVisibleBound = false;
2476 mInputShown = false;
2477 mShowRequested = false;
2478 mShowExplicitlyRequested = false;
2479 mShowForced = false;
2484 public InputBindResult startInputOrWindowGainedFocus(
2485 /* @InputMethodClient.StartInputReason */ final int startInputReason,
2486 IInputMethodClient client, IBinder windowToken, int controlFlags, int softInputMode,
2487 int windowFlags, @Nullable EditorInfo attribute, IInputContext inputContext,
2488 /* @InputConnectionInspector.missingMethods */ final int missingMethods) {
2489 if (windowToken != null) {
2490 return windowGainedFocus(startInputReason, client, windowToken, controlFlags,
2491 softInputMode, windowFlags, attribute, inputContext, missingMethods);
2493 return startInput(startInputReason, client, inputContext, missingMethods, attribute,
2498 private InputBindResult windowGainedFocus(
2499 /* @InputMethodClient.StartInputReason */ final int startInputReason,
2500 IInputMethodClient client, IBinder windowToken, int controlFlags,
2501 /* @android.view.WindowManager.LayoutParams.SoftInputModeFlags */ int softInputMode,
2502 int windowFlags, EditorInfo attribute, IInputContext inputContext,
2503 /* @InputConnectionInspector.missingMethods */ final int missingMethods) {
2504 // Needs to check the validity before clearing calling identity
2505 final boolean calledFromValidUser = calledFromValidUser();
2506 InputBindResult res = null;
2507 long ident = Binder.clearCallingIdentity();
2509 synchronized (mMethodMap) {
2510 if (DEBUG) Slog.v(TAG, "windowGainedFocus: reason="
2511 + InputMethodClient.getStartInputReason(startInputReason)
2512 + " client=" + client.asBinder()
2513 + " inputContext=" + inputContext
2514 + " missingMethods="
2515 + InputConnectionInspector.getMissingMethodFlagsAsString(missingMethods)
2516 + " attribute=" + attribute
2517 + " controlFlags=#" + Integer.toHexString(controlFlags)
2518 + " softInputMode=" + InputMethodClient.softInputModeToString(softInputMode)
2519 + " windowFlags=#" + Integer.toHexString(windowFlags));
2521 ClientState cs = mClients.get(client.asBinder());
2523 throw new IllegalArgumentException("unknown client "
2524 + client.asBinder());
2528 if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
2529 // Check with the window manager to make sure this client actually
2530 // has a window with focus. If not, reject. This is thread safe
2531 // because if the focus changes some time before or after, the
2532 // next client receiving focus that has any interest in input will
2533 // be calling through here after that change happens.
2535 Slog.w(TAG, "Focus gain on non-focused client " + cs.client
2536 + " (uid=" + cs.uid + " pid=" + cs.pid + ")");
2540 } catch (RemoteException e) {
2543 if (!calledFromValidUser) {
2544 Slog.w(TAG, "A background user is requesting window. Hiding IME.");
2545 Slog.w(TAG, "If you want to interect with IME, you need "
2546 + "android.permission.INTERACT_ACROSS_USERS_FULL");
2547 hideCurrentInputLocked(0, null);
2551 if (mCurFocusedWindow == windowToken) {
2553 Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
2554 + " attribute=" + attribute + ", token = " + windowToken);
2556 if (attribute != null) {
2557 return startInputUncheckedLocked(cs, inputContext, missingMethods,
2558 attribute, controlFlags, startInputReason);
2562 mCurFocusedWindow = windowToken;
2563 mCurFocusedWindowSoftInputMode = softInputMode;
2564 mCurFocusedWindowClient = cs;
2566 // Should we auto-show the IME even if the caller has not
2567 // specified what should be done with it?
2568 // We only do this automatically if the window can resize
2569 // to accommodate the IME (so what the user sees will give
2570 // them good context without input information being obscured
2571 // by the IME) or if running on a large screen where there
2572 // is more room for the target window + IME.
2573 final boolean doAutoShow =
2574 (softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
2575 == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
2576 || mRes.getConfiguration().isLayoutSizeAtLeast(
2577 Configuration.SCREENLAYOUT_SIZE_LARGE);
2578 final boolean isTextEditor =
2579 (controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
2581 // We want to start input before showing the IME, but after closing
2582 // it. We want to do this after closing it to help the IME disappear
2583 // more quickly (not get stuck behind it initializing itself for the
2584 // new focused input, even if its window wants to hide the IME).
2585 boolean didStart = false;
2587 switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
2588 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
2589 if (!isTextEditor || !doAutoShow) {
2590 if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
2591 // There is no focus view, and this window will
2592 // be behind any soft input window, so hide the
2593 // soft input window if it is shown.
2594 if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
2595 hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
2597 } else if (isTextEditor && doAutoShow && (softInputMode &
2598 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2599 // There is a focus view, and we are navigating forward
2600 // into the window, so show the input window for the user.
2601 // We only do this automatically if the window can resize
2602 // to accommodate the IME (so what the user sees will give
2603 // them good context without input information being obscured
2604 // by the IME) or if running on a large screen where there
2605 // is more room for the target window + IME.
2606 if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
2607 if (attribute != null) {
2608 res = startInputUncheckedLocked(cs, inputContext,
2609 missingMethods, attribute, controlFlags, startInputReason);
2612 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2615 case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
2618 case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
2619 if ((softInputMode &
2620 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2621 if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
2622 hideCurrentInputLocked(0, null);
2625 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
2626 if (DEBUG) Slog.v(TAG, "Window asks to hide input");
2627 hideCurrentInputLocked(0, null);
2629 case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
2630 if ((softInputMode &
2631 WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
2632 if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
2633 if (attribute != null) {
2634 res = startInputUncheckedLocked(cs, inputContext,
2635 missingMethods, attribute, controlFlags, startInputReason);
2638 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2641 case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
2642 if (DEBUG) Slog.v(TAG, "Window asks to always show input");
2643 if (attribute != null) {
2644 res = startInputUncheckedLocked(cs, inputContext, missingMethods,
2645 attribute, controlFlags, startInputReason);
2648 showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
2652 if (!didStart && attribute != null) {
2653 res = startInputUncheckedLocked(cs, inputContext, missingMethods, attribute,
2654 controlFlags, startInputReason);
2658 Binder.restoreCallingIdentity(ident);
2665 public void showInputMethodPickerFromClient(
2666 IInputMethodClient client, int auxiliarySubtypeMode) {
2667 if (!calledFromValidUser()) {
2670 synchronized (mMethodMap) {
2671 if (mCurClient == null || client == null
2672 || mCurClient.client.asBinder() != client.asBinder()) {
2673 Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
2674 + Binder.getCallingUid() + ": " + client);
2677 // Always call subtype picker, because subtype picker is a superset of input method
2679 mHandler.sendMessage(mCaller.obtainMessageI(
2680 MSG_SHOW_IM_SUBTYPE_PICKER, auxiliarySubtypeMode));
2685 public void setInputMethod(IBinder token, String id) {
2686 if (!calledFromValidUser()) {
2689 setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
2693 public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
2694 if (!calledFromValidUser()) {
2697 synchronized (mMethodMap) {
2698 if (subtype != null) {
2699 setInputMethodWithSubtypeIdLocked(token, id,
2700 InputMethodUtils.getSubtypeIdFromHashCode(mMethodMap.get(id),
2701 subtype.hashCode()));
2703 setInputMethod(token, id);
2709 public void showInputMethodAndSubtypeEnablerFromClient(
2710 IInputMethodClient client, String inputMethodId) {
2711 if (!calledFromValidUser()) {
2714 synchronized (mMethodMap) {
2715 executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
2716 MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
2721 public boolean switchToLastInputMethod(IBinder token) {
2722 if (!calledFromValidUser()) {
2725 synchronized (mMethodMap) {
2726 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2727 final InputMethodInfo lastImi;
2728 if (lastIme != null) {
2729 lastImi = mMethodMap.get(lastIme.first);
2733 String targetLastImiId = null;
2734 int subtypeId = NOT_A_SUBTYPE_ID;
2735 if (lastIme != null && lastImi != null) {
2736 final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
2737 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2738 final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
2739 : mCurrentSubtype.hashCode();
2740 // If the last IME is the same as the current IME and the last subtype is not
2741 // defined, there is no need to switch to the last IME.
2742 if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
2743 targetLastImiId = lastIme.first;
2744 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2748 if (TextUtils.isEmpty(targetLastImiId)
2749 && !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
2750 // This is a safety net. If the currentSubtype can't be added to the history
2751 // and the framework couldn't find the last ime, we will make the last ime be
2752 // the most applicable enabled keyboard subtype of the system imes.
2753 final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
2754 if (enabled != null) {
2755 final int N = enabled.size();
2756 final String locale = mCurrentSubtype == null
2757 ? mRes.getConfiguration().locale.toString()
2758 : mCurrentSubtype.getLocale();
2759 for (int i = 0; i < N; ++i) {
2760 final InputMethodInfo imi = enabled.get(i);
2761 if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
2762 InputMethodSubtype keyboardSubtype =
2763 InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
2764 InputMethodUtils.getSubtypes(imi),
2765 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
2766 if (keyboardSubtype != null) {
2767 targetLastImiId = imi.getId();
2768 subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
2769 imi, keyboardSubtype.hashCode());
2770 if(keyboardSubtype.getLocale().equals(locale)) {
2779 if (!TextUtils.isEmpty(targetLastImiId)) {
2781 Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
2782 + ", from: " + mCurMethodId + ", " + subtypeId);
2784 setInputMethodWithSubtypeIdLocked(token, targetLastImiId, subtypeId);
2793 public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
2794 if (!calledFromValidUser()) {
2797 synchronized (mMethodMap) {
2798 if (!calledWithValidToken(token)) {
2801 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2802 onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2803 true /* forward */);
2804 if (nextSubtype == null) {
2807 setInputMethodWithSubtypeIdLocked(token, nextSubtype.mImi.getId(),
2808 nextSubtype.mSubtypeId);
2814 public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
2815 if (!calledFromValidUser()) {
2818 synchronized (mMethodMap) {
2819 if (!calledWithValidToken(token)) {
2822 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
2823 false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype,
2824 true /* forward */);
2825 if (nextSubtype == null) {
2833 public InputMethodSubtype getLastInputMethodSubtype() {
2834 if (!calledFromValidUser()) {
2837 synchronized (mMethodMap) {
2838 final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
2839 // TODO: Handle the case of the last IME with no subtypes
2840 if (lastIme == null || TextUtils.isEmpty(lastIme.first)
2841 || TextUtils.isEmpty(lastIme.second)) return null;
2842 final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
2843 if (lastImi == null) return null;
2845 final int lastSubtypeHash = Integer.parseInt(lastIme.second);
2846 final int lastSubtypeId =
2847 InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
2848 if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
2851 return lastImi.getSubtypeAt(lastSubtypeId);
2852 } catch (NumberFormatException e) {
2859 public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
2860 if (!calledFromValidUser()) {
2863 // By this IPC call, only a process which shares the same uid with the IME can add
2864 // additional input method subtypes to the IME.
2865 if (TextUtils.isEmpty(imiId) || subtypes == null) return;
2866 synchronized (mMethodMap) {
2867 if (!mSystemReady) {
2870 final InputMethodInfo imi = mMethodMap.get(imiId);
2871 if (imi == null) return;
2872 final String[] packageInfos;
2874 packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
2875 } catch (RemoteException e) {
2876 Slog.e(TAG, "Failed to get package infos");
2879 if (packageInfos != null) {
2880 final int packageNum = packageInfos.length;
2881 for (int i = 0; i < packageNum; ++i) {
2882 if (packageInfos[i].equals(imi.getPackageName())) {
2883 mFileManager.addInputMethodSubtypes(imi, subtypes);
2884 final long ident = Binder.clearCallingIdentity();
2886 buildInputMethodListLocked(false /* resetDefaultEnabledIme */);
2888 Binder.restoreCallingIdentity(ident);
2899 public int getInputMethodWindowVisibleHeight() {
2900 return mWindowManagerInternal.getInputMethodWindowVisibleHeight();
2904 public void clearLastInputMethodWindowForTransition(IBinder token) {
2905 if (!calledFromValidUser()) {
2908 synchronized (mMethodMap) {
2909 if (!calledWithValidToken(token)) {
2913 mWindowManagerInternal.clearLastInputMethodWindowForTransition();
2917 public void notifyUserAction(int sequenceNumber) {
2919 Slog.d(TAG, "Got the notification of a user action. sequenceNumber:" + sequenceNumber);
2921 synchronized (mMethodMap) {
2922 if (mCurUserActionNotificationSequenceNumber != sequenceNumber) {
2924 Slog.d(TAG, "Ignoring the user action notification due to the sequence number "
2925 + "mismatch. expected:" + mCurUserActionNotificationSequenceNumber
2926 + " actual: " + sequenceNumber);
2930 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
2932 mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
2937 private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
2938 synchronized (mMethodMap) {
2939 setInputMethodWithSubtypeIdLocked(token, id, subtypeId);
2943 private void setInputMethodWithSubtypeIdLocked(IBinder token, String id, int subtypeId) {
2944 if (token == null) {
2945 if (mContext.checkCallingOrSelfPermission(
2946 android.Manifest.permission.WRITE_SECURE_SETTINGS)
2947 != PackageManager.PERMISSION_GRANTED) {
2948 throw new SecurityException(
2949 "Using null token requires permission "
2950 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
2952 } else if (mCurToken != token) {
2953 Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
2954 + " token: " + token);
2958 final long ident = Binder.clearCallingIdentity();
2960 setInputMethodLocked(id, subtypeId);
2962 Binder.restoreCallingIdentity(ident);
2967 public void hideMySoftInput(IBinder token, int flags) {
2968 if (!calledFromValidUser()) {
2971 synchronized (mMethodMap) {
2972 if (!calledWithValidToken(token)) {
2975 long ident = Binder.clearCallingIdentity();
2977 hideCurrentInputLocked(flags, null);
2979 Binder.restoreCallingIdentity(ident);
2985 public void showMySoftInput(IBinder token, int flags) {
2986 if (!calledFromValidUser()) {
2989 synchronized (mMethodMap) {
2990 if (!calledWithValidToken(token)) {
2993 long ident = Binder.clearCallingIdentity();
2995 showCurrentInputLocked(flags, null);
2997 Binder.restoreCallingIdentity(ident);
3002 void setEnabledSessionInMainThread(SessionState session) {
3003 if (mEnabledSession != session) {
3004 if (mEnabledSession != null && mEnabledSession.session != null) {
3006 if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
3007 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, false);
3008 } catch (RemoteException e) {
3011 mEnabledSession = session;
3012 if (mEnabledSession != null && mEnabledSession.session != null) {
3014 if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
3015 mEnabledSession.method.setSessionEnabled(mEnabledSession.session, true);
3016 } catch (RemoteException e) {
3023 public boolean handleMessage(Message msg) {
3026 case MSG_SHOW_IM_SUBTYPE_PICKER:
3027 final boolean showAuxSubtypes;
3029 case InputMethodManager.SHOW_IM_PICKER_MODE_AUTO:
3030 // This is undocumented so far, but IMM#showInputMethodPicker() has been
3031 // implemented so that auxiliary subtypes will be excluded when the soft
3032 // keyboard is invisible.
3033 showAuxSubtypes = mInputShown;
3035 case InputMethodManager.SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES:
3036 showAuxSubtypes = true;
3038 case InputMethodManager.SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES:
3039 showAuxSubtypes = false;
3042 Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
3045 showInputMethodMenu(showAuxSubtypes);
3048 case MSG_SHOW_IM_SUBTYPE_ENABLER:
3049 showInputMethodAndSubtypeEnabler((String)msg.obj);
3052 case MSG_SHOW_IM_CONFIG:
3053 showConfigureInputMethods();
3056 // ---------------------------------------------------------
3058 case MSG_UNBIND_INPUT:
3060 ((IInputMethod)msg.obj).unbindInput();
3061 } catch (RemoteException e) {
3062 // There is nothing interesting about the method dying.
3065 case MSG_BIND_INPUT:
3066 args = (SomeArgs)msg.obj;
3068 ((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
3069 } catch (RemoteException e) {
3073 case MSG_SHOW_SOFT_INPUT:
3074 args = (SomeArgs)msg.obj;
3076 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
3077 + msg.arg1 + ", " + args.arg2 + ")");
3078 ((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
3079 } catch (RemoteException e) {
3083 case MSG_HIDE_SOFT_INPUT:
3084 args = (SomeArgs)msg.obj;
3086 if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
3088 ((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
3089 } catch (RemoteException e) {
3093 case MSG_HIDE_CURRENT_INPUT_METHOD:
3094 synchronized (mMethodMap) {
3095 hideCurrentInputLocked(0, null);
3098 case MSG_ATTACH_TOKEN:
3099 args = (SomeArgs)msg.obj;
3101 if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
3102 ((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
3103 } catch (RemoteException e) {
3107 case MSG_CREATE_SESSION: {
3108 args = (SomeArgs)msg.obj;
3109 IInputMethod method = (IInputMethod)args.arg1;
3110 InputChannel channel = (InputChannel)args.arg2;
3112 method.createSession(channel, (IInputSessionCallback)args.arg3);
3113 } catch (RemoteException e) {
3115 // Dispose the channel if the input method is not local to this process
3116 // because the remote proxy will get its own copy when unparceled.
3117 if (channel != null && Binder.isProxy(method)) {
3124 // ---------------------------------------------------------
3126 case MSG_START_INPUT: {
3127 final int missingMethods = msg.arg1;
3128 final boolean restarting = msg.arg2 != 0;
3129 args = (SomeArgs) msg.obj;
3130 final IBinder startInputToken = (IBinder) args.arg1;
3131 final SessionState session = (SessionState) args.arg2;
3132 final IInputContext inputContext = (IInputContext) args.arg3;
3133 final EditorInfo editorInfo = (EditorInfo) args.arg4;
3135 setEnabledSessionInMainThread(session);
3136 session.method.startInput(startInputToken, inputContext, missingMethods,
3137 editorInfo, restarting);
3138 } catch (RemoteException e) {
3144 // ---------------------------------------------------------
3146 case MSG_UNBIND_CLIENT:
3148 ((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1, msg.arg2);
3149 } catch (RemoteException e) {
3150 // There is nothing interesting about the last client dying.
3153 case MSG_BIND_CLIENT: {
3154 args = (SomeArgs)msg.obj;
3155 IInputMethodClient client = (IInputMethodClient)args.arg1;
3156 InputBindResult res = (InputBindResult)args.arg2;
3158 client.onBindMethod(res);
3159 } catch (RemoteException e) {
3160 Slog.w(TAG, "Client died receiving input method " + args.arg2);
3162 // Dispose the channel if the input method is not local to this process
3163 // because the remote proxy will get its own copy when unparceled.
3164 if (res.channel != null && Binder.isProxy(client)) {
3165 res.channel.dispose();
3171 case MSG_SET_ACTIVE:
3173 ((ClientState)msg.obj).client.setActive(msg.arg1 != 0, msg.arg2 != 0);
3174 } catch (RemoteException e) {
3175 Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
3176 + ((ClientState)msg.obj).pid + " uid "
3177 + ((ClientState)msg.obj).uid);
3180 case MSG_SET_INTERACTIVE:
3181 handleSetInteractive(msg.arg1 != 0);
3183 case MSG_SWITCH_IME:
3184 handleSwitchInputMethod(msg.arg1 != 0);
3186 case MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER: {
3187 final int sequenceNumber = msg.arg1;
3188 final ClientState clientState = (ClientState)msg.obj;
3190 clientState.client.setUserActionNotificationSequenceNumber(sequenceNumber);
3191 } catch (RemoteException e) {
3192 Slog.w(TAG, "Got RemoteException sending "
3193 + "setUserActionNotificationSequenceNumber("
3194 + sequenceNumber + ") notification to pid "
3195 + clientState.pid + " uid "
3200 case MSG_REPORT_FULLSCREEN_MODE: {
3201 final boolean fullscreen = msg.arg1 != 0;
3202 final ClientState clientState = (ClientState)msg.obj;
3204 clientState.client.reportFullscreenMode(fullscreen);
3205 } catch (RemoteException e) {
3206 Slog.w(TAG, "Got RemoteException sending "
3207 + "reportFullscreen(" + fullscreen + ") notification to pid="
3208 + clientState.pid + " uid=" + clientState.uid);
3213 // --------------------------------------------------------------
3214 case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
3215 mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
3217 case MSG_SYSTEM_UNLOCK_USER:
3218 final int userId = msg.arg1;
3219 onUnlockUser(userId);
3225 private void handleSetInteractive(final boolean interactive) {
3226 synchronized (mMethodMap) {
3227 mIsInteractive = interactive;
3228 updateSystemUiLocked(mCurToken, interactive ? mImeWindowVis : 0, mBackDisposition);
3230 // Inform the current client of the change in active status
3231 if (mCurClient != null && mCurClient.client != null) {
3232 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIIO(
3233 MSG_SET_ACTIVE, mIsInteractive ? 1 : 0, mInFullscreenMode ? 1 : 0,
3239 private void handleSwitchInputMethod(final boolean forwardDirection) {
3240 synchronized (mMethodMap) {
3241 final ImeSubtypeListItem nextSubtype = mSwitchingController.getNextInputMethodLocked(
3242 false, mMethodMap.get(mCurMethodId), mCurrentSubtype, forwardDirection);
3243 if (nextSubtype == null) {
3246 setInputMethodLocked(nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
3247 final InputMethodInfo newInputMethodInfo = mMethodMap.get(mCurMethodId);
3248 if (newInputMethodInfo == null) {
3251 final CharSequence toastText = InputMethodUtils.getImeAndSubtypeDisplayName(mContext,
3252 newInputMethodInfo, mCurrentSubtype);
3253 if (!TextUtils.isEmpty(toastText)) {
3254 if (mSubtypeSwitchedByShortCutToast == null) {
3255 mSubtypeSwitchedByShortCutToast = Toast.makeText(mContext, toastText,
3256 Toast.LENGTH_SHORT);
3258 mSubtypeSwitchedByShortCutToast.setText(toastText);
3260 mSubtypeSwitchedByShortCutToast.show();
3265 private boolean chooseNewDefaultIMELocked() {
3266 final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
3267 mSettings.getEnabledInputMethodListLocked());
3270 Slog.d(TAG, "New default IME was selected: " + imi.getId());
3272 resetSelectedInputMethodAndSubtypeLocked(imi.getId());
3279 void buildInputMethodListLocked(boolean resetDefaultEnabledIme) {
3281 Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
3282 + " \n ------ caller=" + Debug.getCallers(10));
3284 if (!mSystemReady) {
3285 Slog.e(TAG, "buildInputMethodListLocked is not allowed until system is ready");
3288 mMethodList.clear();
3290 mMethodMapUpdateCount++;
3291 mMyPackageMonitor.clearPackagesToMonitorComponentChangeLocked();
3293 // Use for queryIntentServicesAsUser
3294 final PackageManager pm = mContext.getPackageManager();
3296 // Note: We do not specify PackageManager.MATCH_ENCRYPTION_* flags here because the default
3297 // behavior of PackageManager is exactly what we want. It by default picks up appropriate
3298 // services depending on the unlock state for the specified user.
3299 final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
3300 new Intent(InputMethod.SERVICE_INTERFACE),
3301 PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
3302 mSettings.getCurrentUserId());
3304 final HashMap<String, List<InputMethodSubtype>> additionalSubtypeMap =
3305 mFileManager.getAllAdditionalInputMethodSubtypes();
3306 for (int i = 0; i < services.size(); ++i) {
3307 ResolveInfo ri = services.get(i);
3308 ServiceInfo si = ri.serviceInfo;
3309 final String imeId = InputMethodInfo.computeId(ri);
3310 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3311 Slog.w(TAG, "Skipping input method " + imeId
3312 + ": it does not require the permission "
3313 + android.Manifest.permission.BIND_INPUT_METHOD);
3317 if (DEBUG) Slog.d(TAG, "Checking " + imeId);
3319 final List<InputMethodSubtype> additionalSubtypes = additionalSubtypeMap.get(imeId);
3321 InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
3323 final String id = p.getId();
3324 mMethodMap.put(id, p);
3327 Slog.d(TAG, "Found an input method " + p);
3329 } catch (Exception e) {
3330 Slog.wtf(TAG, "Unable to load input method " + imeId, e);
3334 // Construct the set of possible IME packages for onPackageChanged() to avoid false
3335 // negatives when the package state remains to be the same but only the component state is
3338 // Here we intentionally use PackageManager.MATCH_DISABLED_COMPONENTS since the purpose
3339 // of this query is to avoid false negatives. PackageManager.MATCH_ALL could be more
3340 // conservative, but it seems we cannot use it for now (Issue 35176630).
3341 final List<ResolveInfo> allInputMethodServices = pm.queryIntentServicesAsUser(
3342 new Intent(InputMethod.SERVICE_INTERFACE),
3343 PackageManager.MATCH_DISABLED_COMPONENTS, mSettings.getCurrentUserId());
3344 final int N = allInputMethodServices.size();
3345 for (int i = 0; i < N; ++i) {
3346 final ServiceInfo si = allInputMethodServices.get(i).serviceInfo;
3347 if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(si.permission)) {
3350 mMyPackageMonitor.addPackageToMonitorComponentChangeLocked(si.packageName);
3354 // TODO: The following code should find better place to live.
3355 if (!resetDefaultEnabledIme) {
3356 boolean enabledImeFound = false;
3357 final List<InputMethodInfo> enabledImes = mSettings.getEnabledInputMethodListLocked();
3358 final int N = enabledImes.size();
3359 for (int i = 0; i < N; ++i) {
3360 final InputMethodInfo imi = enabledImes.get(i);
3361 if (mMethodList.contains(imi)) {
3362 enabledImeFound = true;
3366 if (!enabledImeFound) {
3368 Slog.i(TAG, "All the enabled IMEs are gone. Reset default enabled IMEs.");
3370 resetDefaultEnabledIme = true;
3371 resetSelectedInputMethodAndSubtypeLocked("");
3375 if (resetDefaultEnabledIme) {
3376 final ArrayList<InputMethodInfo> defaultEnabledIme =
3377 InputMethodUtils.getDefaultEnabledImes(mContext, mMethodList);
3378 final int N = defaultEnabledIme.size();
3379 for (int i = 0; i < N; ++i) {
3380 final InputMethodInfo imi = defaultEnabledIme.get(i);
3382 Slog.d(TAG, "--- enable ime = " + imi);
3384 setInputMethodEnabledLocked(imi.getId(), true);
3388 final String defaultImiId = mSettings.getSelectedInputMethod();
3389 if (!TextUtils.isEmpty(defaultImiId)) {
3390 if (!mMethodMap.containsKey(defaultImiId)) {
3391 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
3392 if (chooseNewDefaultIMELocked()) {
3393 updateInputMethodsFromSettingsLocked(true);
3396 // Double check that the default IME is certainly enabled.
3397 setInputMethodEnabledLocked(defaultImiId, true);
3400 // Here is not the perfect place to reset the switching controller. Ideally
3401 // mSwitchingController and mSettings should be able to share the same state.
3402 // TODO: Make sure that mSwitchingController and mSettings are sharing the
3403 // the same enabled IMEs list.
3404 mSwitchingController.resetCircularListLocked(mContext);
3407 // ----------------------------------------------------------------------
3409 private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
3410 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
3411 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3412 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3413 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3414 if (!TextUtils.isEmpty(inputMethodId)) {
3415 intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
3418 synchronized (mMethodMap) {
3419 userId = mSettings.getCurrentUserId();
3421 mContext.startActivityAsUser(intent, null, UserHandle.of(userId));
3424 private void showConfigureInputMethods() {
3425 Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
3426 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
3427 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
3428 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
3429 mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
3432 private boolean isScreenLocked() {
3433 return mKeyguardManager != null
3434 && mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
3437 private void showInputMethodMenu(boolean showAuxSubtypes) {
3438 if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
3440 final Context context = mContext;
3441 final boolean isScreenLocked = isScreenLocked();
3443 final String lastInputMethodId = mSettings.getSelectedInputMethod();
3444 int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
3445 if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
3447 synchronized (mMethodMap) {
3448 final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
3449 mSettings.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked(
3451 if (immis == null || immis.size() == 0) {
3455 hideInputMethodMenuLocked();
3457 final List<ImeSubtypeListItem> imList =
3458 mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
3459 showAuxSubtypes, isScreenLocked);
3461 if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
3462 final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
3463 if (currentSubtype != null) {
3464 final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
3465 lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3466 currentImi, currentSubtype.hashCode());
3470 final int N = imList.size();
3471 mIms = new InputMethodInfo[N];
3472 mSubtypeIds = new int[N];
3473 int checkedItem = 0;
3474 for (int i = 0; i < N; ++i) {
3475 final ImeSubtypeListItem item = imList.get(i);
3476 mIms[i] = item.mImi;
3477 mSubtypeIds[i] = item.mSubtypeId;
3478 if (mIms[i].getId().equals(lastInputMethodId)) {
3479 int subtypeId = mSubtypeIds[i];
3480 if ((subtypeId == NOT_A_SUBTYPE_ID)
3481 || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
3482 || (subtypeId == lastInputMethodSubtypeId)) {
3488 final Context settingsContext = new ContextThemeWrapper(context,
3489 com.android.internal.R.style.Theme_DeviceDefault_Settings);
3491 mDialogBuilder = new AlertDialog.Builder(settingsContext);
3492 mDialogBuilder.setOnCancelListener(new OnCancelListener() {
3494 public void onCancel(DialogInterface dialog) {
3495 hideInputMethodMenu();
3499 final Context dialogContext = mDialogBuilder.getContext();
3500 final TypedArray a = dialogContext.obtainStyledAttributes(null,
3501 com.android.internal.R.styleable.DialogPreference,
3502 com.android.internal.R.attr.alertDialogStyle, 0);
3503 final Drawable dialogIcon = a.getDrawable(
3504 com.android.internal.R.styleable.DialogPreference_dialogIcon);
3507 mDialogBuilder.setIcon(dialogIcon);
3509 final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
3510 final View tv = inflater.inflate(
3511 com.android.internal.R.layout.input_method_switch_dialog_title, null);
3512 mDialogBuilder.setCustomTitle(tv);
3514 // Setup layout for a toggle switch of the hardware keyboard
3515 mSwitchingDialogTitleView = tv;
3516 mSwitchingDialogTitleView
3517 .findViewById(com.android.internal.R.id.hard_keyboard_section)
3518 .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
3519 ? View.VISIBLE : View.GONE);
3520 final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
3521 com.android.internal.R.id.hard_keyboard_switch);
3522 hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
3523 hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
3525 public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
3526 mSettings.setShowImeWithHardKeyboard(isChecked);
3527 // Ensure that the input method dialog is dismissed when changing
3528 // the hardware keyboard state.
3529 hideInputMethodMenu();
3533 final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
3534 com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
3535 final OnClickListener choiceListener = new OnClickListener() {
3537 public void onClick(final DialogInterface dialog, final int which) {
3538 synchronized (mMethodMap) {
3539 if (mIms == null || mIms.length <= which || mSubtypeIds == null
3540 || mSubtypeIds.length <= which) {
3543 final InputMethodInfo im = mIms[which];
3544 int subtypeId = mSubtypeIds[which];
3545 adapter.mCheckedItem = which;
3546 adapter.notifyDataSetChanged();
3547 hideInputMethodMenu();
3549 if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
3550 subtypeId = NOT_A_SUBTYPE_ID;
3552 setInputMethodLocked(im.getId(), subtypeId);
3557 mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
3559 mSwitchingDialog = mDialogBuilder.create();
3560 mSwitchingDialog.setCanceledOnTouchOutside(true);
3561 final Window w = mSwitchingDialog.getWindow();
3562 final WindowManager.LayoutParams attrs = w.getAttributes();
3563 w.setType(TYPE_INPUT_METHOD_DIALOG);
3564 // Use an alternate token for the dialog for that window manager can group the token
3565 // with other IME windows based on type vs. grouping based on whichever token happens
3566 // to get selected by the system later on.
3567 attrs.token = mSwitchingDialogToken;
3568 attrs.privateFlags |= PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
3569 attrs.setTitle("Select input method");
3570 w.setAttributes(attrs);
3571 updateSystemUi(mCurToken, mImeWindowVis, mBackDisposition);
3572 mSwitchingDialog.show();
3576 private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
3577 private final LayoutInflater mInflater;
3578 private final int mTextViewResourceId;
3579 private final List<ImeSubtypeListItem> mItemsList;
3580 public int mCheckedItem;
3581 public ImeSubtypeListAdapter(Context context, int textViewResourceId,
3582 List<ImeSubtypeListItem> itemsList, int checkedItem) {
3583 super(context, textViewResourceId, itemsList);
3585 mTextViewResourceId = textViewResourceId;
3586 mItemsList = itemsList;
3587 mCheckedItem = checkedItem;
3588 mInflater = context.getSystemService(LayoutInflater.class);
3592 public View getView(int position, View convertView, ViewGroup parent) {
3593 final View view = convertView != null ? convertView
3594 : mInflater.inflate(mTextViewResourceId, null);
3595 if (position < 0 || position >= mItemsList.size()) return view;
3596 final ImeSubtypeListItem item = mItemsList.get(position);
3597 final CharSequence imeName = item.mImeName;
3598 final CharSequence subtypeName = item.mSubtypeName;
3599 final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
3600 final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
3601 if (TextUtils.isEmpty(subtypeName)) {
3602 firstTextView.setText(imeName);
3603 secondTextView.setVisibility(View.GONE);
3605 firstTextView.setText(subtypeName);
3606 secondTextView.setText(imeName);
3607 secondTextView.setVisibility(View.VISIBLE);
3609 final RadioButton radioButton =
3610 (RadioButton)view.findViewById(com.android.internal.R.id.radio);
3611 radioButton.setChecked(position == mCheckedItem);
3616 void hideInputMethodMenu() {
3617 synchronized (mMethodMap) {
3618 hideInputMethodMenuLocked();
3622 void hideInputMethodMenuLocked() {
3623 if (DEBUG) Slog.v(TAG, "Hide switching menu");
3625 if (mSwitchingDialog != null) {
3626 mSwitchingDialog.dismiss();
3627 mSwitchingDialog = null;
3630 updateSystemUiLocked(mCurToken, mImeWindowVis, mBackDisposition);
3631 mDialogBuilder = null;
3635 // ----------------------------------------------------------------------
3638 public boolean setInputMethodEnabled(String id, boolean enabled) {
3639 // TODO: Make this work even for non-current users?
3640 if (!calledFromValidUser()) {
3643 synchronized (mMethodMap) {
3644 if (mContext.checkCallingOrSelfPermission(
3645 android.Manifest.permission.WRITE_SECURE_SETTINGS)
3646 != PackageManager.PERMISSION_GRANTED) {
3647 throw new SecurityException(
3648 "Requires permission "
3649 + android.Manifest.permission.WRITE_SECURE_SETTINGS);
3652 long ident = Binder.clearCallingIdentity();
3654 return setInputMethodEnabledLocked(id, enabled);
3656 Binder.restoreCallingIdentity(ident);
3661 boolean setInputMethodEnabledLocked(String id, boolean enabled) {
3662 // Make sure this is a valid input method.
3663 InputMethodInfo imm = mMethodMap.get(id);
3665 throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
3668 List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
3669 .getEnabledInputMethodsAndSubtypeListLocked();
3672 for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
3673 if (pair.first.equals(id)) {
3674 // We are enabling this input method, but it is already enabled.
3675 // Nothing to do. The previous state was enabled.
3679 mSettings.appendAndPutEnabledInputMethodLocked(id, false);
3680 // Previous state was disabled.
3683 StringBuilder builder = new StringBuilder();
3684 if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
3685 builder, enabledInputMethodsList, id)) {
3686 // Disabled input method is currently selected, switch to another one.
3687 final String selId = mSettings.getSelectedInputMethod();
3688 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
3689 Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
3690 resetSelectedInputMethodAndSubtypeLocked("");
3692 // Previous state was enabled.
3695 // We are disabling the input method but it is already disabled.
3696 // Nothing to do. The previous state was disabled.
3702 private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
3703 boolean setSubtypeOnly) {
3704 // Update the history of InputMethod and Subtype
3705 mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
3707 mCurUserActionNotificationSequenceNumber =
3708 Math.max(mCurUserActionNotificationSequenceNumber + 1, 1);
3710 Slog.d(TAG, "Bump mCurUserActionNotificationSequenceNumber:"
3711 + mCurUserActionNotificationSequenceNumber);
3714 if (mCurClient != null && mCurClient.client != null) {
3715 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
3716 MSG_SET_USER_ACTION_NOTIFICATION_SEQUENCE_NUMBER,
3717 mCurUserActionNotificationSequenceNumber, mCurClient));
3721 if (imi == null || subtypeId < 0) {
3722 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3723 mCurrentSubtype = null;
3725 if (subtypeId < imi.getSubtypeCount()) {
3726 InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
3727 mSettings.putSelectedSubtype(subtype.hashCode());
3728 mCurrentSubtype = subtype;
3730 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
3731 // If the subtype is not specified, choose the most applicable one
3732 mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
3736 if (!setSubtypeOnly) {
3737 // Set InputMethod here
3738 mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
3742 private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
3743 InputMethodInfo imi = mMethodMap.get(newDefaultIme);
3744 int lastSubtypeId = NOT_A_SUBTYPE_ID;
3745 // newDefaultIme is empty when there is no candidate for the selected IME.
3746 if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
3747 String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
3748 if (subtypeHashCode != null) {
3750 lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
3751 imi, Integer.parseInt(subtypeHashCode));
3752 } catch (NumberFormatException e) {
3753 Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
3757 setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
3760 // If there are no selected shortcuts, tries finding the most applicable ones.
3761 private Pair<InputMethodInfo, InputMethodSubtype>
3762 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
3763 List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
3764 InputMethodInfo mostApplicableIMI = null;
3765 InputMethodSubtype mostApplicableSubtype = null;
3766 boolean foundInSystemIME = false;
3768 // Search applicable subtype for each InputMethodInfo
3769 for (InputMethodInfo imi: imis) {
3770 final String imiId = imi.getId();
3771 if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
3774 InputMethodSubtype subtype = null;
3775 final List<InputMethodSubtype> enabledSubtypes =
3776 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3777 // 1. Search by the current subtype's locale from enabledSubtypes.
3778 if (mCurrentSubtype != null) {
3779 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3780 mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
3782 // 2. Search by the system locale from enabledSubtypes.
3783 // 3. Search the first enabled subtype matched with mode from enabledSubtypes.
3784 if (subtype == null) {
3785 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3786 mRes, enabledSubtypes, mode, null, true);
3788 final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
3789 InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
3790 final ArrayList<InputMethodSubtype> subtypesForSearch =
3791 overridingImplicitlyEnabledSubtypes.isEmpty()
3792 ? InputMethodUtils.getSubtypes(imi)
3793 : overridingImplicitlyEnabledSubtypes;
3794 // 4. Search by the current subtype's locale from all subtypes.
3795 if (subtype == null && mCurrentSubtype != null) {
3796 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3797 mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
3799 // 5. Search by the system locale from all subtypes.
3800 // 6. Search the first enabled subtype matched with mode from all subtypes.
3801 if (subtype == null) {
3802 subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3803 mRes, subtypesForSearch, mode, null, true);
3805 if (subtype != null) {
3806 if (imiId.equals(mCurMethodId)) {
3807 // The current input method is the most applicable IME.
3808 mostApplicableIMI = imi;
3809 mostApplicableSubtype = subtype;
3811 } else if (!foundInSystemIME) {
3812 // The system input method is 2nd applicable IME.
3813 mostApplicableIMI = imi;
3814 mostApplicableSubtype = subtype;
3815 if ((imi.getServiceInfo().applicationInfo.flags
3816 & ApplicationInfo.FLAG_SYSTEM) != 0) {
3817 foundInSystemIME = true;
3823 if (mostApplicableIMI != null) {
3824 Slog.w(TAG, "Most applicable shortcut input method was:"
3825 + mostApplicableIMI.getId());
3826 if (mostApplicableSubtype != null) {
3827 Slog.w(TAG, "Most applicable shortcut input method subtype was:"
3828 + "," + mostApplicableSubtype.getMode() + ","
3829 + mostApplicableSubtype.getLocale());
3833 if (mostApplicableIMI != null) {
3834 return new Pair<> (mostApplicableIMI, mostApplicableSubtype);
3841 * @return Return the current subtype of this input method.
3844 public InputMethodSubtype getCurrentInputMethodSubtype() {
3845 // TODO: Make this work even for non-current users?
3846 if (!calledFromValidUser()) {
3849 synchronized (mMethodMap) {
3850 return getCurrentInputMethodSubtypeLocked();
3854 private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
3855 if (mCurMethodId == null) {
3858 final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
3859 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3860 if (imi == null || imi.getSubtypeCount() == 0) {
3863 if (!subtypeIsSelected || mCurrentSubtype == null
3864 || !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
3865 int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
3866 if (subtypeId == NOT_A_SUBTYPE_ID) {
3867 // If there are no selected subtypes, the framework will try to find
3868 // the most applicable subtype from explicitly or implicitly enabled
3870 List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
3871 mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
3872 // If there is only one explicitly or implicitly enabled subtype,
3874 if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
3875 mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
3876 } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
3877 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3878 mRes, explicitlyOrImplicitlyEnabledSubtypes,
3879 InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
3880 if (mCurrentSubtype == null) {
3881 mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
3882 mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
3887 mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
3890 return mCurrentSubtype;
3893 // TODO: We should change the return type from List to List<Parcelable>
3894 @SuppressWarnings("rawtypes")
3896 public List getShortcutInputMethodsAndSubtypes() {
3897 synchronized (mMethodMap) {
3898 ArrayList<Object> ret = new ArrayList<>();
3899 if (mShortcutInputMethodsAndSubtypes.size() == 0) {
3900 // If there are no selected shortcut subtypes, the framework will try to find
3901 // the most applicable subtype from all subtypes whose mode is
3902 // SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
3903 Pair<InputMethodInfo, InputMethodSubtype> info =
3904 findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
3905 InputMethodUtils.SUBTYPE_MODE_VOICE);
3907 ret.add(info.first);
3908 ret.add(info.second);
3912 for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
3914 for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
3923 public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
3924 // TODO: Make this work even for non-current users?
3925 if (!calledFromValidUser()) {
3928 synchronized (mMethodMap) {
3929 if (subtype != null && mCurMethodId != null) {
3930 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
3931 int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
3932 if (subtypeId != NOT_A_SUBTYPE_ID) {
3933 setInputMethodLocked(mCurMethodId, subtypeId);
3941 // TODO: Cache the state for each user and reset when the cached user is removed.
3942 private static class InputMethodFileManager {
3943 private static final String SYSTEM_PATH = "system";
3944 private static final String INPUT_METHOD_PATH = "inputmethod";
3945 private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
3946 private static final String NODE_SUBTYPES = "subtypes";
3947 private static final String NODE_SUBTYPE = "subtype";
3948 private static final String NODE_IMI = "imi";
3949 private static final String ATTR_ID = "id";
3950 private static final String ATTR_LABEL = "label";
3951 private static final String ATTR_ICON = "icon";
3952 private static final String ATTR_IME_SUBTYPE_ID = "subtypeId";
3953 private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
3954 private static final String ATTR_IME_SUBTYPE_LANGUAGE_TAG = "languageTag";
3955 private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
3956 private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
3957 private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
3958 private static final String ATTR_IS_ASCII_CAPABLE = "isAsciiCapable";
3959 private final AtomicFile mAdditionalInputMethodSubtypeFile;
3960 private final HashMap<String, InputMethodInfo> mMethodMap;
3961 private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
3963 public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
3964 if (methodMap == null) {
3965 throw new NullPointerException("methodMap is null");
3967 mMethodMap = methodMap;
3968 final File systemDir = userId == UserHandle.USER_SYSTEM
3969 ? new File(Environment.getDataDirectory(), SYSTEM_PATH)
3970 : Environment.getUserSystemDirectory(userId);
3971 final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
3972 if (!inputMethodDir.exists() && !inputMethodDir.mkdirs()) {
3973 Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
3975 final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
3976 mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
3977 if (!subtypeFile.exists()) {
3978 // If "subtypes.xml" doesn't exist, create a blank file.
3979 writeAdditionalInputMethodSubtypes(
3980 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
3982 readAdditionalInputMethodSubtypes(
3983 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
3987 private void deleteAllInputMethodSubtypes(String imiId) {
3988 synchronized (mMethodMap) {
3989 mAdditionalSubtypesMap.remove(imiId);
3990 writeAdditionalInputMethodSubtypes(
3991 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
3995 public void addInputMethodSubtypes(
3996 InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
3997 synchronized (mMethodMap) {
3998 final ArrayList<InputMethodSubtype> subtypes = new ArrayList<>();
3999 final int N = additionalSubtypes.length;
4000 for (int i = 0; i < N; ++i) {
4001 final InputMethodSubtype subtype = additionalSubtypes[i];
4002 if (!subtypes.contains(subtype)) {
4003 subtypes.add(subtype);
4005 Slog.w(TAG, "Duplicated subtype definition found: "
4006 + subtype.getLocale() + ", " + subtype.getMode());
4009 mAdditionalSubtypesMap.put(imi.getId(), subtypes);
4010 writeAdditionalInputMethodSubtypes(
4011 mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
4015 public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
4016 synchronized (mMethodMap) {
4017 return mAdditionalSubtypesMap;
4021 private static void writeAdditionalInputMethodSubtypes(
4022 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
4023 HashMap<String, InputMethodInfo> methodMap) {
4024 // Safety net for the case that this function is called before methodMap is set.
4025 final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
4026 FileOutputStream fos = null;
4028 fos = subtypesFile.startWrite();
4029 final XmlSerializer out = new FastXmlSerializer();
4030 out.setOutput(fos, StandardCharsets.UTF_8.name());
4031 out.startDocument(null, true);
4032 out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
4033 out.startTag(null, NODE_SUBTYPES);
4034 for (String imiId : allSubtypes.keySet()) {
4035 if (isSetMethodMap && !methodMap.containsKey(imiId)) {
4036 Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
4039 out.startTag(null, NODE_IMI);
4040 out.attribute(null, ATTR_ID, imiId);
4041 final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
4042 final int N = subtypesList.size();
4043 for (int i = 0; i < N; ++i) {
4044 final InputMethodSubtype subtype = subtypesList.get(i);
4045 out.startTag(null, NODE_SUBTYPE);
4046 if (subtype.hasSubtypeId()) {
4047 out.attribute(null, ATTR_IME_SUBTYPE_ID,
4048 String.valueOf(subtype.getSubtypeId()));
4050 out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
4051 out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
4052 out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
4053 out.attribute(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG,
4054 subtype.getLanguageTag());
4055 out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
4056 out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
4057 out.attribute(null, ATTR_IS_AUXILIARY,
4058 String.valueOf(subtype.isAuxiliary() ? 1 : 0));
4059 out.attribute(null, ATTR_IS_ASCII_CAPABLE,
4060 String.valueOf(subtype.isAsciiCapable() ? 1 : 0));
4061 out.endTag(null, NODE_SUBTYPE);
4063 out.endTag(null, NODE_IMI);
4065 out.endTag(null, NODE_SUBTYPES);
4067 subtypesFile.finishWrite(fos);
4068 } catch (java.io.IOException e) {
4069 Slog.w(TAG, "Error writing subtypes", e);
4071 subtypesFile.failWrite(fos);
4076 private static void readAdditionalInputMethodSubtypes(
4077 HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
4078 if (allSubtypes == null || subtypesFile == null) return;
4079 allSubtypes.clear();
4080 try (final FileInputStream fis = subtypesFile.openRead()) {
4081 final XmlPullParser parser = Xml.newPullParser();
4082 parser.setInput(fis, StandardCharsets.UTF_8.name());
4083 int type = parser.getEventType();
4084 // Skip parsing until START_TAG
4085 while ((type = parser.next()) != XmlPullParser.START_TAG
4086 && type != XmlPullParser.END_DOCUMENT) {}
4087 String firstNodeName = parser.getName();
4088 if (!NODE_SUBTYPES.equals(firstNodeName)) {
4089 throw new XmlPullParserException("Xml doesn't start with subtypes");
4091 final int depth =parser.getDepth();
4092 String currentImiId = null;
4093 ArrayList<InputMethodSubtype> tempSubtypesArray = null;
4094 while (((type = parser.next()) != XmlPullParser.END_TAG
4095 || parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
4096 if (type != XmlPullParser.START_TAG)
4098 final String nodeName = parser.getName();
4099 if (NODE_IMI.equals(nodeName)) {
4100 currentImiId = parser.getAttributeValue(null, ATTR_ID);
4101 if (TextUtils.isEmpty(currentImiId)) {
4102 Slog.w(TAG, "Invalid imi id found in subtypes.xml");
4105 tempSubtypesArray = new ArrayList<>();
4106 allSubtypes.put(currentImiId, tempSubtypesArray);
4107 } else if (NODE_SUBTYPE.equals(nodeName)) {
4108 if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
4109 Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
4112 final int icon = Integer.parseInt(
4113 parser.getAttributeValue(null, ATTR_ICON));
4114 final int label = Integer.parseInt(
4115 parser.getAttributeValue(null, ATTR_LABEL));
4116 final String imeSubtypeLocale =
4117 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
4118 final String languageTag =
4119 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LANGUAGE_TAG);
4120 final String imeSubtypeMode =
4121 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
4122 final String imeSubtypeExtraValue =
4123 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
4124 final boolean isAuxiliary = "1".equals(String.valueOf(
4125 parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
4126 final boolean isAsciiCapable = "1".equals(String.valueOf(
4127 parser.getAttributeValue(null, ATTR_IS_ASCII_CAPABLE)));
4128 final InputMethodSubtypeBuilder builder = new InputMethodSubtypeBuilder()
4129 .setSubtypeNameResId(label)
4130 .setSubtypeIconResId(icon)
4131 .setSubtypeLocale(imeSubtypeLocale)
4132 .setLanguageTag(languageTag)
4133 .setSubtypeMode(imeSubtypeMode)
4134 .setSubtypeExtraValue(imeSubtypeExtraValue)
4135 .setIsAuxiliary(isAuxiliary)
4136 .setIsAsciiCapable(isAsciiCapable);
4137 final String subtypeIdString =
4138 parser.getAttributeValue(null, ATTR_IME_SUBTYPE_ID);
4139 if (subtypeIdString != null) {
4140 builder.setSubtypeId(Integer.parseInt(subtypeIdString));
4142 tempSubtypesArray.add(builder.build());
4145 } catch (XmlPullParserException | IOException | NumberFormatException e) {
4146 Slog.w(TAG, "Error reading subtypes", e);
4152 private static final class LocalServiceImpl implements InputMethodManagerInternal {
4154 private final Handler mHandler;
4156 LocalServiceImpl(@NonNull final Handler handler) {
4161 public void setInteractive(boolean interactive) {
4162 // Do everything in handler so as not to block the caller.
4163 mHandler.sendMessage(mHandler.obtainMessage(MSG_SET_INTERACTIVE,
4164 interactive ? 1 : 0, 0));
4168 public void switchInputMethod(boolean forwardDirection) {
4169 // Do everything in handler so as not to block the caller.
4170 mHandler.sendMessage(mHandler.obtainMessage(MSG_SWITCH_IME,
4171 forwardDirection ? 1 : 0, 0));
4175 public void hideCurrentInputMethod() {
4176 mHandler.removeMessages(MSG_HIDE_CURRENT_INPUT_METHOD);
4177 mHandler.sendEmptyMessage(MSG_HIDE_CURRENT_INPUT_METHOD);
4181 private static String imeWindowStatusToString(final int imeWindowVis) {
4182 final StringBuilder sb = new StringBuilder();
4183 boolean first = true;
4184 if ((imeWindowVis & InputMethodService.IME_ACTIVE) != 0) {
4185 sb.append("Active");
4188 if ((imeWindowVis & InputMethodService.IME_VISIBLE) != 0) {
4192 sb.append("Visible");
4194 return sb.toString();
4198 public IInputContentUriToken createInputContentUriToken(@Nullable IBinder token,
4199 @Nullable Uri contentUri, @Nullable String packageName) {
4200 if (!calledFromValidUser()) {
4204 if (token == null) {
4205 throw new NullPointerException("token");
4207 if (packageName == null) {
4208 throw new NullPointerException("packageName");
4210 if (contentUri == null) {
4211 throw new NullPointerException("contentUri");
4213 final String contentUriScheme = contentUri.getScheme();
4214 if (!"content".equals(contentUriScheme)) {
4215 throw new InvalidParameterException("contentUri must have content scheme");
4218 synchronized (mMethodMap) {
4219 final int uid = Binder.getCallingUid();
4220 if (mCurMethodId == null) {
4223 if (mCurToken != token) {
4224 Slog.e(TAG, "Ignoring createInputContentUriToken mCurToken=" + mCurToken
4225 + " token=" + token);
4228 // We cannot simply distinguish a bad IME that reports an arbitrary package name from
4229 // an unfortunate IME whose internal state is already obsolete due to the asynchronous
4230 // nature of our system. Let's compare it with our internal record.
4231 if (!TextUtils.equals(mCurAttribute.packageName, packageName)) {
4232 Slog.e(TAG, "Ignoring createInputContentUriToken mCurAttribute.packageName="
4233 + mCurAttribute.packageName + " packageName=" + packageName);
4236 // This user ID can never bee spoofed.
4237 final int imeUserId = UserHandle.getUserId(uid);
4238 // This user ID can never bee spoofed.
4239 final int appUserId = UserHandle.getUserId(mCurClient.uid);
4240 // This user ID may be invalid if "contentUri" embedded an invalid user ID.
4241 final int contentUriOwnerUserId = ContentProvider.getUserIdFromUri(contentUri,
4243 final Uri contentUriWithoutUserId = ContentProvider.getUriWithoutUserId(contentUri);
4244 // Note: InputContentUriTokenHandler.take() checks whether the IME (specified by "uid")
4245 // actually has the right to grant a read permission for "contentUriWithoutUserId" that
4246 // is claimed to belong to "contentUriOwnerUserId". For example, specifying random
4247 // content URI and/or contentUriOwnerUserId just results in a SecurityException thrown
4248 // from InputContentUriTokenHandler.take() and can never be allowed beyond what is
4249 // actually allowed to "uid", which is guaranteed to be the IME's one.
4250 return new InputContentUriTokenHandler(contentUriWithoutUserId, uid,
4251 packageName, contentUriOwnerUserId, appUserId);
4256 public void reportFullscreenMode(IBinder token, boolean fullscreen) {
4257 if (!calledFromValidUser()) {
4260 synchronized (mMethodMap) {
4261 if (!calledWithValidToken(token)) {
4264 if (mCurClient != null && mCurClient.client != null) {
4265 mInFullscreenMode = fullscreen;
4266 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
4267 MSG_REPORT_FULLSCREEN_MODE, fullscreen ? 1 : 0, mCurClient));
4273 protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
4274 if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
4275 != PackageManager.PERMISSION_GRANTED) {
4277 pw.println("Permission Denial: can't dump InputMethodManager from from pid="
4278 + Binder.getCallingPid()
4279 + ", uid=" + Binder.getCallingUid());
4283 IInputMethod method;
4285 ClientState focusedWindowClient;
4287 final Printer p = new PrintWriterPrinter(pw);
4289 synchronized (mMethodMap) {
4290 p.println("Current Input Method Manager state:");
4291 int N = mMethodList.size();
4292 p.println(" Input Methods: mMethodMapUpdateCount=" + mMethodMapUpdateCount);
4293 for (int i=0; i<N; i++) {
4294 InputMethodInfo info = mMethodList.get(i);
4295 p.println(" InputMethod #" + i + ":");
4298 p.println(" Clients:");
4299 for (ClientState ci : mClients.values()) {
4300 p.println(" Client " + ci + ":");
4301 p.println(" client=" + ci.client);
4302 p.println(" inputContext=" + ci.inputContext);
4303 p.println(" sessionRequested=" + ci.sessionRequested);
4304 p.println(" curSession=" + ci.curSession);
4306 p.println(" mCurMethodId=" + mCurMethodId);
4307 client = mCurClient;
4308 p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
4309 p.println(" mCurFocusedWindow=" + mCurFocusedWindow
4310 + " softInputMode=" +
4311 InputMethodClient.softInputModeToString(mCurFocusedWindowSoftInputMode)
4312 + " client=" + mCurFocusedWindowClient);
4313 focusedWindowClient = mCurFocusedWindowClient;
4314 p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
4315 + " mBoundToMethod=" + mBoundToMethod);
4316 p.println(" mCurToken=" + mCurToken);
4317 p.println(" mCurIntent=" + mCurIntent);
4318 method = mCurMethod;
4319 p.println(" mCurMethod=" + mCurMethod);
4320 p.println(" mEnabledSession=" + mEnabledSession);
4321 p.println(" mImeWindowVis=" + imeWindowStatusToString(mImeWindowVis));
4322 p.println(" mShowRequested=" + mShowRequested
4323 + " mShowExplicitlyRequested=" + mShowExplicitlyRequested
4324 + " mShowForced=" + mShowForced
4325 + " mInputShown=" + mInputShown);
4326 p.println(" mInFullscreenMode=" + mInFullscreenMode);
4327 p.println(" mCurUserActionNotificationSequenceNumber="
4328 + mCurUserActionNotificationSequenceNumber);
4329 p.println(" mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
4330 p.println(" mSettingsObserver=" + mSettingsObserver);
4331 p.println(" mSwitchingController:");
4332 mSwitchingController.dump(p);
4333 p.println(" mSettings:");
4334 mSettings.dumpLocked(p, " ");
4336 p.println(" mStartInputHistory:");
4337 mStartInputHistory.dump(pw, " ");
4341 if (client != null) {
4344 TransferPipe.dumpAsync(client.client.asBinder(), fd, args);
4345 } catch (IOException | RemoteException e) {
4346 p.println("Failed to dump input method client: " + e);
4349 p.println("No input method client.");
4352 if (focusedWindowClient != null && client != focusedWindowClient) {
4354 p.println("Warning: Current input method client doesn't match the last focused. "
4356 p.println("Dumping input method client in the last focused window just in case.");
4360 TransferPipe.dumpAsync(focusedWindowClient.client.asBinder(), fd, args);
4361 } catch (IOException | RemoteException e) {
4362 p.println("Failed to dump input method client in focused window: " + e);
4367 if (method != null) {
4370 TransferPipe.dumpAsync(method.asBinder(), fd, args);
4371 } catch (IOException | RemoteException e) {
4372 p.println("Failed to dump input method service: " + e);
4375 p.println("No input method service.");