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

Commit 70ff1255 authored by Wilson Wu's avatar Wilson Wu
Browse files

Make InputMethodPreference support work profile

Introduces userId into InputMethodPreference and
per-user context into InputMethodSettingValuesWrapper.

So we can handle the work input method if it's work flow.

Bug: 174360557
Bug: 197707782
Test: Manual test as bug video
Test: atest SettingsLibTests:com.android.settingslib.inputmethod.InputMethodPreferenceTest
Change-Id: Ic233cf183c8783937e638f9ab672c248a136c071
parent 1f3a923a
Loading
Loading
Loading
Loading
+41 −4
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ package com.android.settingslib.inputmethod;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.ContentResolver;
import android.content.Context;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.icu.text.ListFormatter;
import android.os.UserHandle;
import android.provider.Settings;
import android.provider.Settings.SettingNotFoundException;
import android.text.TextUtils;
@@ -155,9 +157,41 @@ public class InputMethodAndSubtypeUtilCompat {
        return set;
    }

    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat context,
    /**
     * Save the enabled/disabled input methods and selected subtype states into system settings.
     *
     * @param fragment The preference fragment user interact with.
     * @param resolver The {@link ContentResolver} used to access the database.
     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
     */
    public static void saveInputMethodSubtypeList(PreferenceFragmentCompat fragment,
            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
            boolean hasHardKeyboard) {
        saveInputMethodSubtypeListForUserInternal(
                fragment, resolver, inputMethodInfos, hasHardKeyboard, UserHandle.myUserId());
    }

    /**
     * Save the enabled/disabled input methods and selected subtype states into system settings as
     * given userId.
     *
     * @param fragment The preference fragment user interact with.
     * @param resolver The {@link ContentResolver} used to access the database.
     * @param inputMethodInfos The list of {@link InputMethodInfo} to be checked.
     * @param hasHardKeyboard {@code true} if the device has the hardware keyboard.
     * @param userId The given userId
     */
    public static void saveInputMethodSubtypeListForUser(PreferenceFragmentCompat fragment,
            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
            boolean hasHardKeyboard, @UserIdInt int userId) {
        saveInputMethodSubtypeListForUserInternal(
                fragment, resolver, inputMethodInfos, hasHardKeyboard, userId);
    }

    private static void saveInputMethodSubtypeListForUserInternal(PreferenceFragmentCompat fragment,
            ContentResolver resolver, List<InputMethodInfo> inputMethodInfos,
            boolean hasHardKeyboard, @UserIdInt int userId) {
        String currentInputMethodId = Settings.Secure.getString(resolver,
                Settings.Secure.DEFAULT_INPUT_METHOD);
        final int selectedInputMethodSubtype = getInputMethodSubtypeSelected(resolver);
@@ -168,7 +202,7 @@ public class InputMethodAndSubtypeUtilCompat {
        boolean needsToResetSelectedSubtype = false;
        for (final InputMethodInfo imi : inputMethodInfos) {
            final String imiId = imi.getId();
            final Preference pref = context.findPreference(imiId);
            final Preference pref = fragment.findPreference(imiId);
            if (pref == null) {
                continue;
            }
@@ -184,8 +218,11 @@ public class InputMethodAndSubtypeUtilCompat {
            }
            final boolean isCurrentInputMethod = imiId.equals(currentInputMethodId);
            final boolean systemIme = imi.isSystem();
            // Create context as given userId
            final Context wrapperContext = userId == UserHandle.myUserId() ? fragment.getActivity()
                    : fragment.getActivity().createContextAsUser(UserHandle.of(userId), 0);
            if ((!hasHardKeyboard && InputMethodSettingValuesWrapper.getInstance(
                    context.getActivity()).isAlwaysCheckedIme(imi))
                    wrapperContext).isAlwaysCheckedIme(imi))
                    || isImeChecked) {
                if (!enabledIMEsAndSubtypesMap.containsKey(imiId)) {
                    // imiId has just been enabled
@@ -198,7 +235,7 @@ public class InputMethodAndSubtypeUtilCompat {
                for (int i = 0; i < subtypeCount; ++i) {
                    final InputMethodSubtype subtype = imi.getSubtypeAt(i);
                    final String subtypeHashCodeStr = String.valueOf(subtype.hashCode());
                    final TwoStatePreference subtypePref = (TwoStatePreference) context
                    final TwoStatePreference subtypePref = (TwoStatePreference) fragment
                            .findPreference(imiId + subtypeHashCodeStr);
                    // In the Configure input method screen which does not have subtype preferences.
                    if (subtypePref == null) {
+27 −19
Original line number Diff line number Diff line
@@ -18,6 +18,7 @@ package com.android.settingslib.inputmethod;

import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;

import android.annotation.UserIdInt;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Context;
@@ -75,30 +76,34 @@ public class InputMethodPreference extends PrimarySwitchPreference
    private final OnSavePreferenceListener mOnSaveListener;
    private final InputMethodSettingValuesWrapper mInputMethodSettingValues;
    private final boolean mIsAllowedByOrganization;
    @UserIdInt
    private final int mUserId;

    private AlertDialog mDialog = null;

    /**
     * A preference entry of an input method.
     *
     * @param context The Context this is associated with.
     * @param prefContext The Context this preference is associated with.
     * @param imi The {@link InputMethodInfo} of this preference.
     * @param isAllowedByOrganization false if the IME has been disabled by a device or profile
     *     owner.
     * @param onSaveListener The listener called when this preference has been changed and needs
     *     to save the state to shared preference.
     * @param userId The userId to specify the corresponding user for this preference.
     */
    public InputMethodPreference(final Context context, final InputMethodInfo imi,
            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener) {
        this(context, imi, imi.loadLabel(context.getPackageManager()), isAllowedByOrganization,
                onSaveListener);
    public InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
            final boolean isAllowedByOrganization, final OnSavePreferenceListener onSaveListener,
            final @UserIdInt int userId) {
        this(prefContext, imi, imi.loadLabel(prefContext.getPackageManager()),
                isAllowedByOrganization, onSaveListener, userId);
    }

    @VisibleForTesting
    InputMethodPreference(final Context context, final InputMethodInfo imi,
    InputMethodPreference(final Context prefContext, final InputMethodInfo imi,
            final CharSequence title, final boolean isAllowedByOrganization,
            final OnSavePreferenceListener onSaveListener) {
        super(context);
            final OnSavePreferenceListener onSaveListener, final @UserIdInt int userId) {
        super(prefContext);
        setPersistent(false);
        mImi = imi;
        mIsAllowedByOrganization = isAllowedByOrganization;
@@ -114,7 +119,12 @@ public class InputMethodPreference extends PrimarySwitchPreference
            intent.setClassName(imi.getPackageName(), settingsActivity);
            setIntent(intent);
        }
        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(context);
        // Handle the context by given userId because {@link InputMethodSettingValuesWrapper} is
        // per-user instance.
        final Context userAwareContext = userId == UserHandle.myUserId() ? prefContext :
                getContext().createContextAsUser(UserHandle.of(userId), 0);
        mInputMethodSettingValues = InputMethodSettingValuesWrapper.getInstance(userAwareContext);
        mUserId = userId;
        mHasPriorityInSorting = imi.isSystem()
                && InputMethodAndSubtypeUtil.isValidNonAuxAsciiCapableIme(imi);
        setOnPreferenceClickListener(this);
@@ -130,17 +140,15 @@ public class InputMethodPreference extends PrimarySwitchPreference
        super.onBindViewHolder(holder);
        final Switch switchWidget = getSwitch();
        if (switchWidget != null) {
            // Avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}.
            switchWidget.setOnClickListener(v -> {
                // no-op, avoid default behavior in {@link PrimarySwitchPreference#onBindViewHolder}
            });
            switchWidget.setOnCheckedChangeListener((buttonView, isChecked) -> {
                // Avoid the invocation after we call {@link PrimarySwitchPreference#setChecked()}
                // in {@link setCheckedInternal}
                if (isChecked != isChecked()) {
                    // Keep switch to previous state because we have to show the dialog first
                    buttonView.setChecked(!isChecked);
                    callChangeListener(isChecked());
                if (!switchWidget.isEnabled()) {
                    return;
                }
                final boolean newValue = !isChecked();
                // Keep switch to previous state because we have to show the dialog first.
                switchWidget.setChecked(isChecked());
                callChangeListener(newValue);
            });
        }
        final ImageView icon = holder.itemView.findViewById(android.R.id.icon);
@@ -187,7 +195,7 @@ public class InputMethodPreference extends PrimarySwitchPreference
            final Intent intent = getIntent();
            if (intent != null) {
                // Invoke a settings activity of an input method.
                context.startActivity(intent);
                context.startActivityAsUser(intent, UserHandle.of(mUserId));
            }
        } catch (final ActivityNotFoundException e) {
            Log.d(TAG, "IME's Settings Activity Not Found", e);
+33 −9
Original line number Diff line number Diff line
@@ -16,13 +16,18 @@

package com.android.settingslib.inputmethod;

import android.annotation.AnyThread;
import android.annotation.NonNull;
import android.annotation.UiThread;
import android.content.ContentResolver;
import android.content.Context;
import android.util.Log;
import android.util.SparseArray;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodManager;

import com.android.internal.annotations.GuardedBy;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -39,20 +44,39 @@ import java.util.List;
public class InputMethodSettingValuesWrapper {
    private static final String TAG = InputMethodSettingValuesWrapper.class.getSimpleName();

    private static volatile InputMethodSettingValuesWrapper sInstance;
    private static final Object sInstanceMapLock = new Object();
    /**
     * Manages mapping between user ID and corresponding singleton
     * {@link InputMethodSettingValuesWrapper} object.
     */
    @GuardedBy("sInstanceMapLock")
    private static SparseArray<InputMethodSettingValuesWrapper> sInstanceMap = new SparseArray<>();
    private final ArrayList<InputMethodInfo> mMethodList = new ArrayList<>();
    private final ContentResolver mContentResolver;
    private final InputMethodManager mImm;

    public static InputMethodSettingValuesWrapper getInstance(Context context) {
        if (sInstance == null) {
            synchronized (TAG) {
                if (sInstance == null) {
                    sInstance = new InputMethodSettingValuesWrapper(context);
    @AnyThread
    @NonNull
    public static InputMethodSettingValuesWrapper getInstance(@NonNull Context context) {
        final int requestUserId = context.getUserId();
        InputMethodSettingValuesWrapper valuesWrapper;
        // First time to create the wrapper.
        synchronized (sInstanceMapLock) {
            if (sInstanceMap.size() == 0) {
                valuesWrapper = new InputMethodSettingValuesWrapper(context);
                sInstanceMap.put(requestUserId, valuesWrapper);
                return valuesWrapper;
            }
            // We have same user context as request.
            if (sInstanceMap.indexOfKey(requestUserId) >= 0) {
                return sInstanceMap.get(requestUserId);
            }
            // Request by a new user context.
            valuesWrapper = new InputMethodSettingValuesWrapper(context);
            sInstanceMap.put(context.getUserId(), valuesWrapper);
        }
            }
        }
        return sInstance;

        return valuesWrapper;
    }

    // Ensure singleton
@@ -64,7 +88,7 @@ public class InputMethodSettingValuesWrapper {

    public void refreshAllInputMethodAndSubtypes() {
        mMethodList.clear();
        mMethodList.addAll(mImm.getInputMethodList());
        mMethodList.addAll(mImm.getInputMethodListAsUser(mContentResolver.getUserId()));
    }

    public List<InputMethodInfo> getInputMethodList() {
+3 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.os.UserHandle;
import android.view.inputmethod.InputMethodInfo;
import android.view.inputmethod.InputMethodSubtype;

@@ -112,7 +113,8 @@ public class InputMethodPreferenceTest {
                createInputMethodInfo(systemIme, name),
                title,
                true /* isAllowedByOrganization */,
                p -> {} /* onSavePreferenceListener */);
                p -> {} /* onSavePreferenceListener */,
                UserHandle.myUserId());
    }

    private static InputMethodInfo createInputMethodInfo(