Merge "Make InputMethodPreference support work profile"

This commit is contained in:
Wilson Wu
2021-12-03 04:05:18 +00:00
committed by Android (Google) Code Review
4 changed files with 104 additions and 33 deletions

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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() {

View File

@@ -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(