Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c88452c3 authored by Charles Chen's avatar Charles Chen
Browse files

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
parent d9f98db1
Loading
Loading
Loading
Loading
+29 −280
Original line number Diff line number Diff line
@@ -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);
+327 −0

File added.

Preview size limit exceeded, changes collapsed.