android_frameworks_base/services/java/com/android/server/InputMethodManagerService.java
John Spurlock e0980501e9 Simplify IME policy and enforce in binder calls.
Revert the overly complicated bc7b6fc2a4b701596a2c8eecc4aeef522abeeafa.

Since setImeWindowStatus can be called externally, enforce similar keyguard
policy so that this is not a backdoor for IMEs bound under the keyguard to
drive IME visibility.

Policy is simplified, we keep track of whether the keyguard is locked
when the current IME switches.

This fixes b/7498792 (activity under lockscreen drives IME visibility
when lockscreen is rotated) and does not break b/11186297 (IME visible
when BT keyboard is attached).

Bug:7498792
Change-Id: Idd335cc8259c3532fa869449a9048b420f286e6d
2013-10-29 10:47:07 -04:00

3575 lines
153 KiB
Java

/*
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.android.server;
import com.android.internal.content.PackageMonitor;
import com.android.internal.inputmethod.InputMethodUtils;
import com.android.internal.inputmethod.InputMethodUtils.InputMethodSettings;
import com.android.internal.os.HandlerCaller;
import com.android.internal.os.SomeArgs;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.view.IInputContext;
import com.android.internal.view.IInputMethod;
import com.android.internal.view.IInputSessionCallback;
import com.android.internal.view.IInputMethodClient;
import com.android.internal.view.IInputMethodManager;
import com.android.internal.view.IInputMethodSession;
import com.android.internal.view.InputBindResult;
import com.android.server.EventLogTags;
import com.android.server.wm.WindowManagerService;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import android.app.ActivityManagerNative;
import android.app.AppGlobals;
import android.app.AlertDialog;
import android.app.IUserSwitchObserver;
import android.app.KeyguardManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentResolver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.ContentObserver;
import android.inputmethodservice.InputMethodService;
import android.os.Binder;
import android.os.Environment;
import android.os.Handler;
import android.os.IBinder;
import android.os.IInterface;
import android.os.IRemoteCallback;
import android.os.Message;
import android.os.Process;
import android.os.Parcel;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;
import android.text.style.SuggestionSpan;
import android.util.AtomicFile;
import android.util.EventLog;
import android.util.LruCache;
import android.util.Pair;
import android.util.PrintWriterPrinter;
import android.util.Printer;
import android.util.Slog;
import android.util.Xml;
import android.view.IWindowManager;
import android.view.InputChannel;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputBinding;
import android.view.inputmethod.InputMethod;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;
import android.view.inputmethod.InputMethodSubtype;
import android.widget.ArrayAdapter;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.RadioButton;
import android.widget.Switch;
import android.widget.TextView;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.TreeMap;
/**
* This class provides a system service that manages input methods.
*/
public class InputMethodManagerService extends IInputMethodManager.Stub
implements ServiceConnection, Handler.Callback {
static final boolean DEBUG = false;
static final String TAG = "InputMethodManagerService";
static final int MSG_SHOW_IM_PICKER = 1;
static final int MSG_SHOW_IM_SUBTYPE_PICKER = 2;
static final int MSG_SHOW_IM_SUBTYPE_ENABLER = 3;
static final int MSG_SHOW_IM_CONFIG = 4;
static final int MSG_UNBIND_INPUT = 1000;
static final int MSG_BIND_INPUT = 1010;
static final int MSG_SHOW_SOFT_INPUT = 1020;
static final int MSG_HIDE_SOFT_INPUT = 1030;
static final int MSG_ATTACH_TOKEN = 1040;
static final int MSG_CREATE_SESSION = 1050;
static final int MSG_START_INPUT = 2000;
static final int MSG_RESTART_INPUT = 2010;
static final int MSG_UNBIND_METHOD = 3000;
static final int MSG_BIND_METHOD = 3010;
static final int MSG_SET_ACTIVE = 3020;
static final int MSG_HARD_KEYBOARD_SWITCH_CHANGED = 4000;
static final long TIME_TO_RECONNECT = 10*1000;
static final int SECURE_SUGGESTION_SPANS_MAX_SIZE = 20;
private static final int NOT_A_SUBTYPE_ID = InputMethodUtils.NOT_A_SUBTYPE_ID;
private static final String TAG_TRY_SUPPRESSING_IME_SWITCHER = "TrySuppressingImeSwitcher";
final Context mContext;
final Resources mRes;
final Handler mHandler;
final InputMethodSettings mSettings;
final SettingsObserver mSettingsObserver;
final IWindowManager mIWindowManager;
final HandlerCaller mCaller;
final boolean mHasFeature;
private InputMethodFileManager mFileManager;
private InputMethodAndSubtypeListManager mImListManager;
private final HardKeyboardListener mHardKeyboardListener;
private final WindowManagerService mWindowManagerService;
final InputBindResult mNoBinding = new InputBindResult(null, null, null, -1);
// All known input methods. mMethodMap also serves as the global
// lock for this class.
final ArrayList<InputMethodInfo> mMethodList = new ArrayList<InputMethodInfo>();
final HashMap<String, InputMethodInfo> mMethodMap = new HashMap<String, InputMethodInfo>();
private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
new LruCache<SuggestionSpan, InputMethodInfo>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
// Used to bring IME service up to visible adjustment while it is being shown.
final ServiceConnection mVisibleConnection = new ServiceConnection() {
@Override public void onServiceConnected(ComponentName name, IBinder service) {
}
@Override public void onServiceDisconnected(ComponentName name) {
}
};
boolean mVisibleBound = false;
// Ongoing notification
private NotificationManager mNotificationManager;
private KeyguardManager mKeyguardManager;
private StatusBarManagerService mStatusBar;
private Notification mImeSwitcherNotification;
private PendingIntent mImeSwitchPendingIntent;
private boolean mShowOngoingImeSwitcherForPhones;
private boolean mNotificationShown;
private final boolean mImeSelectedOnBoot;
class SessionState {
final ClientState client;
final IInputMethod method;
IInputMethodSession session;
InputChannel channel;
@Override
public String toString() {
return "SessionState{uid " + client.uid + " pid " + client.pid
+ " method " + Integer.toHexString(
System.identityHashCode(method))
+ " session " + Integer.toHexString(
System.identityHashCode(session))
+ " channel " + channel
+ "}";
}
SessionState(ClientState _client, IInputMethod _method,
IInputMethodSession _session, InputChannel _channel) {
client = _client;
method = _method;
session = _session;
channel = _channel;
}
}
static final class ClientState {
final IInputMethodClient client;
final IInputContext inputContext;
final int uid;
final int pid;
final InputBinding binding;
boolean sessionRequested;
SessionState curSession;
@Override
public String toString() {
return "ClientState{" + Integer.toHexString(
System.identityHashCode(this)) + " uid " + uid
+ " pid " + pid + "}";
}
ClientState(IInputMethodClient _client, IInputContext _inputContext,
int _uid, int _pid) {
client = _client;
inputContext = _inputContext;
uid = _uid;
pid = _pid;
binding = new InputBinding(null, inputContext.asBinder(), uid, pid);
}
}
final HashMap<IBinder, ClientState> mClients
= new HashMap<IBinder, ClientState>();
/**
* Set once the system is ready to run third party code.
*/
boolean mSystemReady;
/**
* Id of the currently selected input method.
*/
String mCurMethodId;
/**
* The current binding sequence number, incremented every time there is
* a new bind performed.
*/
int mCurSeq;
/**
* The client that is currently bound to an input method.
*/
ClientState mCurClient;
/**
* The last window token that gained focus.
*/
IBinder mCurFocusedWindow;
/**
* The input context last provided by the current client.
*/
IInputContext mCurInputContext;
/**
* The attributes last provided by the current client.
*/
EditorInfo mCurAttribute;
/**
* The input method ID of the input method service that we are currently
* connected to or in the process of connecting to.
*/
String mCurId;
/**
* The current subtype of the current input method.
*/
private InputMethodSubtype mCurrentSubtype;
// This list contains the pairs of InputMethodInfo and InputMethodSubtype.
private final HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>
mShortcutInputMethodsAndSubtypes =
new HashMap<InputMethodInfo, ArrayList<InputMethodSubtype>>();
// Was the keyguard locked when this client became current?
private boolean mCurClientInKeyguard;
/**
* Set to true if our ServiceConnection is currently actively bound to
* a service (whether or not we have gotten its IBinder back yet).
*/
boolean mHaveConnection;
/**
* Set if the client has asked for the input method to be shown.
*/
boolean mShowRequested;
/**
* Set if we were explicitly told to show the input method.
*/
boolean mShowExplicitlyRequested;
/**
* Set if we were forced to be shown.
*/
boolean mShowForced;
/**
* Set if we last told the input method to show itself.
*/
boolean mInputShown;
/**
* The Intent used to connect to the current input method.
*/
Intent mCurIntent;
/**
* The token we have made for the currently active input method, to
* identify it in the future.
*/
IBinder mCurToken;
/**
* If non-null, this is the input method service we are currently connected
* to.
*/
IInputMethod mCurMethod;
/**
* Time that we last initiated a bind to the input method, to determine
* if we should try to disconnect and reconnect to it.
*/
long mLastBindTime;
/**
* Have we called mCurMethod.bindInput()?
*/
boolean mBoundToMethod;
/**
* Currently enabled session. Only touched by service thread, not
* protected by a lock.
*/
SessionState mEnabledSession;
/**
* True if the screen is on. The value is true initially.
*/
boolean mScreenOn = true;
int mBackDisposition = InputMethodService.BACK_DISPOSITION_DEFAULT;
int mImeWindowVis;
private AlertDialog.Builder mDialogBuilder;
private AlertDialog mSwitchingDialog;
private View mSwitchingDialogTitleView;
private InputMethodInfo[] mIms;
private int[] mSubtypeIds;
private Locale mLastSystemLocale;
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
private final IPackageManager mIPackageManager;
class SettingsObserver extends ContentObserver {
String mLastEnabled = "";
SettingsObserver(Handler handler) {
super(handler);
ContentResolver resolver = mContext.getContentResolver();
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.DEFAULT_INPUT_METHOD), false, this);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.ENABLED_INPUT_METHODS), false, this);
resolver.registerContentObserver(Settings.Secure.getUriFor(
Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE), false, this);
}
@Override public void onChange(boolean selfChange) {
synchronized (mMethodMap) {
boolean enabledChanged = false;
String newEnabled = mSettings.getEnabledInputMethodsStr();
if (!mLastEnabled.equals(newEnabled)) {
mLastEnabled = newEnabled;
enabledChanged = true;
}
updateFromSettingsLocked(enabledChanged);
}
}
}
class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
private void updateActive() {
// Inform the current client of the change in active status
if (mCurClient != null && mCurClient.client != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
}
}
@Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
if (Intent.ACTION_SCREEN_ON.equals(action)) {
mScreenOn = true;
refreshImeWindowVisibilityLocked();
updateActive();
return;
} else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
mScreenOn = false;
setImeWindowVisibilityStatusHiddenLocked();
updateActive();
return;
} else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
hideInputMethodMenu();
// No need to updateActive
return;
} else {
Slog.w(TAG, "Unexpected intent " + intent);
}
}
}
class MyPackageMonitor extends PackageMonitor {
private boolean isChangingPackagesOfCurrentUser() {
final int userId = getChangingUserId();
final boolean retval = userId == mSettings.getCurrentUserId();
if (DEBUG) {
if (!retval) {
Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
}
}
return retval;
}
@Override
public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
if (!isChangingPackagesOfCurrentUser()) {
return false;
}
synchronized (mMethodMap) {
String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
InputMethodInfo imi = mMethodList.get(i);
if (imi.getId().equals(curInputMethodId)) {
for (String pkg : packages) {
if (imi.getPackageName().equals(pkg)) {
if (!doit) {
return true;
}
resetSelectedInputMethodAndSubtypeLocked("");
chooseNewDefaultIMELocked();
return true;
}
}
}
}
}
}
return false;
}
@Override
public void onSomePackagesChanged() {
if (!isChangingPackagesOfCurrentUser()) {
return;
}
synchronized (mMethodMap) {
InputMethodInfo curIm = null;
String curInputMethodId = mSettings.getSelectedInputMethod();
final int N = mMethodList.size();
if (curInputMethodId != null) {
for (int i=0; i<N; i++) {
InputMethodInfo imi = mMethodList.get(i);
final String imiId = imi.getId();
if (imiId.equals(curInputMethodId)) {
curIm = imi;
}
int change = isPackageDisappearing(imi.getPackageName());
if (isPackageModified(imi.getPackageName())) {
mFileManager.deleteAllInputMethodSubtypes(imiId);
}
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
Slog.i(TAG, "Input method uninstalled, disabling: "
+ imi.getComponent());
setInputMethodEnabledLocked(imi.getId(), false);
}
}
}
buildInputMethodListLocked(
mMethodList, mMethodMap, false /* resetDefaultEnabledIme */);
boolean changed = false;
if (curIm != null) {
int change = isPackageDisappearing(curIm.getPackageName());
if (change == PACKAGE_TEMPORARY_CHANGE
|| change == PACKAGE_PERMANENT_CHANGE) {
ServiceInfo si = null;
try {
si = mIPackageManager.getServiceInfo(
curIm.getComponent(), 0, mSettings.getCurrentUserId());
} catch (RemoteException ex) {
}
if (si == null) {
// Uh oh, current input method is no longer around!
// Pick another one...
Slog.i(TAG, "Current input method removed: " + curInputMethodId);
setImeWindowVisibilityStatusHiddenLocked();
if (!chooseNewDefaultIMELocked()) {
changed = true;
curIm = null;
Slog.i(TAG, "Unsetting current input method");
resetSelectedInputMethodAndSubtypeLocked("");
}
}
}
}
if (curIm == null) {
// We currently don't have a default input method... is
// one now available?
changed = chooseNewDefaultIMELocked();
}
if (changed) {
updateFromSettingsLocked(false);
}
}
}
}
private static final class MethodCallback extends IInputSessionCallback.Stub {
private final InputMethodManagerService mParentIMMS;
private final IInputMethod mMethod;
private final InputChannel mChannel;
MethodCallback(InputMethodManagerService imms, IInputMethod method,
InputChannel channel) {
mParentIMMS = imms;
mMethod = method;
mChannel = channel;
}
@Override
public void sessionCreated(IInputMethodSession session) {
mParentIMMS.onSessionCreated(mMethod, session, mChannel);
}
}
private class HardKeyboardListener
implements WindowManagerService.OnHardKeyboardStatusChangeListener {
@Override
public void onHardKeyboardStatusChange(boolean available, boolean enabled) {
mHandler.sendMessage(mHandler.obtainMessage(
MSG_HARD_KEYBOARD_SWITCH_CHANGED, available ? 1 : 0, enabled ? 1 : 0));
}
public void handleHardKeyboardStatusChange(boolean available, boolean enabled) {
if (DEBUG) {
Slog.w(TAG, "HardKeyboardStatusChanged: available = " + available + ", enabled = "
+ enabled);
}
synchronized(mMethodMap) {
if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
&& mSwitchingDialog.isShowing()) {
mSwitchingDialogTitleView.findViewById(
com.android.internal.R.id.hard_keyboard_section).setVisibility(
available ? View.VISIBLE : View.GONE);
}
}
}
}
public InputMethodManagerService(Context context, WindowManagerService windowManager) {
mIPackageManager = AppGlobals.getPackageManager();
mContext = context;
mRes = context.getResources();
mHandler = new Handler(this);
mIWindowManager = IWindowManager.Stub.asInterface(
ServiceManager.getService(Context.WINDOW_SERVICE));
mCaller = new HandlerCaller(context, null, new HandlerCaller.Callback() {
@Override
public void executeMessage(Message msg) {
handleMessage(msg);
}
}, true /*asyncHandler*/);
mWindowManagerService = windowManager;
mHardKeyboardListener = new HardKeyboardListener();
mHasFeature = context.getPackageManager().hasSystemFeature(
PackageManager.FEATURE_INPUT_METHODS);
mImeSwitcherNotification = new Notification();
mImeSwitcherNotification.icon = com.android.internal.R.drawable.ic_notification_ime_default;
mImeSwitcherNotification.when = 0;
mImeSwitcherNotification.flags = Notification.FLAG_ONGOING_EVENT;
mImeSwitcherNotification.tickerText = null;
mImeSwitcherNotification.defaults = 0; // please be quiet
mImeSwitcherNotification.sound = null;
mImeSwitcherNotification.vibrate = null;
// Tag this notification specially so SystemUI knows it's important
mImeSwitcherNotification.kind = new String[] { "android.system.imeswitcher" };
Intent intent = new Intent(Settings.ACTION_SHOW_INPUT_METHOD_PICKER);
mImeSwitchPendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0);
mShowOngoingImeSwitcherForPhones = false;
final IntentFilter broadcastFilter = new IntentFilter();
broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
mNotificationShown = false;
int userId = 0;
try {
ActivityManagerNative.getDefault().registerUserSwitchObserver(
new IUserSwitchObserver.Stub() {
@Override
public void onUserSwitching(int newUserId, IRemoteCallback reply) {
synchronized(mMethodMap) {
switchUserLocked(newUserId);
}
if (reply != null) {
try {
reply.sendResult(null);
} catch (RemoteException e) {
}
}
}
@Override
public void onUserSwitchComplete(int newUserId) throws RemoteException {
}
});
userId = ActivityManagerNative.getDefault().getCurrentUser().id;
} catch (RemoteException e) {
Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
}
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
// mSettings should be created before buildInputMethodListLocked
mSettings = new InputMethodSettings(
mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
mFileManager = new InputMethodFileManager(mMethodMap, userId);
mImListManager = new InputMethodAndSubtypeListManager(context, this);
// Just checking if defaultImiId is empty or not
final String defaultImiId = mSettings.getSelectedInputMethod();
if (DEBUG) {
Slog.d(TAG, "Initial default ime = " + defaultImiId);
}
mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
buildInputMethodListLocked(mMethodList, mMethodMap,
!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
mSettings.enableAllIMEsIfThereIsNoEnabledIME();
if (!mImeSelectedOnBoot) {
Slog.w(TAG, "No IME selected. Choose the most applicable IME.");
resetDefaultImeLocked(context);
}
mSettingsObserver = new SettingsObserver(mHandler);
updateFromSettingsLocked(true);
// IMMS wants to receive Intent.ACTION_LOCALE_CHANGED in order to update the current IME
// according to the new system locale.
final IntentFilter filter = new IntentFilter();
filter.addAction(Intent.ACTION_LOCALE_CHANGED);
mContext.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
synchronized(mMethodMap) {
resetStateIfCurrentLocaleChangedLocked();
}
}
}, filter);
}
private void resetDefaultImeLocked(Context context) {
// Do not reset the default (current) IME when it is a 3rd-party IME
if (mCurMethodId != null
&& !InputMethodUtils.isSystemIme(mMethodMap.get(mCurMethodId))) {
return;
}
InputMethodInfo defIm = null;
for (InputMethodInfo imi : mMethodList) {
if (defIm == null) {
if (InputMethodUtils.isValidSystemDefaultIme(
mSystemReady, imi, context)) {
defIm = imi;
Slog.i(TAG, "Selected default: " + imi.getId());
}
}
}
if (defIm == null && mMethodList.size() > 0) {
defIm = InputMethodUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodListLocked());
Slog.i(TAG, "No default found, using " + defIm.getId());
}
if (defIm != null) {
setSelectedInputMethodAndSubtypeLocked(defIm, NOT_A_SUBTYPE_ID, false);
}
}
private void resetAllInternalStateLocked(final boolean updateOnlyWhenLocaleChanged,
final boolean resetDefaultEnabledIme) {
if (!mSystemReady) {
// not system ready
return;
}
final Locale newLocale = mRes.getConfiguration().locale;
if (!updateOnlyWhenLocaleChanged
|| (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
if (!updateOnlyWhenLocaleChanged) {
hideCurrentInputLocked(0, null);
mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
if (DEBUG) {
Slog.i(TAG, "Locale has been changed to " + newLocale);
}
// InputMethodAndSubtypeListManager should be reset when the locale is changed.
mImListManager = new InputMethodAndSubtypeListManager(mContext, this);
buildInputMethodListLocked(mMethodList, mMethodMap, resetDefaultEnabledIme);
if (!updateOnlyWhenLocaleChanged) {
final String selectedImiId = mSettings.getSelectedInputMethod();
if (TextUtils.isEmpty(selectedImiId)) {
// This is the first time of the user switch and
// set the current ime to the proper one.
resetDefaultImeLocked(mContext);
}
} else {
// If the locale is changed, needs to reset the default ime
resetDefaultImeLocked(mContext);
}
updateFromSettingsLocked(true);
mLastSystemLocale = newLocale;
if (!updateOnlyWhenLocaleChanged) {
try {
startInputInnerLocked();
} catch (RuntimeException e) {
Slog.w(TAG, "Unexpected exception", e);
}
}
}
}
private void resetStateIfCurrentLocaleChangedLocked() {
resetAllInternalStateLocked(true /* updateOnlyWhenLocaleChanged */,
true /* resetDefaultImeLocked */);
}
private void switchUserLocked(int newUserId) {
mSettings.setCurrentUserId(newUserId);
// InputMethodFileManager should be reset when the user is changed
mFileManager = new InputMethodFileManager(mMethodMap, newUserId);
final String defaultImiId = mSettings.getSelectedInputMethod();
// For secondary users, the list of enabled IMEs may not have been updated since the
// callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may
// not be empty even if the IME has been uninstalled by the primary user.
// Even in such cases, IMMS works fine because it will find the most applicable
// IME for that user.
final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId);
if (DEBUG) {
Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId);
}
resetAllInternalStateLocked(false /* updateOnlyWhenLocaleChanged */,
initialUserSwitch /* needsToResetDefaultIme */);
if (initialUserSwitch) {
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(mContext.getPackageManager(),
mSettings.getEnabledInputMethodListLocked());
}
}
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
throws RemoteException {
try {
return super.onTransact(code, data, reply, flags);
} catch (RuntimeException e) {
// The input method manager only throws security exceptions, so let's
// log all others.
if (!(e instanceof SecurityException)) {
Slog.wtf(TAG, "Input Method Manager Crash", e);
}
throw e;
}
}
public void systemRunning(StatusBarManagerService statusBar) {
synchronized (mMethodMap) {
if (DEBUG) {
Slog.d(TAG, "--- systemReady");
}
if (!mSystemReady) {
mSystemReady = true;
mKeyguardManager =
(KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mStatusBar = statusBar;
statusBar.setIconVisibility("ime", false);
updateImeWindowStatusLocked();
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
com.android.internal.R.bool.show_ongoing_ime_switcher);
if (mShowOngoingImeSwitcherForPhones) {
mWindowManagerService.setOnHardKeyboardStatusChangeListener(
mHardKeyboardListener);
}
buildInputMethodListLocked(mMethodList, mMethodMap,
!mImeSelectedOnBoot /* resetDefaultEnabledIme */);
if (!mImeSelectedOnBoot) {
Slog.w(TAG, "Reset the default IME as \"Resource\" is ready here.");
resetStateIfCurrentLocaleChangedLocked();
InputMethodUtils.setNonSelectedSystemImesDisabledUntilUsed(
mContext.getPackageManager(),
mSettings.getEnabledInputMethodListLocked());
}
mLastSystemLocale = mRes.getConfiguration().locale;
try {
startInputInnerLocked();
} catch (RuntimeException e) {
Slog.w(TAG, "Unexpected exception", e);
}
}
}
}
private void setImeWindowVisibilityStatusHiddenLocked() {
mImeWindowVis = 0;
updateImeWindowStatusLocked();
}
private void refreshImeWindowVisibilityLocked() {
final Configuration conf = mRes.getConfiguration();
final boolean haveHardKeyboard = conf.keyboard
!= Configuration.KEYBOARD_NOKEYS;
final boolean hardKeyShown = haveHardKeyboard
&& conf.hardKeyboardHidden
!= Configuration.HARDKEYBOARDHIDDEN_YES;
final boolean isScreenLocked = isKeyguardLocked();
final boolean inputActive = !isScreenLocked && (mInputShown || hardKeyShown);
// We assume the softkeyboard is shown when the input is active as long as the
// hard keyboard is not shown.
final boolean inputVisible = inputActive && !hardKeyShown;
mImeWindowVis = (inputActive ? InputMethodService.IME_ACTIVE : 0)
| (inputVisible ? InputMethodService.IME_VISIBLE : 0);
updateImeWindowStatusLocked();
}
private void updateImeWindowStatusLocked() {
setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
}
// ---------------------------------------------------------------------------------------
// Check whether or not this is a valid IPC. Assumes an IPC is valid when either
// 1) it comes from the system process
// 2) the calling process' user id is identical to the current user id IMMS thinks.
private boolean calledFromValidUser() {
final int uid = Binder.getCallingUid();
final int userId = UserHandle.getUserId(uid);
if (DEBUG) {
Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+ "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+ " calling userId = " + userId + ", foreground user id = "
+ mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()
+ InputMethodUtils.getApiCallStack());
}
if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
return true;
}
// Caveat: A process which has INTERACT_ACROSS_USERS_FULL gets results for the
// foreground user, not for the user of that process. Accordingly InputMethodManagerService
// must not manage background users' states in any functions.
// Note that privacy-sensitive IPCs, such as setInputMethod, are still securely guarded
// by a token.
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
== PackageManager.PERMISSION_GRANTED) {
if (DEBUG) {
Slog.d(TAG, "--- Access granted because the calling process has "
+ "the INTERACT_ACROSS_USERS_FULL permission");
}
return true;
}
Slog.w(TAG, "--- IPC called from background users. Ignore. \n"
+ InputMethodUtils.getStackTrace());
return false;
}
private boolean bindCurrentInputMethodService(
Intent service, ServiceConnection conn, int flags) {
if (service == null || conn == null) {
Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
return false;
}
return mContext.bindServiceAsUser(service, conn, flags,
new UserHandle(mSettings.getCurrentUserId()));
}
@Override
public List<InputMethodInfo> getInputMethodList() {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return Collections.emptyList();
}
synchronized (mMethodMap) {
return new ArrayList<InputMethodInfo>(mMethodList);
}
}
@Override
public List<InputMethodInfo> getEnabledInputMethodList() {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return Collections.emptyList();
}
synchronized (mMethodMap) {
return mSettings.getEnabledInputMethodListLocked();
}
}
private HashMap<InputMethodInfo, List<InputMethodSubtype>>
getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
enabledInputMethodAndSubtypes.put(
imi, mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true));
}
return enabledInputMethodAndSubtypes;
}
/**
* @param imiId if null, returns enabled subtypes for the current imi
* @return enabled subtypes of the specified imi
*/
@Override
public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId,
boolean allowsImplicitlySelectedSubtypes) {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return Collections.<InputMethodSubtype>emptyList();
}
synchronized (mMethodMap) {
final InputMethodInfo imi;
if (imiId == null && mCurMethodId != null) {
imi = mMethodMap.get(mCurMethodId);
} else {
imi = mMethodMap.get(imiId);
}
if (imi == null) {
return Collections.<InputMethodSubtype>emptyList();
}
return mSettings.getEnabledInputMethodSubtypeListLocked(
mContext, imi, allowsImplicitlySelectedSubtypes);
}
}
@Override
public void addClient(IInputMethodClient client,
IInputContext inputContext, int uid, int pid) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
mClients.put(client.asBinder(), new ClientState(client,
inputContext, uid, pid));
}
}
@Override
public void removeClient(IInputMethodClient client) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
ClientState cs = mClients.remove(client.asBinder());
if (cs != null) {
clearClientSessionLocked(cs);
}
}
}
void executeOrSendMessage(IInterface target, Message msg) {
if (target.asBinder() instanceof Binder) {
mCaller.sendMessage(msg);
} else {
handleMessage(msg);
msg.recycle();
}
}
void unbindCurrentClientLocked() {
if (mCurClient != null) {
if (DEBUG) Slog.v(TAG, "unbindCurrentInputLocked: client = "
+ mCurClient.client.asBinder());
if (mBoundToMethod) {
mBoundToMethod = false;
if (mCurMethod != null) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
MSG_UNBIND_INPUT, mCurMethod));
}
}
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_SET_ACTIVE, 0, mCurClient));
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
mCurClient.sessionRequested = false;
mCurClient = null;
hideInputMethodMenuLocked();
}
}
private int getImeShowFlags() {
int flags = 0;
if (mShowForced) {
flags |= InputMethod.SHOW_FORCED
| InputMethod.SHOW_EXPLICIT;
} else if (mShowExplicitlyRequested) {
flags |= InputMethod.SHOW_EXPLICIT;
}
return flags;
}
private int getAppShowFlags() {
int flags = 0;
if (mShowForced) {
flags |= InputMethodManager.SHOW_FORCED;
} else if (!mShowExplicitlyRequested) {
flags |= InputMethodManager.SHOW_IMPLICIT;
}
return flags;
}
InputBindResult attachNewInputLocked(boolean initial) {
if (!mBoundToMethod) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_BIND_INPUT, mCurMethod, mCurClient.binding));
mBoundToMethod = true;
}
final SessionState session = mCurClient.curSession;
if (initial) {
executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
MSG_START_INPUT, session, mCurInputContext, mCurAttribute));
} else {
executeOrSendMessage(session.method, mCaller.obtainMessageOOO(
MSG_RESTART_INPUT, session, mCurInputContext, mCurAttribute));
}
if (mShowRequested) {
if (DEBUG) Slog.v(TAG, "Attach new input asks to show input");
showCurrentInputLocked(getAppShowFlags(), null);
}
return new InputBindResult(session.session,
session.channel != null ? session.channel.dup() : null, mCurId, mCurSeq);
}
InputBindResult startInputLocked(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client "
+ client.asBinder());
}
try {
if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
// Check with the window manager to make sure this client actually
// has a window with focus. If not, reject. This is thread safe
// because if the focus changes some time before or after, the
// next client receiving focus that has any interest in input will
// be calling through here after that change happens.
Slog.w(TAG, "Starting input on non-focused client " + cs.client
+ " (uid=" + cs.uid + " pid=" + cs.pid + ")");
return null;
}
} catch (RemoteException e) {
}
return startInputUncheckedLocked(cs, inputContext, attribute, controlFlags);
}
InputBindResult startInputUncheckedLocked(ClientState cs,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
// If no method is currently selected, do nothing.
if (mCurMethodId == null) {
return mNoBinding;
}
if (mCurClient != cs) {
// Was the keyguard locked when switching over to the new client?
mCurClientInKeyguard = isKeyguardLocked();
// If the client is changing, we need to switch over to the new
// one.
unbindCurrentClientLocked();
if (DEBUG) Slog.v(TAG, "switching to client: client = "
+ cs.client.asBinder() + " keyguard=" + mCurClientInKeyguard);
// If the screen is on, inform the new client it is active
if (mScreenOn) {
executeOrSendMessage(cs.client, mCaller.obtainMessageIO(
MSG_SET_ACTIVE, mScreenOn ? 1 : 0, cs));
}
}
// Bump up the sequence for this client and attach it.
mCurSeq++;
if (mCurSeq <= 0) mCurSeq = 1;
mCurClient = cs;
mCurInputContext = inputContext;
mCurAttribute = attribute;
// Check if the input method is changing.
if (mCurId != null && mCurId.equals(mCurMethodId)) {
if (cs.curSession != null) {
// Fast case: if we are already connected to the input method,
// then just return it.
return attachNewInputLocked(
(controlFlags&InputMethodManager.CONTROL_START_INITIAL) != 0);
}
if (mHaveConnection) {
if (mCurMethod != null) {
// Return to client, and we will get back with it when
// we have had a session made for it.
requestClientSessionLocked(cs);
return new InputBindResult(null, null, mCurId, mCurSeq);
} else if (SystemClock.uptimeMillis()
< (mLastBindTime+TIME_TO_RECONNECT)) {
// In this case we have connected to the service, but
// don't yet have its interface. If it hasn't been too
// long since we did the connection, we'll return to
// the client and wait to get the service interface so
// we can report back. If it has been too long, we want
// to fall through so we can try a disconnect/reconnect
// to see if we can get back in touch with the service.
return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME,
mCurMethodId, SystemClock.uptimeMillis()-mLastBindTime, 0);
}
}
}
return startInputInnerLocked();
}
InputBindResult startInputInnerLocked() {
if (mCurMethodId == null) {
return mNoBinding;
}
if (!mSystemReady) {
// If the system is not yet ready, we shouldn't be running third
// party code.
return new InputBindResult(null, null, mCurMethodId, mCurSeq);
}
InputMethodInfo info = mMethodMap.get(mCurMethodId);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
unbindCurrentMethodLocked(false, true);
mCurIntent = new Intent(InputMethod.SERVICE_INTERFACE);
mCurIntent.setComponent(info.getComponent());
mCurIntent.putExtra(Intent.EXTRA_CLIENT_LABEL,
com.android.internal.R.string.input_method_binding_label);
mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) {
mLastBindTime = SystemClock.uptimeMillis();
mHaveConnection = true;
mCurId = info.getId();
mCurToken = new Binder();
try {
if (true || DEBUG) Slog.v(TAG, "Adding window token: " + mCurToken);
mIWindowManager.addWindowToken(mCurToken,
WindowManager.LayoutParams.TYPE_INPUT_METHOD);
} catch (RemoteException e) {
}
return new InputBindResult(null, null, mCurId, mCurSeq);
} else {
mCurIntent = null;
Slog.w(TAG, "Failure connecting to input method service: "
+ mCurIntent);
}
return null;
}
@Override
public InputBindResult startInput(IInputMethodClient client,
IInputContext inputContext, EditorInfo attribute, int controlFlags) {
if (!calledFromValidUser()) {
return null;
}
synchronized (mMethodMap) {
final long ident = Binder.clearCallingIdentity();
try {
return startInputLocked(client, inputContext, attribute, controlFlags);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void finishInput(IInputMethodClient client) {
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
synchronized (mMethodMap) {
if (mCurIntent != null && name.equals(mCurIntent.getComponent())) {
mCurMethod = IInputMethod.Stub.asInterface(service);
if (mCurToken == null) {
Slog.w(TAG, "Service connected without a token!");
unbindCurrentMethodLocked(false, false);
return;
}
if (DEBUG) Slog.v(TAG, "Initiating attach with token: " + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_ATTACH_TOKEN, mCurMethod, mCurToken));
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
requestClientSessionLocked(mCurClient);
}
}
}
}
void onSessionCreated(IInputMethod method, IInputMethodSession session,
InputChannel channel) {
synchronized (mMethodMap) {
if (mCurMethod != null && method != null
&& mCurMethod.asBinder() == method.asBinder()) {
if (mCurClient != null) {
clearClientSessionLocked(mCurClient);
mCurClient.curSession = new SessionState(mCurClient,
method, session, channel);
InputBindResult res = attachNewInputLocked(true);
if (res.method != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageOO(
MSG_BIND_METHOD, mCurClient.client, res));
}
return;
}
}
}
// Session abandoned. Close its associated input channel.
channel.dispose();
}
void unbindCurrentMethodLocked(boolean reportToClient, boolean savePosition) {
if (mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
}
if (mHaveConnection) {
mContext.unbindService(this);
mHaveConnection = false;
}
if (mCurToken != null) {
try {
if (DEBUG) Slog.v(TAG, "Removing window token: " + mCurToken);
if ((mImeWindowVis & InputMethodService.IME_ACTIVE) != 0 && savePosition) {
// The current IME is shown. Hence an IME switch (transition) is happening.
mWindowManagerService.saveLastInputMethodWindowForTransition();
}
mIWindowManager.removeWindowToken(mCurToken);
} catch (RemoteException e) {
}
mCurToken = null;
}
mCurId = null;
clearCurMethodLocked();
if (reportToClient && mCurClient != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
}
}
void requestClientSessionLocked(ClientState cs) {
if (!cs.sessionRequested) {
if (DEBUG) Slog.v(TAG, "Creating new session for client " + cs);
InputChannel[] channels = InputChannel.openInputChannelPair(cs.toString());
cs.sessionRequested = true;
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOOO(
MSG_CREATE_SESSION, mCurMethod, channels[1],
new MethodCallback(this, mCurMethod, channels[0])));
}
}
void clearClientSessionLocked(ClientState cs) {
finishSessionLocked(cs.curSession);
cs.curSession = null;
cs.sessionRequested = false;
}
private void finishSessionLocked(SessionState sessionState) {
if (sessionState != null) {
if (sessionState.session != null) {
try {
sessionState.session.finishSession();
} catch (RemoteException e) {
Slog.w(TAG, "Session failed to close due to remote exception", e);
setImeWindowVisibilityStatusHiddenLocked();
}
sessionState.session = null;
}
if (sessionState.channel != null) {
sessionState.channel.dispose();
sessionState.channel = null;
}
}
}
void clearCurMethodLocked() {
if (mCurMethod != null) {
for (ClientState cs : mClients.values()) {
clearClientSessionLocked(cs);
}
finishSessionLocked(mEnabledSession);
mEnabledSession = null;
mCurMethod = null;
}
if (mStatusBar != null) {
mStatusBar.setIconVisibility("ime", false);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
synchronized (mMethodMap) {
if (DEBUG) Slog.v(TAG, "Service disconnected: " + name
+ " mCurIntent=" + mCurIntent);
if (mCurMethod != null && mCurIntent != null
&& name.equals(mCurIntent.getComponent())) {
clearCurMethodLocked();
// We consider this to be a new bind attempt, since the system
// should now try to restart the service for us.
mLastBindTime = SystemClock.uptimeMillis();
mShowRequested = mInputShown;
mInputShown = false;
if (mCurClient != null) {
executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
MSG_UNBIND_METHOD, mCurSeq, mCurClient.client));
}
}
}
}
@Override
public void updateStatusIcon(IBinder token, String packageName, int iconId) {
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
if (token == null || mCurToken != token) {
Slog.w(TAG, "Ignoring setInputMethod of uid " + uid + " token: " + token);
return;
}
synchronized (mMethodMap) {
if (iconId == 0) {
if (DEBUG) Slog.d(TAG, "hide the small icon for the input method");
if (mStatusBar != null) {
mStatusBar.setIconVisibility("ime", false);
}
} else if (packageName != null) {
if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
CharSequence contentDescription = null;
try {
// Use PackageManager to load label
final PackageManager packageManager = mContext.getPackageManager();
contentDescription = packageManager.getApplicationLabel(
mIPackageManager.getApplicationInfo(packageName, 0,
mSettings.getCurrentUserId()));
} catch (RemoteException e) {
/* ignore */
}
if (mStatusBar != null) {
mStatusBar.setIcon("ime", packageName, iconId, 0,
contentDescription != null
? contentDescription.toString() : null);
mStatusBar.setIconVisibility("ime", true);
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private boolean needsToShowImeSwitchOngoingNotification() {
if (!mShowOngoingImeSwitcherForPhones) return false;
if (isScreenLocked()) return false;
synchronized (mMethodMap) {
List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
final int N = imis.size();
if (N > 2) return true;
if (N < 1) return false;
int nonAuxCount = 0;
int auxCount = 0;
InputMethodSubtype nonAuxSubtype = null;
InputMethodSubtype auxSubtype = null;
for(int i = 0; i < N; ++i) {
final InputMethodInfo imi = imis.get(i);
final List<InputMethodSubtype> subtypes =
mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
final int subtypeCount = subtypes.size();
if (subtypeCount == 0) {
++nonAuxCount;
} else {
for (int j = 0; j < subtypeCount; ++j) {
final InputMethodSubtype subtype = subtypes.get(j);
if (!subtype.isAuxiliary()) {
++nonAuxCount;
nonAuxSubtype = subtype;
} else {
++auxCount;
auxSubtype = subtype;
}
}
}
}
if (nonAuxCount > 1 || auxCount > 1) {
return true;
} else if (nonAuxCount == 1 && auxCount == 1) {
if (nonAuxSubtype != null && auxSubtype != null
&& (nonAuxSubtype.getLocale().equals(auxSubtype.getLocale())
|| auxSubtype.overridesImplicitlyEnabledSubtype()
|| nonAuxSubtype.overridesImplicitlyEnabledSubtype())
&& nonAuxSubtype.containsExtraValueKey(TAG_TRY_SUPPRESSING_IME_SWITCHER)) {
return false;
}
return true;
}
return false;
}
}
private boolean isKeyguardLocked() {
return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
}
// Caution! This method is called in this class. Handle multi-user carefully
@SuppressWarnings("deprecation")
@Override
public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
final long ident = Binder.clearCallingIdentity();
try {
if (token == null || mCurToken != token) {
int uid = Binder.getCallingUid();
Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
return;
}
synchronized (mMethodMap) {
// apply policy for binder calls
if (vis != 0 && isKeyguardLocked() && !mCurClientInKeyguard) {
vis = 0;
}
mImeWindowVis = vis;
mBackDisposition = backDisposition;
if (mStatusBar != null) {
mStatusBar.setImeWindowStatus(token, vis, backDisposition);
}
final boolean iconVisibility = ((vis & (InputMethodService.IME_ACTIVE)) != 0)
&& (mWindowManagerService.isHardKeyboardAvailable()
|| (vis & (InputMethodService.IME_VISIBLE)) != 0);
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
// Used to load label
final CharSequence title = mRes.getText(
com.android.internal.R.string.select_input_method);
final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName(
mContext, imi, mCurrentSubtype);
mImeSwitcherNotification.setLatestEventInfo(
mContext, title, summary, mImeSwitchPendingIntent);
if (mNotificationManager != null) {
if (DEBUG) {
Slog.d(TAG, "--- show notification: label = " + summary);
}
mNotificationManager.notifyAsUser(null,
com.android.internal.R.string.select_input_method,
mImeSwitcherNotification, UserHandle.ALL);
mNotificationShown = true;
}
} else {
if (mNotificationShown && mNotificationManager != null) {
if (DEBUG) {
Slog.d(TAG, "--- hide notification");
}
mNotificationManager.cancelAsUser(null,
com.android.internal.R.string.select_input_method, UserHandle.ALL);
mNotificationShown = false;
}
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
for (int i = 0; i < spans.length; ++i) {
SuggestionSpan ss = spans[i];
if (!TextUtils.isEmpty(ss.getNotificationTargetClassName())) {
mSecureSuggestionSpans.put(ss, currentImi);
}
}
}
}
@Override
public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
// TODO: Do not send the intent if the process of the targetImi is already dead.
if (targetImi != null) {
final String[] suggestions = span.getSuggestions();
if (index < 0 || index >= suggestions.length) return false;
final String className = span.getNotificationTargetClassName();
final Intent intent = new Intent();
// Ensures that only a class in the original IME package will receive the
// notification.
intent.setClassName(targetImi.getPackageName(), className);
intent.setAction(SuggestionSpan.ACTION_SUGGESTION_PICKED);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_BEFORE, originalString);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_AFTER, suggestions[index]);
intent.putExtra(SuggestionSpan.SUGGESTION_SPAN_PICKED_HASHCODE, span.hashCode());
final long ident = Binder.clearCallingIdentity();
try {
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
} finally {
Binder.restoreCallingIdentity(ident);
}
return true;
}
}
return false;
}
void updateFromSettingsLocked(boolean enabledMayChange) {
if (enabledMayChange) {
List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
for (int i=0; i<enabled.size(); i++) {
// We allow the user to select "disabled until used" apps, so if they
// are enabling one of those here we now need to make it enabled.
InputMethodInfo imm = enabled.get(i);
try {
ApplicationInfo ai = mIPackageManager.getApplicationInfo(imm.getPackageName(),
PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
mSettings.getCurrentUserId());
if (ai != null && ai.enabledSetting
== PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
if (DEBUG) {
Slog.d(TAG, "Update state(" + imm.getId()
+ "): DISABLED_UNTIL_USED -> DEFAULT");
}
mIPackageManager.setApplicationEnabledSetting(imm.getPackageName(),
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT,
PackageManager.DONT_KILL_APP, mSettings.getCurrentUserId(),
mContext.getBasePackageName());
}
} catch (RemoteException e) {
}
}
}
// We are assuming that whoever is changing DEFAULT_INPUT_METHOD and
// ENABLED_INPUT_METHODS is taking care of keeping them correctly in
// sync, so we will never have a DEFAULT_INPUT_METHOD that is not
// enabled.
String id = mSettings.getSelectedInputMethod();
// There is no input method selected, try to choose new applicable input method.
if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
id = mSettings.getSelectedInputMethod();
}
if (!TextUtils.isEmpty(id)) {
try {
setInputMethodLocked(id, mSettings.getSelectedInputMethodSubtypeId(id));
} catch (IllegalArgumentException e) {
Slog.w(TAG, "Unknown input method from prefs: " + id, e);
mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
mShortcutInputMethodsAndSubtypes.clear();
} else {
// There is no longer an input method set, so stop any current one.
mCurMethodId = null;
unbindCurrentMethodLocked(true, false);
}
}
/* package */ void setInputMethodLocked(String id, int subtypeId) {
InputMethodInfo info = mMethodMap.get(id);
if (info == null) {
throw new IllegalArgumentException("Unknown id: " + id);
}
// See if we need to notify a subtype change within the same IME.
if (id.equals(mCurMethodId)) {
final int subtypeCount = info.getSubtypeCount();
if (subtypeCount <= 0) {
return;
}
final InputMethodSubtype oldSubtype = mCurrentSubtype;
final InputMethodSubtype newSubtype;
if (subtypeId >= 0 && subtypeId < subtypeCount) {
newSubtype = info.getSubtypeAt(subtypeId);
} else {
// If subtype is null, try to find the most applicable one from
// getCurrentInputMethodSubtype.
newSubtype = getCurrentInputMethodSubtypeLocked();
}
if (newSubtype == null || oldSubtype == null) {
Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
+ ", new subtype = " + newSubtype);
return;
}
if (newSubtype != oldSubtype) {
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, true);
if (mCurMethod != null) {
try {
refreshImeWindowVisibilityLocked();
mCurMethod.changeInputMethodSubtype(newSubtype);
} catch (RemoteException e) {
Slog.w(TAG, "Failed to call changeInputMethodSubtype");
}
}
}
return;
}
// Changing to a different IME.
final long ident = Binder.clearCallingIdentity();
try {
// Set a subtype to this input method.
// subtypeId the name of a subtype which will be set.
setSelectedInputMethodAndSubtypeLocked(info, subtypeId, false);
// mCurMethodId should be updated after setSelectedInputMethodAndSubtypeLocked()
// because mCurMethodId is stored as a history in
// setSelectedInputMethodAndSubtypeLocked().
mCurMethodId = id;
if (ActivityManagerNative.isSystemReady()) {
Intent intent = new Intent(Intent.ACTION_INPUT_METHOD_CHANGED);
intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING);
intent.putExtra("input_method_id", id);
mContext.sendBroadcastAsUser(intent, UserHandle.CURRENT);
}
unbindCurrentClientLocked();
} finally {
Binder.restoreCallingIdentity(ident);
}
}
@Override
public boolean showSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
if (!calledFromValidUser()) {
return false;
}
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
try {
// We need to check if this is the current client with
// focus in the window manager, to allow this call to
// be made before input is started in it.
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
Slog.w(TAG, "Ignoring showSoftInput of uid " + uid + ": " + client);
return false;
}
} catch (RemoteException e) {
return false;
}
}
if (DEBUG) Slog.v(TAG, "Client requesting input be shown");
return showCurrentInputLocked(flags, resultReceiver);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
boolean showCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
mShowRequested = true;
if ((flags&InputMethodManager.SHOW_IMPLICIT) == 0) {
mShowExplicitlyRequested = true;
}
if ((flags&InputMethodManager.SHOW_FORCED) != 0) {
mShowExplicitlyRequested = true;
mShowForced = true;
}
if (!mSystemReady) {
return false;
}
boolean res = false;
if (mCurMethod != null) {
if (DEBUG) Slog.d(TAG, "showCurrentInputLocked: mCurToken=" + mCurToken);
executeOrSendMessage(mCurMethod, mCaller.obtainMessageIOO(
MSG_SHOW_SOFT_INPUT, getImeShowFlags(), mCurMethod,
resultReceiver));
mInputShown = true;
if (mHaveConnection && !mVisibleBound) {
bindCurrentInputMethodService(
mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
mVisibleBound = true;
}
res = true;
} else if (mHaveConnection && SystemClock.uptimeMillis()
>= (mLastBindTime+TIME_TO_RECONNECT)) {
// The client has asked to have the input method shown, but
// we have been sitting here too long with a connection to the
// service and no interface received, so let's disconnect/connect
// to try to prod things along.
EventLog.writeEvent(EventLogTags.IMF_FORCE_RECONNECT_IME, mCurMethodId,
SystemClock.uptimeMillis()-mLastBindTime,1);
Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
mContext.unbindService(this);
bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
| Context.BIND_NOT_VISIBLE);
} else {
if (DEBUG) {
Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
+ ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
}
}
return res;
}
@Override
public boolean hideSoftInput(IInputMethodClient client, int flags,
ResultReceiver resultReceiver) {
if (!calledFromValidUser()) {
return false;
}
int uid = Binder.getCallingUid();
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
try {
// We need to check if this is the current client with
// focus in the window manager, to allow this call to
// be made before input is started in it.
if (!mIWindowManager.inputMethodClientHasFocus(client)) {
if (DEBUG) Slog.w(TAG, "Ignoring hideSoftInput of uid "
+ uid + ": " + client);
setImeWindowVisibilityStatusHiddenLocked();
return false;
}
} catch (RemoteException e) {
setImeWindowVisibilityStatusHiddenLocked();
return false;
}
}
if (DEBUG) Slog.v(TAG, "Client requesting input be hidden");
return hideCurrentInputLocked(flags, resultReceiver);
}
} finally {
Binder.restoreCallingIdentity(ident);
}
}
boolean hideCurrentInputLocked(int flags, ResultReceiver resultReceiver) {
if ((flags&InputMethodManager.HIDE_IMPLICIT_ONLY) != 0
&& (mShowExplicitlyRequested || mShowForced)) {
if (DEBUG) Slog.v(TAG, "Not hiding: explicit show not cancelled by non-explicit hide");
return false;
}
if (mShowForced && (flags&InputMethodManager.HIDE_NOT_ALWAYS) != 0) {
if (DEBUG) Slog.v(TAG, "Not hiding: forced show not cancelled by not-always hide");
return false;
}
boolean res;
if (mInputShown && mCurMethod != null) {
executeOrSendMessage(mCurMethod, mCaller.obtainMessageOO(
MSG_HIDE_SOFT_INPUT, mCurMethod, resultReceiver));
res = true;
} else {
res = false;
}
if (mHaveConnection && mVisibleBound) {
mContext.unbindService(mVisibleConnection);
mVisibleBound = false;
}
mInputShown = false;
mShowRequested = false;
mShowExplicitlyRequested = false;
mShowForced = false;
return res;
}
@Override
public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
int controlFlags, int softInputMode, int windowFlags,
EditorInfo attribute, IInputContext inputContext) {
// Needs to check the validity before clearing calling identity
final boolean calledFromValidUser = calledFromValidUser();
InputBindResult res = null;
long ident = Binder.clearCallingIdentity();
try {
synchronized (mMethodMap) {
if (DEBUG) Slog.v(TAG, "windowGainedFocus: " + client.asBinder()
+ " controlFlags=#" + Integer.toHexString(controlFlags)
+ " softInputMode=#" + Integer.toHexString(softInputMode)
+ " windowFlags=#" + Integer.toHexString(windowFlags));
ClientState cs = mClients.get(client.asBinder());
if (cs == null) {
throw new IllegalArgumentException("unknown client "
+ client.asBinder());
}
try {
if (!mIWindowManager.inputMethodClientHasFocus(cs.client)) {
// Check with the window manager to make sure this client actually
// has a window with focus. If not, reject. This is thread safe
// because if the focus changes some time before or after, the
// next client receiving focus that has any interest in input will
// be calling through here after that change happens.
Slog.w(TAG, "Focus gain on non-focused client " + cs.client
+ " (uid=" + cs.uid + " pid=" + cs.pid + ")");
return null;
}
} catch (RemoteException e) {
}
if (!calledFromValidUser) {
Slog.w(TAG, "A background user is requesting window. Hiding IME.");
Slog.w(TAG, "If you want to interect with IME, you need "
+ "android.permission.INTERACT_ACROSS_USERS_FULL");
hideCurrentInputLocked(0, null);
return null;
}
if (mCurFocusedWindow == windowToken) {
Slog.w(TAG, "Window already focused, ignoring focus gain of: " + client
+ " attribute=" + attribute + ", token = " + windowToken);
if (attribute != null) {
return startInputUncheckedLocked(cs, inputContext, attribute,
controlFlags);
}
return null;
}
mCurFocusedWindow = windowToken;
// Should we auto-show the IME even if the caller has not
// specified what should be done with it?
// We only do this automatically if the window can resize
// to accommodate the IME (so what the user sees will give
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
final boolean doAutoShow =
(softInputMode & WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST)
== WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE
|| mRes.getConfiguration().isLayoutSizeAtLeast(
Configuration.SCREENLAYOUT_SIZE_LARGE);
final boolean isTextEditor =
(controlFlags&InputMethodManager.CONTROL_WINDOW_IS_TEXT_EDITOR) != 0;
// We want to start input before showing the IME, but after closing
// it. We want to do this after closing it to help the IME disappear
// more quickly (not get stuck behind it initializing itself for the
// new focused input, even if its window wants to hide the IME).
boolean didStart = false;
switch (softInputMode&WindowManager.LayoutParams.SOFT_INPUT_MASK_STATE) {
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED:
if (!isTextEditor || !doAutoShow) {
if (WindowManager.LayoutParams.mayUseInputMethod(windowFlags)) {
// There is no focus view, and this window will
// be behind any soft input window, so hide the
// soft input window if it is shown.
if (DEBUG) Slog.v(TAG, "Unspecified window will hide input");
hideCurrentInputLocked(InputMethodManager.HIDE_NOT_ALWAYS, null);
}
} else if (isTextEditor && doAutoShow && (softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
// There is a focus view, and we are navigating forward
// into the window, so show the input window for the user.
// We only do this automatically if the window can resize
// to accommodate the IME (so what the user sees will give
// them good context without input information being obscured
// by the IME) or if running on a large screen where there
// is more room for the target window + IME.
if (DEBUG) Slog.v(TAG, "Unspecified window will show input");
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, attribute,
controlFlags);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
// Do nothing.
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN:
if ((softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to hide input going forward");
hideCurrentInputLocked(0, null);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN:
if (DEBUG) Slog.v(TAG, "Window asks to hide input");
hideCurrentInputLocked(0, null);
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE:
if ((softInputMode &
WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) != 0) {
if (DEBUG) Slog.v(TAG, "Window asks to show input going forward");
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, attribute,
controlFlags);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
}
break;
case WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE:
if (DEBUG) Slog.v(TAG, "Window asks to always show input");
if (attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, attribute,
controlFlags);
didStart = true;
}
showCurrentInputLocked(InputMethodManager.SHOW_IMPLICIT, null);
break;
}
if (!didStart && attribute != null) {
res = startInputUncheckedLocked(cs, inputContext, attribute,
controlFlags);
}
}
} finally {
Binder.restoreCallingIdentity(ident);
}
return res;
}
@Override
public void showInputMethodPickerFromClient(IInputMethodClient client) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
Slog.w(TAG, "Ignoring showInputMethodPickerFromClient of uid "
+ Binder.getCallingUid() + ": " + client);
}
// Always call subtype picker, because subtype picker is a superset of input method
// picker.
mHandler.sendEmptyMessage(MSG_SHOW_IM_SUBTYPE_PICKER);
}
}
@Override
public void setInputMethod(IBinder token, String id) {
if (!calledFromValidUser()) {
return;
}
setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
}
@Override
public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (subtype != null) {
setInputMethodWithSubtypeId(token, id, InputMethodUtils.getSubtypeIdFromHashCode(
mMethodMap.get(id), subtype.hashCode()));
} else {
setInputMethod(token, id);
}
}
}
@Override
public void showInputMethodAndSubtypeEnablerFromClient(
IInputMethodClient client, String inputMethodId) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (mCurClient == null || client == null
|| mCurClient.client.asBinder() != client.asBinder()) {
Slog.w(TAG, "Ignoring showInputMethodAndSubtypeEnablerFromClient of: " + client);
}
executeOrSendMessage(mCurMethod, mCaller.obtainMessageO(
MSG_SHOW_IM_SUBTYPE_ENABLER, inputMethodId));
}
}
@Override
public boolean switchToLastInputMethod(IBinder token) {
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
final InputMethodInfo lastImi;
if (lastIme != null) {
lastImi = mMethodMap.get(lastIme.first);
} else {
lastImi = null;
}
String targetLastImiId = null;
int subtypeId = NOT_A_SUBTYPE_ID;
if (lastIme != null && lastImi != null) {
final boolean imiIdIsSame = lastImi.getId().equals(mCurMethodId);
final int lastSubtypeHash = Integer.valueOf(lastIme.second);
final int currentSubtypeHash = mCurrentSubtype == null ? NOT_A_SUBTYPE_ID
: mCurrentSubtype.hashCode();
// If the last IME is the same as the current IME and the last subtype is not
// defined, there is no need to switch to the last IME.
if (!imiIdIsSame || lastSubtypeHash != currentSubtypeHash) {
targetLastImiId = lastIme.first;
subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
}
}
if (TextUtils.isEmpty(targetLastImiId)
&& !InputMethodUtils.canAddToLastInputMethod(mCurrentSubtype)) {
// This is a safety net. If the currentSubtype can't be added to the history
// and the framework couldn't find the last ime, we will make the last ime be
// the most applicable enabled keyboard subtype of the system imes.
final List<InputMethodInfo> enabled = mSettings.getEnabledInputMethodListLocked();
if (enabled != null) {
final int N = enabled.size();
final String locale = mCurrentSubtype == null
? mRes.getConfiguration().locale.toString()
: mCurrentSubtype.getLocale();
for (int i = 0; i < N; ++i) {
final InputMethodInfo imi = enabled.get(i);
if (imi.getSubtypeCount() > 0 && InputMethodUtils.isSystemIme(imi)) {
InputMethodSubtype keyboardSubtype =
InputMethodUtils.findLastResortApplicableSubtypeLocked(mRes,
InputMethodUtils.getSubtypes(imi),
InputMethodUtils.SUBTYPE_MODE_KEYBOARD, locale, true);
if (keyboardSubtype != null) {
targetLastImiId = imi.getId();
subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
imi, keyboardSubtype.hashCode());
if(keyboardSubtype.getLocale().equals(locale)) {
break;
}
}
}
}
}
}
if (!TextUtils.isEmpty(targetLastImiId)) {
if (DEBUG) {
Slog.d(TAG, "Switch to: " + lastImi.getId() + ", " + lastIme.second
+ ", from: " + mCurMethodId + ", " + subtypeId);
}
setInputMethodWithSubtypeId(token, targetLastImiId, subtypeId);
return true;
} else {
return false;
}
}
}
@Override
public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
if (nextSubtype == null) {
return false;
}
setInputMethodWithSubtypeId(token, nextSubtype.mImi.getId(), nextSubtype.mSubtypeId);
return true;
}
}
@Override
public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) {
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype);
if (nextSubtype == null) {
return false;
}
return true;
}
}
@Override
public InputMethodSubtype getLastInputMethodSubtype() {
if (!calledFromValidUser()) {
return null;
}
synchronized (mMethodMap) {
final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
// TODO: Handle the case of the last IME with no subtypes
if (lastIme == null || TextUtils.isEmpty(lastIme.first)
|| TextUtils.isEmpty(lastIme.second)) return null;
final InputMethodInfo lastImi = mMethodMap.get(lastIme.first);
if (lastImi == null) return null;
try {
final int lastSubtypeHash = Integer.valueOf(lastIme.second);
final int lastSubtypeId =
InputMethodUtils.getSubtypeIdFromHashCode(lastImi, lastSubtypeHash);
if (lastSubtypeId < 0 || lastSubtypeId >= lastImi.getSubtypeCount()) {
return null;
}
return lastImi.getSubtypeAt(lastSubtypeId);
} catch (NumberFormatException e) {
return null;
}
}
}
@Override
public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
if (!calledFromValidUser()) {
return;
}
// By this IPC call, only a process which shares the same uid with the IME can add
// additional input method subtypes to the IME.
if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
synchronized (mMethodMap) {
final InputMethodInfo imi = mMethodMap.get(imiId);
if (imi == null) return;
final String[] packageInfos;
try {
packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
} catch (RemoteException e) {
Slog.e(TAG, "Failed to get package infos");
return;
}
if (packageInfos != null) {
final int packageNum = packageInfos.length;
for (int i = 0; i < packageNum; ++i) {
if (packageInfos[i].equals(imi.getPackageName())) {
mFileManager.addInputMethodSubtypes(imi, subtypes);
final long ident = Binder.clearCallingIdentity();
try {
buildInputMethodListLocked(mMethodList, mMethodMap,
false /* resetDefaultEnabledIme */);
} finally {
Binder.restoreCallingIdentity(ident);
}
return;
}
}
}
}
return;
}
private void setInputMethodWithSubtypeId(IBinder token, String id, int subtypeId) {
synchronized (mMethodMap) {
if (token == null) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"Using null token requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
} else if (mCurToken != token) {
Slog.w(TAG, "Ignoring setInputMethod of uid " + Binder.getCallingUid()
+ " token: " + token);
return;
}
final long ident = Binder.clearCallingIdentity();
try {
setInputMethodLocked(id, subtypeId);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void hideMySoftInput(IBinder token, int flags) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
+ Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
try {
hideCurrentInputLocked(flags, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
@Override
public void showMySoftInput(IBinder token, int flags) {
if (!calledFromValidUser()) {
return;
}
synchronized (mMethodMap) {
if (token == null || mCurToken != token) {
Slog.w(TAG, "Ignoring showMySoftInput of uid "
+ Binder.getCallingUid() + " token: " + token);
return;
}
long ident = Binder.clearCallingIdentity();
try {
showCurrentInputLocked(flags, null);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
void setEnabledSessionInMainThread(SessionState session) {
if (mEnabledSession != session) {
if (mEnabledSession != null) {
try {
if (DEBUG) Slog.v(TAG, "Disabling: " + mEnabledSession);
mEnabledSession.method.setSessionEnabled(
mEnabledSession.session, false);
} catch (RemoteException e) {
}
}
mEnabledSession = session;
try {
if (DEBUG) Slog.v(TAG, "Enabling: " + mEnabledSession);
session.method.setSessionEnabled(
session.session, true);
} catch (RemoteException e) {
}
}
}
@Override
public boolean handleMessage(Message msg) {
SomeArgs args;
switch (msg.what) {
case MSG_SHOW_IM_PICKER:
showInputMethodMenu();
return true;
case MSG_SHOW_IM_SUBTYPE_PICKER:
showInputMethodSubtypeMenu();
return true;
case MSG_SHOW_IM_SUBTYPE_ENABLER:
args = (SomeArgs)msg.obj;
showInputMethodAndSubtypeEnabler((String)args.arg1);
args.recycle();
return true;
case MSG_SHOW_IM_CONFIG:
showConfigureInputMethods();
return true;
// ---------------------------------------------------------
case MSG_UNBIND_INPUT:
try {
((IInputMethod)msg.obj).unbindInput();
} catch (RemoteException e) {
// There is nothing interesting about the method dying.
}
return true;
case MSG_BIND_INPUT:
args = (SomeArgs)msg.obj;
try {
((IInputMethod)args.arg1).bindInput((InputBinding)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_SHOW_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".showSoftInput("
+ msg.arg1 + ", " + args.arg2 + ")");
((IInputMethod)args.arg1).showSoftInput(msg.arg1, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_HIDE_SOFT_INPUT:
args = (SomeArgs)msg.obj;
try {
if (DEBUG) Slog.v(TAG, "Calling " + args.arg1 + ".hideSoftInput(0, "
+ args.arg2 + ")");
((IInputMethod)args.arg1).hideSoftInput(0, (ResultReceiver)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_ATTACH_TOKEN:
args = (SomeArgs)msg.obj;
try {
if (DEBUG) Slog.v(TAG, "Sending attach of token: " + args.arg2);
((IInputMethod)args.arg1).attachToken((IBinder)args.arg2);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_CREATE_SESSION: {
args = (SomeArgs)msg.obj;
IInputMethod method = (IInputMethod)args.arg1;
InputChannel channel = (InputChannel)args.arg2;
try {
method.createSession(channel, (IInputSessionCallback)args.arg3);
} catch (RemoteException e) {
} finally {
// Dispose the channel if the input method is not local to this process
// because the remote proxy will get its own copy when unparceled.
if (channel != null && Binder.isProxy(method)) {
channel.dispose();
}
}
args.recycle();
return true;
}
// ---------------------------------------------------------
case MSG_START_INPUT:
args = (SomeArgs)msg.obj;
try {
SessionState session = (SessionState)args.arg1;
setEnabledSessionInMainThread(session);
session.method.startInput((IInputContext)args.arg2,
(EditorInfo)args.arg3);
} catch (RemoteException e) {
}
args.recycle();
return true;
case MSG_RESTART_INPUT:
args = (SomeArgs)msg.obj;
try {
SessionState session = (SessionState)args.arg1;
setEnabledSessionInMainThread(session);
session.method.restartInput((IInputContext)args.arg2,
(EditorInfo)args.arg3);
} catch (RemoteException e) {
}
args.recycle();
return true;
// ---------------------------------------------------------
case MSG_UNBIND_METHOD:
try {
((IInputMethodClient)msg.obj).onUnbindMethod(msg.arg1);
} catch (RemoteException e) {
// There is nothing interesting about the last client dying.
}
return true;
case MSG_BIND_METHOD: {
args = (SomeArgs)msg.obj;
IInputMethodClient client = (IInputMethodClient)args.arg1;
InputBindResult res = (InputBindResult)args.arg2;
try {
client.onBindMethod(res);
} catch (RemoteException e) {
Slog.w(TAG, "Client died receiving input method " + args.arg2);
} finally {
// Dispose the channel if the input method is not local to this process
// because the remote proxy will get its own copy when unparceled.
if (res.channel != null && Binder.isProxy(client)) {
res.channel.dispose();
}
}
args.recycle();
return true;
}
case MSG_SET_ACTIVE:
try {
((ClientState)msg.obj).client.setActive(msg.arg1 != 0);
} catch (RemoteException e) {
Slog.w(TAG, "Got RemoteException sending setActive(false) notification to pid "
+ ((ClientState)msg.obj).pid + " uid "
+ ((ClientState)msg.obj).uid);
}
return true;
// --------------------------------------------------------------
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
mHardKeyboardListener.handleHardKeyboardStatusChange(
msg.arg1 == 1, msg.arg2 == 1);
return true;
}
return false;
}
private boolean chooseNewDefaultIMELocked() {
final InputMethodInfo imi = InputMethodUtils.getMostApplicableDefaultIME(
mSettings.getEnabledInputMethodListLocked());
if (imi != null) {
if (DEBUG) {
Slog.d(TAG, "New default IME was selected: " + imi.getId());
}
resetSelectedInputMethodAndSubtypeLocked(imi.getId());
return true;
}
return false;
}
void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) {
if (DEBUG) {
Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme
+ " \n ------ \n" + InputMethodUtils.getStackTrace());
}
list.clear();
map.clear();
// Use for queryIntentServicesAsUser
final PackageManager pm = mContext.getPackageManager();
String disabledSysImes = mSettings.getDisabledSystemInputMethods();
if (disabledSysImes == null) disabledSysImes = "";
final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
new Intent(InputMethod.SERVICE_INTERFACE),
PackageManager.GET_META_DATA | PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS,
mSettings.getCurrentUserId());
final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
mFileManager.getAllAdditionalInputMethodSubtypes();
for (int i = 0; i < services.size(); ++i) {
ResolveInfo ri = services.get(i);
ServiceInfo si = ri.serviceInfo;
ComponentName compName = new ComponentName(si.packageName, si.name);
if (!android.Manifest.permission.BIND_INPUT_METHOD.equals(
si.permission)) {
Slog.w(TAG, "Skipping input method " + compName
+ ": it does not require the permission "
+ android.Manifest.permission.BIND_INPUT_METHOD);
continue;
}
if (DEBUG) Slog.d(TAG, "Checking " + compName);
try {
InputMethodInfo p = new InputMethodInfo(mContext, ri, additionalSubtypes);
list.add(p);
final String id = p.getId();
map.put(id, p);
if (DEBUG) {
Slog.d(TAG, "Found an input method " + p);
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Unable to load input method " + compName, e);
} catch (IOException e) {
Slog.w(TAG, "Unable to load input method " + compName, e);
}
}
if (resetDefaultEnabledIme) {
final ArrayList<InputMethodInfo> defaultEnabledIme =
InputMethodUtils.getDefaultEnabledImes(mContext, mSystemReady, list);
for (int i = 0; i < defaultEnabledIme.size(); ++i) {
final InputMethodInfo imi = defaultEnabledIme.get(i);
if (DEBUG) {
Slog.d(TAG, "--- enable ime = " + imi);
}
setInputMethodEnabledLocked(imi.getId(), true);
}
}
final String defaultImiId = mSettings.getSelectedInputMethod();
if (!TextUtils.isEmpty(defaultImiId)) {
if (!map.containsKey(defaultImiId)) {
Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
if (chooseNewDefaultIMELocked()) {
updateFromSettingsLocked(true);
}
} else {
// Double check that the default IME is certainly enabled.
setInputMethodEnabledLocked(defaultImiId, true);
}
}
}
// ----------------------------------------------------------------------
private void showInputMethodMenu() {
showInputMethodMenuInternal(false);
}
private void showInputMethodSubtypeMenu() {
showInputMethodMenuInternal(true);
}
private void showInputMethodAndSubtypeEnabler(String inputMethodId) {
Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SUBTYPE_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
if (!TextUtils.isEmpty(inputMethodId)) {
intent.putExtra(Settings.EXTRA_INPUT_METHOD_ID, inputMethodId);
}
mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
}
private void showConfigureInputMethods() {
Intent intent = new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
}
private boolean isScreenLocked() {
return mKeyguardManager != null
&& mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
}
private void showInputMethodMenuInternal(boolean showSubtypes) {
if (DEBUG) Slog.v(TAG, "Show switching menu");
final Context context = mContext;
final boolean isScreenLocked = isScreenLocked();
final String lastInputMethodId = mSettings.getSelectedInputMethod();
int lastInputMethodSubtypeId = mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
synchronized (mMethodMap) {
final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
if (immis == null || immis.size() == 0) {
return;
}
hideInputMethodMenuLocked();
final List<ImeSubtypeListItem> imList =
mImListManager.getSortedInputMethodAndSubtypeList(
showSubtypes, mInputShown, isScreenLocked);
if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
if (currentSubtype != null) {
final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
currentImi, currentSubtype.hashCode());
}
}
final int N = imList.size();
mIms = new InputMethodInfo[N];
mSubtypeIds = new int[N];
int checkedItem = 0;
for (int i = 0; i < N; ++i) {
final ImeSubtypeListItem item = imList.get(i);
mIms[i] = item.mImi;
mSubtypeIds[i] = item.mSubtypeId;
if (mIms[i].getId().equals(lastInputMethodId)) {
int subtypeId = mSubtypeIds[i];
if ((subtypeId == NOT_A_SUBTYPE_ID)
|| (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
|| (subtypeId == lastInputMethodSubtypeId)) {
checkedItem = i;
}
}
}
final TypedArray a = context.obtainStyledAttributes(null,
com.android.internal.R.styleable.DialogPreference,
com.android.internal.R.attr.alertDialogStyle, 0);
mDialogBuilder = new AlertDialog.Builder(context)
.setOnCancelListener(new OnCancelListener() {
@Override
public void onCancel(DialogInterface dialog) {
hideInputMethodMenu();
}
})
.setIcon(a.getDrawable(
com.android.internal.R.styleable.DialogPreference_dialogTitle));
a.recycle();
final LayoutInflater inflater =
(LayoutInflater)mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
final View tv = inflater.inflate(
com.android.internal.R.layout.input_method_switch_dialog_title, null);
mDialogBuilder.setCustomTitle(tv);
// Setup layout for a toggle switch of the hardware keyboard
mSwitchingDialogTitleView = tv;
mSwitchingDialogTitleView.findViewById(
com.android.internal.R.id.hard_keyboard_section).setVisibility(
mWindowManagerService.isHardKeyboardAvailable() ?
View.VISIBLE : View.GONE);
final Switch hardKeySwitch = ((Switch)mSwitchingDialogTitleView.findViewById(
com.android.internal.R.id.hard_keyboard_switch));
hardKeySwitch.setChecked(mWindowManagerService.isHardKeyboardEnabled());
hardKeySwitch.setOnCheckedChangeListener(
new OnCheckedChangeListener() {
@Override
public void onCheckedChanged(
CompoundButton buttonView, boolean isChecked) {
mWindowManagerService.setHardKeyboardEnabled(isChecked);
// Ensure that the input method dialog is dismissed when changing
// the hardware keyboard state.
hideInputMethodMenu();
}
});
final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(context,
com.android.internal.R.layout.simple_list_item_2_single_choice, imList,
checkedItem);
mDialogBuilder.setSingleChoiceItems(adapter, checkedItem,
new AlertDialog.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
synchronized (mMethodMap) {
if (mIms == null || mIms.length <= which
|| mSubtypeIds == null || mSubtypeIds.length <= which) {
return;
}
InputMethodInfo im = mIms[which];
int subtypeId = mSubtypeIds[which];
adapter.mCheckedItem = which;
adapter.notifyDataSetChanged();
hideInputMethodMenu();
if (im != null) {
if ((subtypeId < 0)
|| (subtypeId >= im.getSubtypeCount())) {
subtypeId = NOT_A_SUBTYPE_ID;
}
setInputMethodLocked(im.getId(), subtypeId);
}
}
}
});
if (showSubtypes && !isScreenLocked) {
mDialogBuilder.setPositiveButton(
com.android.internal.R.string.configure_input_methods,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int whichButton) {
showConfigureInputMethods();
}
});
}
mSwitchingDialog = mDialogBuilder.create();
mSwitchingDialog.setCanceledOnTouchOutside(true);
mSwitchingDialog.getWindow().setType(
WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
mSwitchingDialog.getWindow().getAttributes().privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS;
mSwitchingDialog.getWindow().getAttributes().setTitle("Select input method");
mSwitchingDialog.show();
}
}
private static class ImeSubtypeListItem implements Comparable<ImeSubtypeListItem> {
public final CharSequence mImeName;
public final CharSequence mSubtypeName;
public final InputMethodInfo mImi;
public final int mSubtypeId;
private final boolean mIsSystemLocale;
private final boolean mIsSystemLanguage;
public ImeSubtypeListItem(CharSequence imeName, CharSequence subtypeName,
InputMethodInfo imi, int subtypeId, String subtypeLocale, String systemLocale) {
mImeName = imeName;
mSubtypeName = subtypeName;
mImi = imi;
mSubtypeId = subtypeId;
if (TextUtils.isEmpty(subtypeLocale)) {
mIsSystemLocale = false;
mIsSystemLanguage = false;
} else {
mIsSystemLocale = subtypeLocale.equals(systemLocale);
mIsSystemLanguage = mIsSystemLocale
|| subtypeLocale.startsWith(systemLocale.substring(0, 2));
}
}
@Override
public int compareTo(ImeSubtypeListItem other) {
if (TextUtils.isEmpty(mImeName)) {
return 1;
}
if (TextUtils.isEmpty(other.mImeName)) {
return -1;
}
if (!TextUtils.equals(mImeName, other.mImeName)) {
return mImeName.toString().compareTo(other.mImeName.toString());
}
if (TextUtils.equals(mSubtypeName, other.mSubtypeName)) {
return 0;
}
if (mIsSystemLocale) {
return -1;
}
if (other.mIsSystemLocale) {
return 1;
}
if (mIsSystemLanguage) {
return -1;
}
if (other.mIsSystemLanguage) {
return 1;
}
if (TextUtils.isEmpty(mSubtypeName)) {
return 1;
}
if (TextUtils.isEmpty(other.mSubtypeName)) {
return -1;
}
return mSubtypeName.toString().compareTo(other.mSubtypeName.toString());
}
}
private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
private final LayoutInflater mInflater;
private final int mTextViewResourceId;
private final List<ImeSubtypeListItem> mItemsList;
public int mCheckedItem;
public ImeSubtypeListAdapter(Context context, int textViewResourceId,
List<ImeSubtypeListItem> itemsList, int checkedItem) {
super(context, textViewResourceId, itemsList);
mTextViewResourceId = textViewResourceId;
mItemsList = itemsList;
mCheckedItem = checkedItem;
mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
final View view = convertView != null ? convertView
: mInflater.inflate(mTextViewResourceId, null);
if (position < 0 || position >= mItemsList.size()) return view;
final ImeSubtypeListItem item = mItemsList.get(position);
final CharSequence imeName = item.mImeName;
final CharSequence subtypeName = item.mSubtypeName;
final TextView firstTextView = (TextView)view.findViewById(android.R.id.text1);
final TextView secondTextView = (TextView)view.findViewById(android.R.id.text2);
if (TextUtils.isEmpty(subtypeName)) {
firstTextView.setText(imeName);
secondTextView.setVisibility(View.GONE);
} else {
firstTextView.setText(subtypeName);
secondTextView.setText(imeName);
secondTextView.setVisibility(View.VISIBLE);
}
final RadioButton radioButton =
(RadioButton)view.findViewById(com.android.internal.R.id.radio);
radioButton.setChecked(position == mCheckedItem);
return view;
}
}
void hideInputMethodMenu() {
synchronized (mMethodMap) {
hideInputMethodMenuLocked();
}
}
void hideInputMethodMenuLocked() {
if (DEBUG) Slog.v(TAG, "Hide switching menu");
if (mSwitchingDialog != null) {
mSwitchingDialog.dismiss();
mSwitchingDialog = null;
}
mDialogBuilder = null;
mIms = null;
}
// ----------------------------------------------------------------------
@Override
public boolean setInputMethodEnabled(String id, boolean enabled) {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
if (mContext.checkCallingOrSelfPermission(
android.Manifest.permission.WRITE_SECURE_SETTINGS)
!= PackageManager.PERMISSION_GRANTED) {
throw new SecurityException(
"Requires permission "
+ android.Manifest.permission.WRITE_SECURE_SETTINGS);
}
long ident = Binder.clearCallingIdentity();
try {
return setInputMethodEnabledLocked(id, enabled);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
}
boolean setInputMethodEnabledLocked(String id, boolean enabled) {
// Make sure this is a valid input method.
InputMethodInfo imm = mMethodMap.get(id);
if (imm == null) {
throw new IllegalArgumentException("Unknown id: " + mCurMethodId);
}
List<Pair<String, ArrayList<String>>> enabledInputMethodsList = mSettings
.getEnabledInputMethodsAndSubtypeListLocked();
if (enabled) {
for (Pair<String, ArrayList<String>> pair: enabledInputMethodsList) {
if (pair.first.equals(id)) {
// We are enabling this input method, but it is already enabled.
// Nothing to do. The previous state was enabled.
return true;
}
}
mSettings.appendAndPutEnabledInputMethodLocked(id, false);
// Previous state was disabled.
return false;
} else {
StringBuilder builder = new StringBuilder();
if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
builder, enabledInputMethodsList, id)) {
// Disabled input method is currently selected, switch to another one.
final String selId = mSettings.getSelectedInputMethod();
if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
resetSelectedInputMethodAndSubtypeLocked("");
}
// Previous state was enabled.
return true;
} else {
// We are disabling the input method but it is already disabled.
// Nothing to do. The previous state was disabled.
return false;
}
}
}
private void setSelectedInputMethodAndSubtypeLocked(InputMethodInfo imi, int subtypeId,
boolean setSubtypeOnly) {
// Update the history of InputMethod and Subtype
mSettings.saveCurrentInputMethodAndSubtypeToHistory(mCurMethodId, mCurrentSubtype);
// Set Subtype here
if (imi == null || subtypeId < 0) {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
mCurrentSubtype = null;
} else {
if (subtypeId < imi.getSubtypeCount()) {
InputMethodSubtype subtype = imi.getSubtypeAt(subtypeId);
mSettings.putSelectedSubtype(subtype.hashCode());
mCurrentSubtype = subtype;
} else {
mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
// If the subtype is not specified, choose the most applicable one
mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
}
}
// Workaround.
// ASEC is not ready in the IMMS constructor. Accordingly, forward-locked
// IMEs are not recognized and considered uninstalled.
// Actually, we can't move everything after SystemReady because
// IMMS needs to run in the encryption lock screen. So, we just skip changing
// the default IME here and try cheking the default IME again in systemReady().
// TODO: Do nothing before system ready and implement a separated logic for
// the encryption lock screen.
// TODO: ASEC should be ready before IMMS is instantiated.
if (mSystemReady && !setSubtypeOnly) {
// Set InputMethod here
mSettings.putSelectedInputMethod(imi != null ? imi.getId() : "");
}
}
private void resetSelectedInputMethodAndSubtypeLocked(String newDefaultIme) {
InputMethodInfo imi = mMethodMap.get(newDefaultIme);
int lastSubtypeId = NOT_A_SUBTYPE_ID;
// newDefaultIme is empty when there is no candidate for the selected IME.
if (imi != null && !TextUtils.isEmpty(newDefaultIme)) {
String subtypeHashCode = mSettings.getLastSubtypeForInputMethodLocked(newDefaultIme);
if (subtypeHashCode != null) {
try {
lastSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
imi, Integer.valueOf(subtypeHashCode));
} catch (NumberFormatException e) {
Slog.w(TAG, "HashCode for subtype looks broken: " + subtypeHashCode, e);
}
}
}
setSelectedInputMethodAndSubtypeLocked(imi, lastSubtypeId, false);
}
// If there are no selected shortcuts, tries finding the most applicable ones.
private Pair<InputMethodInfo, InputMethodSubtype>
findLastResortApplicableShortcutInputMethodAndSubtypeLocked(String mode) {
List<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
InputMethodInfo mostApplicableIMI = null;
InputMethodSubtype mostApplicableSubtype = null;
boolean foundInSystemIME = false;
// Search applicable subtype for each InputMethodInfo
for (InputMethodInfo imi: imis) {
final String imiId = imi.getId();
if (foundInSystemIME && !imiId.equals(mCurMethodId)) {
continue;
}
InputMethodSubtype subtype = null;
final List<InputMethodSubtype> enabledSubtypes =
mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
// 1. Search by the current subtype's locale from enabledSubtypes.
if (mCurrentSubtype != null) {
subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, enabledSubtypes, mode, mCurrentSubtype.getLocale(), false);
}
// 2. Search by the system locale from enabledSubtypes.
// 3. Search the first enabled subtype matched with mode from enabledSubtypes.
if (subtype == null) {
subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, enabledSubtypes, mode, null, true);
}
final ArrayList<InputMethodSubtype> overridingImplicitlyEnabledSubtypes =
InputMethodUtils.getOverridingImplicitlyEnabledSubtypes(imi, mode);
final ArrayList<InputMethodSubtype> subtypesForSearch =
overridingImplicitlyEnabledSubtypes.isEmpty()
? InputMethodUtils.getSubtypes(imi)
: overridingImplicitlyEnabledSubtypes;
// 4. Search by the current subtype's locale from all subtypes.
if (subtype == null && mCurrentSubtype != null) {
subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, subtypesForSearch, mode, mCurrentSubtype.getLocale(), false);
}
// 5. Search by the system locale from all subtypes.
// 6. Search the first enabled subtype matched with mode from all subtypes.
if (subtype == null) {
subtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, subtypesForSearch, mode, null, true);
}
if (subtype != null) {
if (imiId.equals(mCurMethodId)) {
// The current input method is the most applicable IME.
mostApplicableIMI = imi;
mostApplicableSubtype = subtype;
break;
} else if (!foundInSystemIME) {
// The system input method is 2nd applicable IME.
mostApplicableIMI = imi;
mostApplicableSubtype = subtype;
if ((imi.getServiceInfo().applicationInfo.flags
& ApplicationInfo.FLAG_SYSTEM) != 0) {
foundInSystemIME = true;
}
}
}
}
if (DEBUG) {
if (mostApplicableIMI != null) {
Slog.w(TAG, "Most applicable shortcut input method was:"
+ mostApplicableIMI.getId());
if (mostApplicableSubtype != null) {
Slog.w(TAG, "Most applicable shortcut input method subtype was:"
+ "," + mostApplicableSubtype.getMode() + ","
+ mostApplicableSubtype.getLocale());
}
}
}
if (mostApplicableIMI != null) {
return new Pair<InputMethodInfo, InputMethodSubtype> (mostApplicableIMI,
mostApplicableSubtype);
} else {
return null;
}
}
/**
* @return Return the current subtype of this input method.
*/
@Override
public InputMethodSubtype getCurrentInputMethodSubtype() {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return null;
}
synchronized (mMethodMap) {
return getCurrentInputMethodSubtypeLocked();
}
}
private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
if (mCurMethodId == null) {
return null;
}
final boolean subtypeIsSelected = mSettings.isSubtypeSelected();
final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
if (imi == null || imi.getSubtypeCount() == 0) {
return null;
}
if (!subtypeIsSelected || mCurrentSubtype == null
|| !InputMethodUtils.isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
int subtypeId = mSettings.getSelectedInputMethodSubtypeId(mCurMethodId);
if (subtypeId == NOT_A_SUBTYPE_ID) {
// If there are no selected subtypes, the framework will try to find
// the most applicable subtype from explicitly or implicitly enabled
// subtypes.
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
mSettings.getEnabledInputMethodSubtypeListLocked(mContext, imi, true);
// If there is only one explicitly or implicitly enabled subtype,
// just returns it.
if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
} else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, explicitlyOrImplicitlyEnabledSubtypes,
InputMethodUtils.SUBTYPE_MODE_KEYBOARD, null, true);
if (mCurrentSubtype == null) {
mCurrentSubtype = InputMethodUtils.findLastResortApplicableSubtypeLocked(
mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
true);
}
}
} else {
mCurrentSubtype = InputMethodUtils.getSubtypes(imi).get(subtypeId);
}
}
return mCurrentSubtype;
}
private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
InputMethodSubtype subtype) {
if (mShortcutInputMethodsAndSubtypes.containsKey(imi)) {
mShortcutInputMethodsAndSubtypes.get(imi).add(subtype);
} else {
ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
subtypes.add(subtype);
mShortcutInputMethodsAndSubtypes.put(imi, subtypes);
}
}
// TODO: We should change the return type from List to List<Parcelable>
@SuppressWarnings("rawtypes")
@Override
public List getShortcutInputMethodsAndSubtypes() {
synchronized (mMethodMap) {
ArrayList<Object> ret = new ArrayList<Object>();
if (mShortcutInputMethodsAndSubtypes.size() == 0) {
// If there are no selected shortcut subtypes, the framework will try to find
// the most applicable subtype from all subtypes whose mode is
// SUBTYPE_MODE_VOICE. This is an exceptional case, so we will hardcode the mode.
Pair<InputMethodInfo, InputMethodSubtype> info =
findLastResortApplicableShortcutInputMethodAndSubtypeLocked(
InputMethodUtils.SUBTYPE_MODE_VOICE);
if (info != null) {
ret.add(info.first);
ret.add(info.second);
}
return ret;
}
for (InputMethodInfo imi: mShortcutInputMethodsAndSubtypes.keySet()) {
ret.add(imi);
for (InputMethodSubtype subtype: mShortcutInputMethodsAndSubtypes.get(imi)) {
ret.add(subtype);
}
}
return ret;
}
}
@Override
public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
// TODO: Make this work even for non-current users?
if (!calledFromValidUser()) {
return false;
}
synchronized (mMethodMap) {
if (subtype != null && mCurMethodId != null) {
InputMethodInfo imi = mMethodMap.get(mCurMethodId);
int subtypeId = InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode());
if (subtypeId != NOT_A_SUBTYPE_ID) {
setInputMethodLocked(mCurMethodId, subtypeId);
return true;
}
}
return false;
}
}
private static class InputMethodAndSubtypeListManager {
private final Context mContext;
// Used to load label
private final PackageManager mPm;
private final InputMethodManagerService mImms;
private final String mSystemLocaleStr;
public InputMethodAndSubtypeListManager(Context context, InputMethodManagerService imms) {
mContext = context;
mPm = context.getPackageManager();
mImms = imms;
final Locale locale = context.getResources().getConfiguration().locale;
mSystemLocaleStr = locale != null ? locale.toString() : "";
}
private final TreeMap<InputMethodInfo, List<InputMethodSubtype>> mSortedImmis =
new TreeMap<InputMethodInfo, List<InputMethodSubtype>>(
new Comparator<InputMethodInfo>() {
@Override
public int compare(InputMethodInfo imi1, InputMethodInfo imi2) {
if (imi2 == null) return 0;
if (imi1 == null) return 1;
if (mPm == null) {
return imi1.getId().compareTo(imi2.getId());
}
CharSequence imiId1 = imi1.loadLabel(mPm) + "/" + imi1.getId();
CharSequence imiId2 = imi2.loadLabel(mPm) + "/" + imi2.getId();
return imiId1.toString().compareTo(imiId2.toString());
}
});
public ImeSubtypeListItem getNextInputMethod(
boolean onlyCurrentIme, InputMethodInfo imi, InputMethodSubtype subtype) {
if (imi == null) {
return null;
}
final List<ImeSubtypeListItem> imList = getSortedInputMethodAndSubtypeList();
if (imList.size() <= 1) {
return null;
}
final int N = imList.size();
final int currentSubtypeId = subtype != null
? InputMethodUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
: NOT_A_SUBTYPE_ID;
for (int i = 0; i < N; ++i) {
final ImeSubtypeListItem isli = imList.get(i);
if (isli.mImi.equals(imi) && isli.mSubtypeId == currentSubtypeId) {
if (!onlyCurrentIme) {
return imList.get((i + 1) % N);
}
for (int j = 0; j < N - 1; ++j) {
final ImeSubtypeListItem candidate = imList.get((i + j + 1) % N);
if (candidate.mImi.equals(imi)) {
return candidate;
}
}
return null;
}
}
return null;
}
public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList() {
return getSortedInputMethodAndSubtypeList(true, false, false);
}
public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(boolean showSubtypes,
boolean inputShown, boolean isScreenLocked) {
final ArrayList<ImeSubtypeListItem> imList = new ArrayList<ImeSubtypeListItem>();
final HashMap<InputMethodInfo, List<InputMethodSubtype>> immis =
mImms.getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked();
if (immis == null || immis.size() == 0) {
return Collections.emptyList();
}
mSortedImmis.clear();
mSortedImmis.putAll(immis);
for (InputMethodInfo imi : mSortedImmis.keySet()) {
if (imi == null) continue;
List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList = immis.get(imi);
HashSet<String> enabledSubtypeSet = new HashSet<String>();
for (InputMethodSubtype subtype: explicitlyOrImplicitlyEnabledSubtypeList) {
enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
}
final CharSequence imeLabel = imi.loadLabel(mPm);
if (showSubtypes && enabledSubtypeSet.size() > 0) {
final int subtypeCount = imi.getSubtypeCount();
if (DEBUG) {
Slog.v(TAG, "Add subtypes: " + subtypeCount + ", " + imi.getId());
}
for (int j = 0; j < subtypeCount; ++j) {
final InputMethodSubtype subtype = imi.getSubtypeAt(j);
final String subtypeHashCode = String.valueOf(subtype.hashCode());
// We show all enabled IMEs and subtypes when an IME is shown.
if (enabledSubtypeSet.contains(subtypeHashCode)
&& ((inputShown && !isScreenLocked) || !subtype.isAuxiliary())) {
final CharSequence subtypeLabel =
subtype.overridesImplicitlyEnabledSubtype() ? null
: subtype.getDisplayName(mContext, imi.getPackageName(),
imi.getServiceInfo().applicationInfo);
imList.add(new ImeSubtypeListItem(imeLabel, subtypeLabel, imi, j,
subtype.getLocale(), mSystemLocaleStr));
// Removing this subtype from enabledSubtypeSet because we no longer
// need to add an entry of this subtype to imList to avoid duplicated
// entries.
enabledSubtypeSet.remove(subtypeHashCode);
}
}
} else {
imList.add(new ImeSubtypeListItem(imeLabel, null, imi, NOT_A_SUBTYPE_ID,
null, mSystemLocaleStr));
}
}
Collections.sort(imList);
return imList;
}
}
// TODO: Cache the state for each user and reset when the cached user is removed.
private static class InputMethodFileManager {
private static final String SYSTEM_PATH = "system";
private static final String INPUT_METHOD_PATH = "inputmethod";
private static final String ADDITIONAL_SUBTYPES_FILE_NAME = "subtypes.xml";
private static final String NODE_SUBTYPES = "subtypes";
private static final String NODE_SUBTYPE = "subtype";
private static final String NODE_IMI = "imi";
private static final String ATTR_ID = "id";
private static final String ATTR_LABEL = "label";
private static final String ATTR_ICON = "icon";
private static final String ATTR_IME_SUBTYPE_LOCALE = "imeSubtypeLocale";
private static final String ATTR_IME_SUBTYPE_MODE = "imeSubtypeMode";
private static final String ATTR_IME_SUBTYPE_EXTRA_VALUE = "imeSubtypeExtraValue";
private static final String ATTR_IS_AUXILIARY = "isAuxiliary";
private final AtomicFile mAdditionalInputMethodSubtypeFile;
private final HashMap<String, InputMethodInfo> mMethodMap;
private final HashMap<String, List<InputMethodSubtype>> mAdditionalSubtypesMap =
new HashMap<String, List<InputMethodSubtype>>();
public InputMethodFileManager(HashMap<String, InputMethodInfo> methodMap, int userId) {
if (methodMap == null) {
throw new NullPointerException("methodMap is null");
}
mMethodMap = methodMap;
final File systemDir = userId == UserHandle.USER_OWNER
? new File(Environment.getDataDirectory(), SYSTEM_PATH)
: Environment.getUserSystemDirectory(userId);
final File inputMethodDir = new File(systemDir, INPUT_METHOD_PATH);
if (!inputMethodDir.mkdirs()) {
Slog.w(TAG, "Couldn't create dir.: " + inputMethodDir.getAbsolutePath());
}
final File subtypeFile = new File(inputMethodDir, ADDITIONAL_SUBTYPES_FILE_NAME);
mAdditionalInputMethodSubtypeFile = new AtomicFile(subtypeFile);
if (!subtypeFile.exists()) {
// If "subtypes.xml" doesn't exist, create a blank file.
writeAdditionalInputMethodSubtypes(
mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, methodMap);
} else {
readAdditionalInputMethodSubtypes(
mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile);
}
}
private void deleteAllInputMethodSubtypes(String imiId) {
synchronized (mMethodMap) {
mAdditionalSubtypesMap.remove(imiId);
writeAdditionalInputMethodSubtypes(
mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
}
}
public void addInputMethodSubtypes(
InputMethodInfo imi, InputMethodSubtype[] additionalSubtypes) {
synchronized (mMethodMap) {
final ArrayList<InputMethodSubtype> subtypes = new ArrayList<InputMethodSubtype>();
final int N = additionalSubtypes.length;
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = additionalSubtypes[i];
if (!subtypes.contains(subtype)) {
subtypes.add(subtype);
} else {
Slog.w(TAG, "Duplicated subtype definition found: "
+ subtype.getLocale() + ", " + subtype.getMode());
}
}
mAdditionalSubtypesMap.put(imi.getId(), subtypes);
writeAdditionalInputMethodSubtypes(
mAdditionalSubtypesMap, mAdditionalInputMethodSubtypeFile, mMethodMap);
}
}
public HashMap<String, List<InputMethodSubtype>> getAllAdditionalInputMethodSubtypes() {
synchronized (mMethodMap) {
return mAdditionalSubtypesMap;
}
}
private static void writeAdditionalInputMethodSubtypes(
HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile,
HashMap<String, InputMethodInfo> methodMap) {
// Safety net for the case that this function is called before methodMap is set.
final boolean isSetMethodMap = methodMap != null && methodMap.size() > 0;
FileOutputStream fos = null;
try {
fos = subtypesFile.startWrite();
final XmlSerializer out = new FastXmlSerializer();
out.setOutput(fos, "utf-8");
out.startDocument(null, true);
out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
out.startTag(null, NODE_SUBTYPES);
for (String imiId : allSubtypes.keySet()) {
if (isSetMethodMap && !methodMap.containsKey(imiId)) {
Slog.w(TAG, "IME uninstalled or not valid.: " + imiId);
continue;
}
out.startTag(null, NODE_IMI);
out.attribute(null, ATTR_ID, imiId);
final List<InputMethodSubtype> subtypesList = allSubtypes.get(imiId);
final int N = subtypesList.size();
for (int i = 0; i < N; ++i) {
final InputMethodSubtype subtype = subtypesList.get(i);
out.startTag(null, NODE_SUBTYPE);
out.attribute(null, ATTR_ICON, String.valueOf(subtype.getIconResId()));
out.attribute(null, ATTR_LABEL, String.valueOf(subtype.getNameResId()));
out.attribute(null, ATTR_IME_SUBTYPE_LOCALE, subtype.getLocale());
out.attribute(null, ATTR_IME_SUBTYPE_MODE, subtype.getMode());
out.attribute(null, ATTR_IME_SUBTYPE_EXTRA_VALUE, subtype.getExtraValue());
out.attribute(null, ATTR_IS_AUXILIARY,
String.valueOf(subtype.isAuxiliary() ? 1 : 0));
out.endTag(null, NODE_SUBTYPE);
}
out.endTag(null, NODE_IMI);
}
out.endTag(null, NODE_SUBTYPES);
out.endDocument();
subtypesFile.finishWrite(fos);
} catch (java.io.IOException e) {
Slog.w(TAG, "Error writing subtypes", e);
if (fos != null) {
subtypesFile.failWrite(fos);
}
}
}
private static void readAdditionalInputMethodSubtypes(
HashMap<String, List<InputMethodSubtype>> allSubtypes, AtomicFile subtypesFile) {
if (allSubtypes == null || subtypesFile == null) return;
allSubtypes.clear();
FileInputStream fis = null;
try {
fis = subtypesFile.openRead();
final XmlPullParser parser = Xml.newPullParser();
parser.setInput(fis, null);
int type = parser.getEventType();
// Skip parsing until START_TAG
while ((type = parser.next()) != XmlPullParser.START_TAG
&& type != XmlPullParser.END_DOCUMENT) {}
String firstNodeName = parser.getName();
if (!NODE_SUBTYPES.equals(firstNodeName)) {
throw new XmlPullParserException("Xml doesn't start with subtypes");
}
final int depth =parser.getDepth();
String currentImiId = null;
ArrayList<InputMethodSubtype> tempSubtypesArray = null;
while (((type = parser.next()) != XmlPullParser.END_TAG
|| parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG)
continue;
final String nodeName = parser.getName();
if (NODE_IMI.equals(nodeName)) {
currentImiId = parser.getAttributeValue(null, ATTR_ID);
if (TextUtils.isEmpty(currentImiId)) {
Slog.w(TAG, "Invalid imi id found in subtypes.xml");
continue;
}
tempSubtypesArray = new ArrayList<InputMethodSubtype>();
allSubtypes.put(currentImiId, tempSubtypesArray);
} else if (NODE_SUBTYPE.equals(nodeName)) {
if (TextUtils.isEmpty(currentImiId) || tempSubtypesArray == null) {
Slog.w(TAG, "IME uninstalled or not valid.: " + currentImiId);
continue;
}
final int icon = Integer.valueOf(
parser.getAttributeValue(null, ATTR_ICON));
final int label = Integer.valueOf(
parser.getAttributeValue(null, ATTR_LABEL));
final String imeSubtypeLocale =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_LOCALE);
final String imeSubtypeMode =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_MODE);
final String imeSubtypeExtraValue =
parser.getAttributeValue(null, ATTR_IME_SUBTYPE_EXTRA_VALUE);
final boolean isAuxiliary = "1".equals(String.valueOf(
parser.getAttributeValue(null, ATTR_IS_AUXILIARY)));
final InputMethodSubtype subtype =
new InputMethodSubtype(label, icon, imeSubtypeLocale,
imeSubtypeMode, imeSubtypeExtraValue, isAuxiliary);
tempSubtypesArray.add(subtype);
}
}
} catch (XmlPullParserException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} catch (java.io.IOException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} catch (NumberFormatException e) {
Slog.w(TAG, "Error reading subtypes: " + e);
return;
} finally {
if (fis != null) {
try {
fis.close();
} catch (java.io.IOException e1) {
Slog.w(TAG, "Failed to close.");
}
}
}
}
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
!= PackageManager.PERMISSION_GRANTED) {
pw.println("Permission Denial: can't dump InputMethodManager from from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid());
return;
}
IInputMethod method;
ClientState client;
final Printer p = new PrintWriterPrinter(pw);
synchronized (mMethodMap) {
p.println("Current Input Method Manager state:");
int N = mMethodList.size();
p.println(" Input Methods:");
for (int i=0; i<N; i++) {
InputMethodInfo info = mMethodList.get(i);
p.println(" InputMethod #" + i + ":");
info.dump(p, " ");
}
p.println(" Clients:");
for (ClientState ci : mClients.values()) {
p.println(" Client " + ci + ":");
p.println(" client=" + ci.client);
p.println(" inputContext=" + ci.inputContext);
p.println(" sessionRequested=" + ci.sessionRequested);
p.println(" curSession=" + ci.curSession);
}
p.println(" mCurMethodId=" + mCurMethodId);
client = mCurClient;
p.println(" mCurClient=" + client + " mCurSeq=" + mCurSeq);
p.println(" mCurFocusedWindow=" + mCurFocusedWindow);
p.println(" mCurId=" + mCurId + " mHaveConnect=" + mHaveConnection
+ " mBoundToMethod=" + mBoundToMethod);
p.println(" mCurToken=" + mCurToken);
p.println(" mCurIntent=" + mCurIntent);
method = mCurMethod;
p.println(" mCurMethod=" + mCurMethod);
p.println(" mEnabledSession=" + mEnabledSession);
p.println(" mShowRequested=" + mShowRequested
+ " mShowExplicitlyRequested=" + mShowExplicitlyRequested
+ " mShowForced=" + mShowForced
+ " mInputShown=" + mInputShown);
p.println(" mSystemReady=" + mSystemReady + " mScreenOn=" + mScreenOn);
}
p.println(" ");
if (client != null) {
pw.flush();
try {
client.client.asBinder().dump(fd, args);
} catch (RemoteException e) {
p.println("Input method client dead: " + e);
}
} else {
p.println("No input method client.");
}
p.println(" ");
if (method != null) {
pw.flush();
try {
method.asBinder().dump(fd, args);
} catch (RemoteException e) {
p.println("Input method service dead: " + e);
}
} else {
p.println("No input method service.");
}
}
}