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

Commit 46e75da5 authored by Yohei Yukawa's avatar Yohei Yukawa
Browse files

Prepare to make InputMethodSubtypeSwitchingController per-user

Previously we have reused the same instance of

  InputMethodManagerService#mSwitchingController

across users, which is known to be an error prone especially when
loading resources from APKs.

With this CL a new instance of InputMethodSubtypeSwitchingController
will be recreated every time the current IME user is switching.  This
is an important milestone to keep maintaining multiple instances of
InputMethodSubtypeSwitchingController for each user.

Also there remain no class that holds a strong reference to

  InputMethodManagerService#mSettings

with this CL.

Strictly speaking this is not a mechanical refactoring CL, but if
there is any observable behavior change the new behavior should make
more sense than the previous one.

Bug: 309868254
Bug: 309837937
Test: presubmit
Change-Id: Ibcc03107665975f237f6d7e0ee098d82e49d54ba
parent 5b15365f
Loading
Loading
Loading
Loading
+28 −16
Original line number Diff line number Diff line
@@ -316,7 +316,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
    // Mapping from deviceId to the device-specific imeId for that device.
    private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();

    private final InputMethodSubtypeSwitchingController mSwitchingController;
    // TODO: Instantiate mSwitchingController for each user.
    @NonNull
    private InputMethodSubtypeSwitchingController mSwitchingController;
    final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
            new HardwareKeyboardShortcutController();

@@ -1697,7 +1699,8 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

        AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
        mSwitchingController =
                InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
                InputMethodSubtypeSwitchingController.createInstanceLocked(context, mMethodMap,
                        userId);
        mHardwareKeyboardShortcutController.reset(mSettings);
        mMenuController = new InputMethodMenuController(this);
        mBindingController =
@@ -3204,13 +3207,16 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
            // There is no longer an input method set, so stop any current one.
            resetCurrentMethodAndClientLocked(UnbindReason.NO_IME);
        }
        // Here is not the perfect place to reset the switching controller. Ideally
        // mSwitchingController and mSettings should be able to share the same state.
        // TODO: Make sure that mSwitchingController and mSettings are sharing the
        // the same enabled IMEs list.
        mSwitchingController.resetCircularListLocked(mContext);
        mHardwareKeyboardShortcutController.reset(mSettings);

        // TODO: Instantiate mSwitchingController for each user.
        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
            mSwitchingController.resetCircularListLocked(mMethodMap);
        } else {
            mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
                    mContext, mMethodMap, mSettings.getCurrentUserId());
        }

        mHardwareKeyboardShortcutController.reset(mSettings);
        sendOnNavButtonFlagsChangedLocked();
    }

@@ -4583,6 +4589,9 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                }
                return;
            }
            if (mSettings.getCurrentUserId() != mSwitchingController.getUserId()) {
                return;
            }
            final InputMethodInfo imi = mMethodMap.get(getSelectedMethodIdLocked());
            if (imi != null) {
                mSwitchingController.onUserActionLocked(imi, mCurrentSubtype);
@@ -4820,9 +4829,10 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub
                    int lastInputMethodSubtypeId =
                            mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);

                    final List<ImeSubtypeListItem> imList = mSwitchingController
                            .getSortedInputMethodAndSubtypeListForImeMenuLocked(
                                    showAuxSubtypes, isScreenLocked);
                    final List<ImeSubtypeListItem> imList = InputMethodSubtypeSwitchingController
                            .getSortedInputMethodAndSubtypeList(
                                    showAuxSubtypes, isScreenLocked, false, mContext,
                                    mMethodMap, mSettings.getCurrentUserId());
                    mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
                            lastInputMethodId, lastInputMethodSubtypeId, imList);
                }
@@ -5207,11 +5217,13 @@ public final class InputMethodManagerService extends IInputMethodManager.Stub

        updateDefaultVoiceImeIfNeededLocked();

        // Here is not the perfect place to reset the switching controller. Ideally
        // mSwitchingController and mSettings should be able to share the same state.
        // TODO: Make sure that mSwitchingController and mSettings are sharing the
        // the same enabled IMEs list.
        mSwitchingController.resetCircularListLocked(mContext);
        // TODO: Instantiate mSwitchingController for each user.
        if (mSettings.getCurrentUserId() == mSwitchingController.getUserId()) {
            mSwitchingController.resetCircularListLocked(mMethodMap);
        } else {
            mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
                    mContext, mMethodMap, mSettings.getCurrentUserId());
        }
        mHardwareKeyboardShortcutController.reset(mSettings);

        sendOnNavButtonFlagsChangedLocked();
+87 −84
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.server.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.UserHandle;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Printer;
import android.util.Slog;
@@ -32,7 +36,6 @@ import com.android.server.inputmethod.InputMethodUtils.InputMethodSettings;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Objects;

