Loading packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java +41 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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 Loading @@ -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) { Loading packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +27 −19 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading Loading @@ -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); Loading packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java +33 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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() { Loading packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -112,7 +113,8 @@ public class InputMethodPreferenceTest { createInputMethodInfo(systemIme, name), title, true /* isAllowedByOrganization */, p -> {} /* onSavePreferenceListener */); p -> {} /* onSavePreferenceListener */, UserHandle.myUserId()); } private static InputMethodInfo createInputMethodInfo( Loading Loading
packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java +41 −4 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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); Loading @@ -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; } Loading @@ -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 Loading @@ -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) { Loading
packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodPreference.java +27 −19 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -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; Loading @@ -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); Loading @@ -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); Loading Loading @@ -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); Loading
packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java +33 −9 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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 Loading @@ -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() { Loading
packages/SettingsLib/tests/integ/src/com/android/settingslib/inputmethod/InputMethodPreferenceTest.java +3 −1 Original line number Diff line number Diff line Loading @@ -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; Loading Loading @@ -112,7 +113,8 @@ public class InputMethodPreferenceTest { createInputMethodInfo(systemIme, name), title, true /* isAllowedByOrganization */, p -> {} /* onSavePreferenceListener */); p -> {} /* onSavePreferenceListener */, UserHandle.myUserId()); } private static InputMethodInfo createInputMethodInfo( Loading