Introduce InputMethodMenuController
... for preparation to migrate the input method switch dialog to window context and easier for testing. Test: existing tests pass Bug: 157888351 Change-Id: Ief3c691029b14d848f6ad4a5ec8a8743760c7cda
This commit is contained in:
@@ -37,8 +37,6 @@ import android.annotation.RequiresPermission;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.app.ActivityManager;
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.AppGlobals;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.KeyguardManager;
|
||||
@@ -50,9 +48,6 @@ import android.content.ComponentName;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnCancelListener;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.ServiceConnection;
|
||||
@@ -64,10 +59,8 @@ 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.graphics.Matrix;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.hardware.display.DisplayManagerInternal;
|
||||
import android.hardware.input.InputManagerInternal;
|
||||
import android.inputmethodservice.InputMethodService;
|
||||
@@ -108,14 +101,10 @@ import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.imetracing.ImeTracing;
|
||||
import android.util.proto.ProtoOutputStream;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.DisplayInfo;
|
||||
import android.view.IWindowManager;
|
||||
import android.view.InputChannel;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager.LayoutParams;
|
||||
import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
|
||||
import android.view.autofill.AutofillId;
|
||||
@@ -129,12 +118,6 @@ 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 com.android.internal.annotations.GuardedBy;
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
@@ -165,7 +148,6 @@ import com.android.internal.view.InputBindResult;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.SystemService;
|
||||
import com.android.server.SystemService.TargetUser;
|
||||
import com.android.server.inputmethod.InputMethodManagerInternal.InputMethodListListener;
|
||||
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
|
||||
import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
|
||||
@@ -330,10 +312,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
private final ArrayMap<String, List<InputMethodSubtype>> mAdditionalSubtypeMap =
|
||||
new ArrayMap<>();
|
||||
private final boolean mIsLowRam;
|
||||
private final HardKeyboardListener mHardKeyboardListener;
|
||||
private final AppOpsManager mAppOpsManager;
|
||||
private final UserManager mUserManager;
|
||||
private final UserManagerInternal mUserManagerInternal;
|
||||
private final InputMethodMenuController mMenuController;
|
||||
|
||||
/**
|
||||
* Cache the result of {@code LocalServices.getService(AudioManagerInternal.class)}.
|
||||
@@ -351,7 +333,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
final ArrayMap<String, InputMethodInfo> mMethodMap = new ArrayMap<>();
|
||||
private final LruCache<SuggestionSpan, InputMethodInfo> mSecureSuggestionSpans =
|
||||
new LruCache<>(SECURE_SUGGESTION_SPANS_MAX_SIZE);
|
||||
private final InputMethodSubtypeSwitchingController mSwitchingController;
|
||||
final InputMethodSubtypeSwitchingController mSwitchingController;
|
||||
|
||||
/**
|
||||
* Tracks how many times {@link #mMethodMap} was updated.
|
||||
@@ -380,7 +362,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
// Ongoing notification
|
||||
private NotificationManager mNotificationManager;
|
||||
private KeyguardManager mKeyguardManager;
|
||||
KeyguardManager mKeyguardManager;
|
||||
private @Nullable StatusBarManagerService mStatusBar;
|
||||
private Notification.Builder mImeSwitcherNotification;
|
||||
private PendingIntent mImeSwitchPendingIntent;
|
||||
@@ -724,21 +706,14 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
*/
|
||||
int mImeWindowVis;
|
||||
|
||||
private AlertDialog.Builder mDialogBuilder;
|
||||
private AlertDialog mSwitchingDialog;
|
||||
private IBinder mSwitchingDialogToken = new Binder();
|
||||
private View mSwitchingDialogTitleView;
|
||||
private InputMethodInfo[] mIms;
|
||||
private int[] mSubtypeIds;
|
||||
private LocaleList mLastSystemLocales;
|
||||
private boolean mShowImeWithHardKeyboard;
|
||||
private boolean mAccessibilityRequestingNoSoftKeyboard;
|
||||
private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
|
||||
private final IPackageManager mIPackageManager;
|
||||
private final String mSlotIme;
|
||||
|
||||
/**
|
||||
* Registered {@link InputMethodListListeners}.
|
||||
* Registered {@link InputMethodListListener}.
|
||||
* This variable can be accessed from both of MainThread and BinderThread.
|
||||
*/
|
||||
private final CopyOnWriteArrayList<InputMethodListListener> mInputMethodListListeners =
|
||||
@@ -1138,7 +1113,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
Settings.Secure.ACCESSIBILITY_SOFT_KEYBOARD_MODE);
|
||||
synchronized (mMethodMap) {
|
||||
if (showImeUri.equals(uri)) {
|
||||
updateKeyboardFromSettingsLocked();
|
||||
mMenuController.updateKeyboardFromSettingsLocked();
|
||||
} else if (accessibilityRequestingNoImeUri.equals(uri)) {
|
||||
final int accessibilitySoftKeyboardSetting = Settings.Secure.getIntForUser(
|
||||
mContext.getContentResolver(),
|
||||
@@ -1226,7 +1201,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
return;
|
||||
}
|
||||
}
|
||||
hideInputMethodMenu();
|
||||
mMenuController.hideInputMethodMenu();
|
||||
} else {
|
||||
Slog.w(TAG, "Unexpected intent " + intent);
|
||||
}
|
||||
@@ -1526,29 +1501,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private class HardKeyboardListener
|
||||
implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
|
||||
@Override
|
||||
public void onHardKeyboardStatusChange(boolean available) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
|
||||
available ? 1 : 0));
|
||||
}
|
||||
|
||||
public void handleHardKeyboardStatusChange(boolean available) {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static final class UserSwitchHandlerTask implements Runnable {
|
||||
final InputMethodManagerService mService;
|
||||
|
||||
@@ -1687,7 +1639,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
mAppOpsManager = mContext.getSystemService(AppOpsManager.class);
|
||||
mUserManager = mContext.getSystemService(UserManager.class);
|
||||
mUserManagerInternal = LocalServices.getService(UserManagerInternal.class);
|
||||
mHardKeyboardListener = new HardKeyboardListener();
|
||||
mHasFeature = context.getPackageManager().hasSystemFeature(
|
||||
PackageManager.FEATURE_INPUT_METHODS);
|
||||
mSlotIme = mContext.getString(com.android.internal.R.string.status_bar_ime);
|
||||
@@ -1731,6 +1682,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
|
||||
mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
|
||||
mSettings, context);
|
||||
mMenuController = new InputMethodMenuController(this);
|
||||
}
|
||||
|
||||
private void resetDefaultImeLocked(Context context) {
|
||||
@@ -1861,8 +1813,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
mShowOngoingImeSwitcherForPhones = mRes.getBoolean(
|
||||
com.android.internal.R.bool.show_ongoing_ime_switcher);
|
||||
if (mShowOngoingImeSwitcherForPhones) {
|
||||
final InputMethodMenuController.HardKeyboardListener hardKeyboardListener =
|
||||
mMenuController.getHardKeyboardListener();
|
||||
mWindowManagerInternal.setOnHardKeyboardStatusChangeListener(
|
||||
mHardKeyboardListener);
|
||||
hardKeyboardListener);
|
||||
}
|
||||
|
||||
mMyPackageMonitor.register(mContext, null, UserHandle.ALL, true);
|
||||
@@ -2331,7 +2285,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
mCurClient = null;
|
||||
mCurActivityViewToScreenMatrix = null;
|
||||
|
||||
hideInputMethodMenuLocked();
|
||||
mMenuController.hideInputMethodMenuLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2813,7 +2767,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
private boolean shouldShowImeSwitcherLocked(int visibility) {
|
||||
if (!mShowOngoingImeSwitcherForPhones) return false;
|
||||
if (mSwitchingDialog != null) return false;
|
||||
if (mMenuController.getSwitchingDialogLocked() != null) return false;
|
||||
if (mWindowManagerInternal.isKeyguardShowingAndNotOccluded()
|
||||
&& mKeyguardManager != null && mKeyguardManager.isKeyguardSecure()) return false;
|
||||
if ((visibility & InputMethodService.IME_ACTIVE) == 0
|
||||
@@ -2930,6 +2884,10 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
void updateSystemUiLocked() {
|
||||
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
|
||||
}
|
||||
|
||||
// Caution! This method is called in this class. Handle multi-user carefully
|
||||
private void updateSystemUiLocked(int vis, int backDisposition) {
|
||||
if (mCurToken == null) {
|
||||
@@ -3000,7 +2958,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
void updateFromSettingsLocked(boolean enabledMayChange) {
|
||||
updateInputMethodsFromSettingsLocked(enabledMayChange);
|
||||
updateKeyboardFromSettingsLocked();
|
||||
mMenuController.updateKeyboardFromSettingsLocked();
|
||||
}
|
||||
|
||||
void updateInputMethodsFromSettingsLocked(boolean enabledMayChange) {
|
||||
@@ -3057,17 +3015,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
}
|
||||
|
||||
public void updateKeyboardFromSettingsLocked() {
|
||||
mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
|
||||
if (mSwitchingDialog != null
|
||||
&& mSwitchingDialogTitleView != null
|
||||
&& mSwitchingDialog.isShowing()) {
|
||||
final Switch hardKeySwitch = (Switch)mSwitchingDialogTitleView.findViewById(
|
||||
com.android.internal.R.id.hard_keyboard_switch);
|
||||
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ void setInputMethodLocked(String id, int subtypeId) {
|
||||
InputMethodInfo info = mMethodMap.get(id);
|
||||
if (info == null) {
|
||||
@@ -3697,10 +3644,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
public boolean isInputMethodPickerShownForTest() {
|
||||
synchronized(mMethodMap) {
|
||||
if (mSwitchingDialog == null) {
|
||||
return false;
|
||||
}
|
||||
return mSwitchingDialog.isShowing();
|
||||
return mMenuController.isisInputMethodPickerShownForTestLocked();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3935,7 +3879,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
* {@link InputMethodService#onCreate()}.
|
||||
*
|
||||
* <p>TODO(Bug 113914148): Check if we can remove this.</p>
|
||||
* @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight()}
|
||||
* @return {@link WindowManagerInternal#getInputMethodWindowVisibleHeight(int)}
|
||||
*/
|
||||
@Override
|
||||
public int getInputMethodWindowVisibleHeight() {
|
||||
@@ -4247,7 +4191,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
|
||||
return false;
|
||||
}
|
||||
showInputMethodMenu(showAuxSubtypes, displayId);
|
||||
mMenuController.showInputMethodMenu(showAuxSubtypes, displayId);
|
||||
return true;
|
||||
|
||||
case MSG_SHOW_IM_SUBTYPE_ENABLER:
|
||||
@@ -4484,7 +4428,9 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
|
||||
// --------------------------------------------------------------
|
||||
case MSG_HARD_KEYBOARD_SWITCH_CHANGED:
|
||||
mHardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
|
||||
final InputMethodMenuController.HardKeyboardListener hardKeyboardListener =
|
||||
mMenuController.getHardKeyboardListener();
|
||||
hardKeyboardListener.handleHardKeyboardStatusChange(msg.arg1 == 1);
|
||||
return true;
|
||||
case MSG_SYSTEM_UNLOCK_USER: {
|
||||
final int userId = msg.arg1;
|
||||
@@ -4735,208 +4681,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
mContext.startActivityAsUser(intent, null, UserHandle.CURRENT);
|
||||
}
|
||||
|
||||
private boolean isScreenLocked() {
|
||||
return mKeyguardManager != null
|
||||
&& mKeyguardManager.isKeyguardLocked() && mKeyguardManager.isKeyguardSecure();
|
||||
}
|
||||
|
||||
private void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
|
||||
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
|
||||
|
||||
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 List<ImeSubtypeListItem> imList =
|
||||
mSwitchingController.getSortedInputMethodAndSubtypeListLocked(
|
||||
showAuxSubtypes, isScreenLocked);
|
||||
if (imList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideInputMethodMenuLocked();
|
||||
|
||||
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 ActivityThread currentThread = ActivityThread.currentActivityThread();
|
||||
final Context settingsContext = new ContextThemeWrapper(
|
||||
displayId == DEFAULT_DISPLAY ? currentThread.getSystemUiContext()
|
||||
: currentThread.createSystemUiContext(displayId),
|
||||
com.android.internal.R.style.Theme_DeviceDefault_Settings);
|
||||
|
||||
mDialogBuilder = new AlertDialog.Builder(settingsContext);
|
||||
mDialogBuilder.setOnCancelListener(new OnCancelListener() {
|
||||
@Override
|
||||
public void onCancel(DialogInterface dialog) {
|
||||
hideInputMethodMenu();
|
||||
}
|
||||
});
|
||||
|
||||
final Context dialogContext = mDialogBuilder.getContext();
|
||||
final TypedArray a = dialogContext.obtainStyledAttributes(null,
|
||||
com.android.internal.R.styleable.DialogPreference,
|
||||
com.android.internal.R.attr.alertDialogStyle, 0);
|
||||
final Drawable dialogIcon = a.getDrawable(
|
||||
com.android.internal.R.styleable.DialogPreference_dialogIcon);
|
||||
a.recycle();
|
||||
|
||||
mDialogBuilder.setIcon(dialogIcon);
|
||||
|
||||
final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
|
||||
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(mWindowManagerInternal.isHardKeyboardAvailable()
|
||||
? View.VISIBLE : View.GONE);
|
||||
final Switch hardKeySwitch = (Switch) mSwitchingDialogTitleView.findViewById(
|
||||
com.android.internal.R.id.hard_keyboard_switch);
|
||||
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
|
||||
hardKeySwitch.setOnCheckedChangeListener(new OnCheckedChangeListener() {
|
||||
@Override
|
||||
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
|
||||
mSettings.setShowImeWithHardKeyboard(isChecked);
|
||||
// Ensure that the input method dialog is dismissed when changing
|
||||
// the hardware keyboard state.
|
||||
hideInputMethodMenu();
|
||||
}
|
||||
});
|
||||
|
||||
final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
|
||||
com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
|
||||
final OnClickListener choiceListener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(final DialogInterface dialog, final int which) {
|
||||
synchronized (mMethodMap) {
|
||||
if (mIms == null || mIms.length <= which || mSubtypeIds == null
|
||||
|| mSubtypeIds.length <= which) {
|
||||
return;
|
||||
}
|
||||
final 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
|
||||
|
||||
mSwitchingDialog = mDialogBuilder.create();
|
||||
mSwitchingDialog.setCanceledOnTouchOutside(true);
|
||||
final Window w = mSwitchingDialog.getWindow();
|
||||
final LayoutParams attrs = w.getAttributes();
|
||||
w.setType(LayoutParams.TYPE_INPUT_METHOD_DIALOG);
|
||||
// Use an alternate token for the dialog for that window manager can group the token
|
||||
// with other IME windows based on type vs. grouping based on whichever token happens
|
||||
// to get selected by the system later on.
|
||||
attrs.token = mSwitchingDialogToken;
|
||||
attrs.privateFlags |= LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
|
||||
attrs.setTitle("Select input method");
|
||||
w.setAttributes(attrs);
|
||||
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
|
||||
mSwitchingDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
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 = context.getSystemService(LayoutInflater.class);
|
||||
}
|
||||
|
||||
@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;
|
||||
mSwitchingDialogTitleView = null;
|
||||
}
|
||||
|
||||
updateSystemUiLocked(mImeWindowVis, mBackDisposition);
|
||||
mDialogBuilder = null;
|
||||
mIms = null;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
@@ -5040,7 +4784,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
}
|
||||
}
|
||||
|
||||
private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
|
||||
InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
|
||||
if (mCurMethodId == null) {
|
||||
return null;
|
||||
}
|
||||
@@ -5079,6 +4823,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub
|
||||
return mCurrentSubtype;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
String getCurrentMethodId() {
|
||||
return mCurMethodId;
|
||||
}
|
||||
|
||||
private List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
|
||||
synchronized (mMethodMap) {
|
||||
return getInputMethodListLocked(userId);
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
*
|
||||
* 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.inputmethod;
|
||||
|
||||
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
|
||||
import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
|
||||
import static com.android.server.inputmethod.InputMethodManagerService.MSG_HARD_KEYBOARD_SWITCH_CHANGED;
|
||||
import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
|
||||
|
||||
import android.app.ActivityThread;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.res.TypedArray;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.os.Handler;
|
||||
import android.os.IBinder;
|
||||
import android.text.TextUtils;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.Window;
|
||||
import android.view.WindowManager;
|
||||
import android.view.inputmethod.InputMethodInfo;
|
||||
import android.view.inputmethod.InputMethodSubtype;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.RadioButton;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.inputmethod.InputMethodSubtypeSwitchingController.ImeSubtypeListItem;
|
||||
import com.android.server.wm.WindowManagerInternal;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/** A controller to show/hide the input method menu */
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public class InputMethodMenuController {
|
||||
private static final String TAG = InputMethodMenuController.class.getSimpleName();
|
||||
|
||||
private final InputMethodManagerService mService;
|
||||
private final InputMethodUtils.InputMethodSettings mSettings;
|
||||
private final InputMethodSubtypeSwitchingController mSwitchingController;
|
||||
private final Handler mHandler;
|
||||
private final ArrayMap<String, InputMethodInfo> mMethodMap;
|
||||
private final KeyguardManager mKeyguardManager;
|
||||
private final HardKeyboardListener mHardKeyboardListener;
|
||||
private final WindowManagerInternal mWindowManagerInternal;
|
||||
|
||||
private Context mSettingsContext;
|
||||
private AlertDialog.Builder mDialogBuilder;
|
||||
private AlertDialog mSwitchingDialog;
|
||||
private IBinder mSwitchingDialogToken;
|
||||
private View mSwitchingDialogTitleView;
|
||||
private InputMethodInfo[] mIms;
|
||||
private int[] mSubtypeIds;
|
||||
|
||||
private boolean mShowImeWithHardKeyboard;
|
||||
|
||||
@VisibleForTesting(visibility = PACKAGE)
|
||||
public InputMethodMenuController(InputMethodManagerService service) {
|
||||
mService = service;
|
||||
mSettings = mService.mSettings;
|
||||
mSwitchingController = mService.mSwitchingController;
|
||||
mHandler = mService.mHandler;
|
||||
mMethodMap = mService.mMethodMap;
|
||||
mKeyguardManager = mService.mKeyguardManager;
|
||||
mHardKeyboardListener = new HardKeyboardListener();
|
||||
mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
|
||||
}
|
||||
|
||||
void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
|
||||
if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
|
||||
|
||||
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 List<ImeSubtypeListItem> imList = mSwitchingController
|
||||
.getSortedInputMethodAndSubtypeListLocked(showAuxSubtypes, isScreenLocked);
|
||||
if (imList.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
hideInputMethodMenuLocked();
|
||||
|
||||
if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
|
||||
final InputMethodSubtype currentSubtype =
|
||||
mService.getCurrentInputMethodSubtypeLocked();
|
||||
if (currentSubtype != null) {
|
||||
final String curMethodId = mService.getCurrentMethodId();
|
||||
final InputMethodInfo currentImi = mMethodMap.get(curMethodId);
|
||||
lastInputMethodSubtypeId = InputMethodUtils.getSubtypeIdFromHashCode(
|
||||
currentImi, currentSubtype.hashCode());
|
||||
}
|
||||
}
|
||||
|
||||
final int size = imList.size();
|
||||
mIms = new InputMethodInfo[size];
|
||||
mSubtypeIds = new int[size];
|
||||
int checkedItem = 0;
|
||||
for (int i = 0; i < size; ++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 ActivityThread currentThread = ActivityThread.currentActivityThread();
|
||||
final Context settingsContext = new ContextThemeWrapper(
|
||||
currentThread.createSystemUiContext(displayId),
|
||||
com.android.internal.R.style.Theme_DeviceDefault_Settings);
|
||||
mDialogBuilder = new AlertDialog.Builder(settingsContext);
|
||||
mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
|
||||
|
||||
final Context dialogContext = mDialogBuilder.getContext();
|
||||
final TypedArray a = dialogContext.obtainStyledAttributes(null,
|
||||
com.android.internal.R.styleable.DialogPreference,
|
||||
com.android.internal.R.attr.alertDialogStyle, 0);
|
||||
final Drawable dialogIcon = a.getDrawable(
|
||||
com.android.internal.R.styleable.DialogPreference_dialogIcon);
|
||||
a.recycle();
|
||||
|
||||
mDialogBuilder.setIcon(dialogIcon);
|
||||
|
||||
final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
|
||||
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(mWindowManagerInternal.isHardKeyboardAvailable()
|
||||
? View.VISIBLE : View.GONE);
|
||||
final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
|
||||
com.android.internal.R.id.hard_keyboard_switch);
|
||||
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
|
||||
hardKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
|
||||
mSettings.setShowImeWithHardKeyboard(isChecked);
|
||||
// Ensure that the input method dialog is dismissed when changing
|
||||
// the hardware keyboard state.
|
||||
hideInputMethodMenu();
|
||||
});
|
||||
|
||||
final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
|
||||
com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
|
||||
final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
|
||||
synchronized (mMethodMap) {
|
||||
if (mIms == null || mIms.length <= which || mSubtypeIds == null
|
||||
|| mSubtypeIds.length <= which) {
|
||||
return;
|
||||
}
|
||||
final 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;
|
||||
}
|
||||
mService.setInputMethodLocked(im.getId(), subtypeId);
|
||||
}
|
||||
}
|
||||
};
|
||||
mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
|
||||
|
||||
mSwitchingDialog = mDialogBuilder.create();
|
||||
mSwitchingDialog.setCanceledOnTouchOutside(true);
|
||||
final Window w = mSwitchingDialog.getWindow();
|
||||
final WindowManager.LayoutParams attrs = w.getAttributes();
|
||||
w.setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
|
||||
// Use an alternate token for the dialog for that window manager can group the token
|
||||
// with other IME windows based on type vs. grouping based on whichever token happens
|
||||
// to get selected by the system later on.
|
||||
attrs.token = mSwitchingDialogToken;
|
||||
attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
|
||||
attrs.setTitle("Select input method");
|
||||
w.setAttributes(attrs);
|
||||
mService.updateSystemUiLocked();
|
||||
mSwitchingDialog.show();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isScreenLocked() {
|
||||
return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked()
|
||||
&& mKeyguardManager.isKeyguardSecure();
|
||||
}
|
||||
|
||||
void updateKeyboardFromSettingsLocked() {
|
||||
mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
|
||||
if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
|
||||
&& mSwitchingDialog.isShowing()) {
|
||||
final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
|
||||
com.android.internal.R.id.hard_keyboard_switch);
|
||||
hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
|
||||
}
|
||||
}
|
||||
|
||||
void hideInputMethodMenu() {
|
||||
synchronized (mMethodMap) {
|
||||
hideInputMethodMenuLocked();
|
||||
}
|
||||
}
|
||||
|
||||
void hideInputMethodMenuLocked() {
|
||||
if (DEBUG) Slog.v(TAG, "Hide switching menu");
|
||||
|
||||
if (mSwitchingDialog != null) {
|
||||
mSwitchingDialog.dismiss();
|
||||
mSwitchingDialog = null;
|
||||
mSwitchingDialogTitleView = null;
|
||||
}
|
||||
|
||||
mService.updateSystemUiLocked();
|
||||
mDialogBuilder = null;
|
||||
mIms = null;
|
||||
}
|
||||
|
||||
HardKeyboardListener getHardKeyboardListener() {
|
||||
return mHardKeyboardListener;
|
||||
}
|
||||
|
||||
AlertDialog getSwitchingDialogLocked() {
|
||||
return mSwitchingDialog;
|
||||
}
|
||||
|
||||
boolean isisInputMethodPickerShownForTestLocked() {
|
||||
if (mSwitchingDialog == null) {
|
||||
return false;
|
||||
}
|
||||
return mSwitchingDialog.isShowing();
|
||||
}
|
||||
|
||||
class HardKeyboardListener implements WindowManagerInternal.OnHardKeyboardStatusChangeListener {
|
||||
@Override
|
||||
public void onHardKeyboardStatusChange(boolean available) {
|
||||
mHandler.sendMessage(mHandler.obtainMessage(MSG_HARD_KEYBOARD_SWITCH_CHANGED,
|
||||
available ? 1 : 0));
|
||||
}
|
||||
|
||||
public void handleHardKeyboardStatusChange(boolean available) {
|
||||
if (DEBUG) {
|
||||
Slog.w(TAG, "HardKeyboardStatusChanged: available=" + available);
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class ImeSubtypeListAdapter extends ArrayAdapter<ImeSubtypeListItem> {
|
||||
private final LayoutInflater mInflater;
|
||||
private final int mTextViewResourceId;
|
||||
private final List<ImeSubtypeListItem> mItemsList;
|
||||
public int mCheckedItem;
|
||||
private ImeSubtypeListAdapter(Context context, int textViewResourceId,
|
||||
List<ImeSubtypeListItem> itemsList, int checkedItem) {
|
||||
super(context, textViewResourceId, itemsList);
|
||||
|
||||
mTextViewResourceId = textViewResourceId;
|
||||
mItemsList = itemsList;
|
||||
mCheckedItem = checkedItem;
|
||||
mInflater = LayoutInflater.from(context);
|
||||
}
|
||||
|
||||
@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 = view.findViewById(android.R.id.text1);
|
||||
final TextView secondTextView = 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 = view.findViewById(com.android.internal.R.id.radio);
|
||||
radioButton.setChecked(position == mCheckedItem);
|
||||
return view;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user