/**
@@ -154,24 +157,17 @@ final class InputMethodSubtypeSwitchingController {
        }
    }

    private static class InputMethodAndSubtypeList {
        private final Context mContext;
        // Used to load label
        private final PackageManager mPm;
        private final String mSystemLocaleStr;
        private final InputMethodSettings mSettings;

        InputMethodAndSubtypeList(Context context, InputMethodSettings settings) {
            mContext = context;
            mSettings = settings;
            mPm = context.getPackageManager();
            final Locale locale = context.getResources().getConfiguration().locale;
            mSystemLocaleStr = locale != null ? locale.toString() : "";
        }
    static List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
            boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu,
            @NonNull Context context, @NonNull ArrayMap<String, InputMethodInfo> methodMap,
            @UserIdInt int userId) {
        final Context userAwareContext = context.getUserId() == userId
                ? context
                : context.createContextAsUser(UserHandle.of(userId), 0 /* flags */);
        final String mSystemLocaleStr = SystemLocaleWrapper.get(userId).get(0).toLanguageTag();
        final InputMethodSettings settings = new InputMethodSettings(methodMap, userId);

        public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeList(
                boolean includeAuxiliarySubtypes, boolean isScreenLocked, boolean forImeMenu) {
            final ArrayList<InputMethodInfo> imis = mSettings.getEnabledInputMethodListLocked();
        final ArrayList<InputMethodInfo> imis = settings.getEnabledInputMethodListLocked();
        if (imis.isEmpty()) {
            return Collections.emptyList();
        }
@@ -189,12 +185,12 @@ final class InputMethodSubtypeSwitchingController {
                continue;
            }
            final List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypeList =
                        mSettings.getEnabledInputMethodSubtypeListLocked(imi, true);
                    settings.getEnabledInputMethodSubtypeListLocked(imi, true);
            final ArraySet<String> enabledSubtypeSet = new ArraySet<>();
            for (InputMethodSubtype subtype : explicitlyOrImplicitlyEnabledSubtypeList) {
                enabledSubtypeSet.add(String.valueOf(subtype.hashCode()));
            }
                final CharSequence imeLabel = imi.loadLabel(mPm);
            final CharSequence imeLabel = imi.loadLabel(userAwareContext.getPackageManager());
            if (enabledSubtypeSet.size() > 0) {
                final int subtypeCount = imi.getSubtypeCount();
                if (DEBUG) {
@@ -208,7 +204,7 @@ final class InputMethodSubtypeSwitchingController {
                            && (includeAuxiliarySubtypes || !subtype.isAuxiliary())) {
                        final CharSequence subtypeLabel =
                                subtype.overridesImplicitlyEnabledSubtype() ? null : subtype
                                            .getDisplayName(mContext, imi.getPackageName(),
                                        .getDisplayName(userAwareContext, imi.getPackageName(),
                                                imi.getServiceInfo().applicationInfo);
                        imList.add(new ImeSubtypeListItem(imeLabel,
                                subtypeLabel, imi, j, subtype.getLocale(), mSystemLocaleStr));
@@ -227,7 +223,6 @@ final class InputMethodSubtypeSwitchingController {
        Collections.sort(imList);
        return imList;
    }
    }

    private static int calculateSubtypeId(InputMethodInfo imi, InputMethodSubtype subtype) {
        return subtype != null ? SubtypeUtils.getSubtypeIdFromHashCode(imi, subtype.hashCode())
@@ -479,18 +474,32 @@ final class InputMethodSubtypeSwitchingController {
        }
    }

    private final InputMethodSettings mSettings;
    private InputMethodAndSubtypeList mSubtypeList;
    private final Context mContext;
    @UserIdInt
    private final int mUserId;
    private ControllerImpl mController;

    private InputMethodSubtypeSwitchingController(InputMethodSettings settings, Context context) {
        mSettings = settings;
        resetCircularListLocked(context);
    private InputMethodSubtypeSwitchingController(@NonNull Context context,
            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
        mContext = context;
        mUserId = userId;
        mController = ControllerImpl.createFrom(null,
                getSortedInputMethodAndSubtypeList(
                        false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
                        false /* forImeMenu */, context, methodMap, userId));
    }

    @NonNull
    public static InputMethodSubtypeSwitchingController createInstanceLocked(
            InputMethodSettings settings, Context context) {
        return new InputMethodSubtypeSwitchingController(settings, context);
            @NonNull Context context,
            @NonNull ArrayMap<String, InputMethodInfo> methodMap, @UserIdInt int userId) {
        return new InputMethodSubtypeSwitchingController(context, methodMap, userId);
    }

    @AnyThread
    @UserIdInt
    int getUserId() {
        return mUserId;
    }

    public void onUserActionLocked(InputMethodInfo imi, InputMethodSubtype subtype) {
@@ -503,12 +512,12 @@ final class InputMethodSubtypeSwitchingController {
        mController.onUserActionLocked(imi, subtype);
    }

    public void resetCircularListLocked(Context context) {
        mSubtypeList = new InputMethodAndSubtypeList(context, mSettings);
    public void resetCircularListLocked(
            @NonNull ArrayMap<String, InputMethodInfo> methodMap) {
        mController = ControllerImpl.createFrom(mController,
                mSubtypeList.getSortedInputMethodAndSubtypeList(
                getSortedInputMethodAndSubtypeList(
                        false /* includeAuxiliarySubtypes */, false /* isScreenLocked */,
                        false /* forImeMenu */));
                        false /* forImeMenu */, mContext, methodMap, mUserId));
    }

    public ImeSubtypeListItem getNextInputMethodLocked(boolean onlyCurrentIme, InputMethodInfo imi,
@@ -522,12 +531,6 @@ final class InputMethodSubtypeSwitchingController {
        return mController.getNextInputMethod(onlyCurrentIme, imi, subtype);
    }

    public List<ImeSubtypeListItem> getSortedInputMethodAndSubtypeListForImeMenuLocked(
            boolean includingAuxiliarySubtypes, boolean isScreenLocked) {
        return mSubtypeList.getSortedInputMethodAndSubtypeList(
                includingAuxiliarySubtypes, isScreenLocked, true /* forImeMenu */);
    }

    public void dump(final Printer pw) {
        if (mController != null) {
            mController.dump(pw);