From f71db730256ed2e5999dd434fd78449a546c51d1 Mon Sep 17 00:00:00 2001 From: Raff Tsai Date: Mon, 2 Dec 2019 10:55:29 +0800 Subject: [PATCH] Implement work-profile infra in BasePreferenceController - Add settings:forWork in xml - Set mIsForWork based on xml attribute - Delete WorkProfilePreferenceController and move its function to BasePreferenceController Fixes: 123376083 Test: Add work profile, go to Settings->System->Language&input, UI should show work profile related items Change-Id: Id2dcbb0e158c117cdfd363466a275f4e133c345e --- res/values/attrs.xml | 8 +- res/xml/language_and_input.xml | 3 + res/xml/location_settings.xml | 2 + .../core/BasePreferenceController.java | 94 +++++++++++++++++- .../core/PreferenceControllerListHelper.java | 6 +- .../core/PreferenceXmlParserUtils.java | 17 +++- .../core/WorkProfilePreferenceController.java | 97 ------------------- .../settings/dashboard/DashboardFragment.java | 2 + ...ellCheckerForWorkPreferenceController.java | 12 +-- ...alKeyboardForWorkPreferenceController.java | 12 +-- ...DictionaryForWorkPreferenceController.java | 11 +-- .../LocationForWorkPreferenceController.java | 4 +- ...cationForWorkPreferenceControllerTest.java | 13 --- 13 files changed, 132 insertions(+), 149 deletions(-) delete mode 100644 src/com/android/settings/core/WorkProfilePreferenceController.java diff --git a/res/values/attrs.xml b/res/values/attrs.xml index 42167b74789..6514304ec77 100644 --- a/res/values/attrs.xml +++ b/res/values/attrs.xml @@ -68,8 +68,10 @@ - + + + @@ -112,10 +114,6 @@ - - - - diff --git a/res/xml/language_and_input.xml b/res/xml/language_and_input.xml index e3690a9c077..5880076bdec 100644 --- a/res/xml/language_and_input.xml +++ b/res/xml/language_and_input.xml @@ -101,18 +101,21 @@ android:key="virtual_keyboards_for_work_pref" android:title="@string/virtual_keyboards_for_work_title" android:fragment="com.android.settings.inputmethod.VirtualKeyboardFragment" + settings:forWork="true" settings:controller="com.android.settings.inputmethod.VirtualKeyboardForWorkPreferenceController" /> diff --git a/res/xml/location_settings.xml b/res/xml/location_settings.xml index 932cb658f8d..fb03f4c7f3d 100644 --- a/res/xml/location_settings.xml +++ b/res/xml/location_settings.xml @@ -62,6 +62,7 @@ android:selectable="true" android:title="@string/managed_profile_location_switch_title" settings:controller="com.android.settings.location.LocationForWorkPreferenceController" + settings:forWork="true" settings:useAdminDisabledSummary="true"/> diff --git a/src/com/android/settings/core/BasePreferenceController.java b/src/com/android/settings/core/BasePreferenceController.java index eeb77ad131e..7215bb79159 100644 --- a/src/com/android/settings/core/BasePreferenceController.java +++ b/src/com/android/settings/core/BasePreferenceController.java @@ -13,17 +13,24 @@ */ package com.android.settings.core; +import static com.android.settings.dashboard.DashboardFragment.CATEGORY; + import android.annotation.IntDef; +import android.app.settings.SettingsEnums; import android.content.ContentResolver; import android.content.Context; import android.net.Uri; +import android.os.UserHandle; +import android.os.UserManager; import android.provider.SettingsSlicesContract; import android.text.TextUtils; import android.util.Log; +import androidx.annotation.Nullable; import androidx.preference.Preference; import androidx.preference.PreferenceScreen; +import com.android.settings.Utils; import com.android.settings.slices.SettingsSliceProvider; import com.android.settings.slices.SliceData; import com.android.settings.slices.Sliceable; @@ -109,9 +116,11 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public static final int DISABLED_DEPENDENT_SETTING = 5; - protected final String mPreferenceKey; protected UiBlockListener mUiBlockListener; + private boolean mIsForWork; + @Nullable + private UserHandle mWorkProfileUser; /** * Instantiate a controller as specified controller type and user-defined key. @@ -151,6 +160,34 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl } } + /** + * Instantiate a controller as specified controller type and work profile + *

+ * This is done through reflection. Do not use this method unless you know what you are doing. + * + * @param context application context + * @param controllerName class name of the {@link BasePreferenceController} + * @param key attribute android:key of the {@link Preference} + * @param isWorkProfile is this controller only for work profile user? + */ + public static BasePreferenceController createInstance(Context context, String controllerName, + String key, boolean isWorkProfile) { + try { + final Class clazz = Class.forName(controllerName); + final Constructor preferenceConstructor = + clazz.getConstructor(Context.class, String.class); + final Object[] params = new Object[]{context, key}; + final BasePreferenceController controller = + (BasePreferenceController) preferenceConstructor.newInstance(params); + controller.setForWork(isWorkProfile); + return controller; + } catch (ClassNotFoundException | NoSuchMethodException | InstantiationException + | IllegalArgumentException | InvocationTargetException | IllegalAccessException e) { + throw new IllegalStateException( + "Invalid preference controller: " + controllerName, e); + } + } + public BasePreferenceController(Context context, String preferenceKey) { super(context); mPreferenceKey = preferenceKey; @@ -166,6 +203,9 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl *

* The status is used for the convenience methods: {@link #isAvailable()}, * {@link #isSupported()} + *

+ * The inherited class doesn't need to check work profile is existed or not if + * android:forWork="true" is set in preference xml. */ @AvailabilityStatus public abstract int getAvailabilityStatus(); @@ -198,12 +238,18 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl * {@link #DISABLED_DEPENDENT_SETTING}, then the setting will be disabled by default in the * DashboardFragment, and it is up to the {@link BasePreferenceController} to enable the * preference at the right time. - * + *

+ * This function also check if work profile is existed when android:forWork="true" is set for + * the controller in preference xml. * TODO (mfritze) Build a dependency mechanism to allow a controller to easily define the * dependent setting. */ @Override public final boolean isAvailable() { + if (mIsForWork && mWorkProfileUser == null) { + return false; + } + final int availabilityStatus = getAvailabilityStatus(); return (availabilityStatus == AVAILABLE || availabilityStatus == AVAILABLE_UNSEARCHABLE @@ -266,6 +312,41 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl } } + /** + * Indicates this controller is only for work profile user + */ + void setForWork(boolean forWork) { + mIsForWork = forWork; + if (mIsForWork) { + mWorkProfileUser = Utils.getManagedProfile(UserManager.get(mContext)); + } + } + + /** + * Launches the specified fragment for the work profile user if the associated + * {@link Preference} is clicked. Otherwise just forward it to the super class. + * + * @param preference the preference being clicked. + * @return {@code true} if handled. + */ + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return super.handlePreferenceTreeClick(preference); + } + if (!mIsForWork || mWorkProfileUser == null) { + return super.handlePreferenceTreeClick(preference); + } + new SubSettingLauncher(preference.getContext()) + .setDestination(preference.getFragment()) + .setSourceMetricsCategory(preference.getExtras().getInt(CATEGORY, + SettingsEnums.PAGE_UNKNOWN)) + .setArguments(preference.getExtras()) + .setUserHandle(mWorkProfileUser) + .launch(); + return true; + } + /** * Updates raw data for search provider. * @@ -316,4 +397,13 @@ public abstract class BasePreferenceController extends AbstractPreferenceControl */ public interface UiBlocker { } + + /** + * @return Non-{@code null} {@link UserHandle} when a work profile is enabled. + * Otherwise {@code null}. + */ + @Nullable + protected UserHandle getWorkProfileUser() { + return mWorkProfileUser; + } } diff --git a/src/com/android/settings/core/PreferenceControllerListHelper.java b/src/com/android/settings/core/PreferenceControllerListHelper.java index 5879ba443b3..6d450fe137f 100644 --- a/src/com/android/settings/core/PreferenceControllerListHelper.java +++ b/src/com/android/settings/core/PreferenceControllerListHelper.java @@ -17,6 +17,7 @@ package com.android.settings.core; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_CONTROLLER; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_FOR_WORK; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; import android.annotation.NonNull; @@ -55,7 +56,7 @@ public class PreferenceControllerListHelper { try { preferenceMetadata = PreferenceXmlParserUtils.extractMetadata(context, xmlResId, MetadataFlag.FLAG_NEED_KEY | MetadataFlag.FLAG_NEED_PREF_CONTROLLER - | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN); + | MetadataFlag.FLAG_INCLUDE_PREF_SCREEN | MetadataFlag.FLAG_FOR_WORK); } catch (IOException | XmlPullParserException e) { Log.e(TAG, "Failed to parse preference xml for getting controllers", e); return controllers; @@ -72,6 +73,7 @@ public class PreferenceControllerListHelper { } catch (IllegalStateException e) { Log.d(TAG, "Could not find Context-only controller for pref: " + controllerName); final String key = metadata.getString(METADATA_KEY); + final boolean isWorkProfile = metadata.getBoolean(METADATA_FOR_WORK, false); if (TextUtils.isEmpty(key)) { Log.w(TAG, "Controller requires key but it's not defined in xml: " + controllerName); @@ -79,7 +81,7 @@ public class PreferenceControllerListHelper { } try { controller = BasePreferenceController.createInstance(context, controllerName, - key); + key, isWorkProfile); } catch (IllegalStateException e2) { Log.w(TAG, "Cannot instantiate controller from reflection: " + controllerName); continue; diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java index ede6393983f..240426d69d9 100644 --- a/src/com/android/settings/core/PreferenceXmlParserUtils.java +++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java @@ -72,7 +72,8 @@ public class PreferenceXmlParserUtils { MetadataFlag.FLAG_NEED_PREF_SUMMARY, MetadataFlag.FLAG_NEED_PREF_ICON, MetadataFlag.FLAG_NEED_SEARCHABLE, - MetadataFlag.FLAG_UNAVAILABLE_SLICE_SUBTITLE}) + MetadataFlag.FLAG_UNAVAILABLE_SLICE_SUBTITLE, + MetadataFlag.FLAG_FOR_WORK}) @Retention(RetentionPolicy.SOURCE) public @interface MetadataFlag { @@ -87,6 +88,7 @@ public class PreferenceXmlParserUtils { int FLAG_NEED_SEARCHABLE = 1 << 9; int FLAG_NEED_PREF_APPEND = 1 << 10; int FLAG_UNAVAILABLE_SLICE_SUBTITLE = 1 << 11; + int FLAG_FOR_WORK = 1 << 12; } public static final String METADATA_PREF_TYPE = "type"; @@ -98,8 +100,8 @@ public class PreferenceXmlParserUtils { public static final String METADATA_KEYWORDS = "keywords"; public static final String METADATA_SEARCHABLE = "searchable"; public static final String METADATA_APPEND = "staticPreferenceLocation"; - public static final String METADATA_UNAVAILABLE_SLICE_SUBTITLE = - "unavailable_slice_subtitle"; + public static final String METADATA_UNAVAILABLE_SLICE_SUBTITLE = "unavailable_slice_subtitle"; + public static final String METADATA_FOR_WORK = "for_work"; private static final String ENTRIES_SEPARATOR = "|"; @@ -244,6 +246,10 @@ public class PreferenceXmlParserUtils { preferenceMetadata.putString(METADATA_UNAVAILABLE_SLICE_SUBTITLE, getUnavailableSliceSubtitle(preferenceAttributes)); } + if (hasFlag(flags, MetadataFlag.FLAG_FOR_WORK)) { + preferenceMetadata.putBoolean(METADATA_FOR_WORK, + isForWork(preferenceAttributes)); + } metadata.add(preferenceMetadata); preferenceAttributes.recycle(); @@ -329,4 +335,9 @@ public class PreferenceXmlParserUtils { return styledAttributes.getString( R.styleable.Preference_unavailableSliceSubtitle); } + + private static boolean isForWork(TypedArray styledAttributes) { + return styledAttributes.getBoolean( + R.styleable.Preference_forWork, false); + } } \ No newline at end of file diff --git a/src/com/android/settings/core/WorkProfilePreferenceController.java b/src/com/android/settings/core/WorkProfilePreferenceController.java deleted file mode 100644 index 603af20a870..00000000000 --- a/src/com/android/settings/core/WorkProfilePreferenceController.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2019 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file - * except in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the specific language governing - * permissions and limitations under the License. - */ - -package com.android.settings.core; - -import android.content.Context; -import android.os.UserHandle; -import android.os.UserManager; -import android.text.TextUtils; - -import androidx.annotation.Nullable; -import androidx.preference.Preference; - -import com.android.settings.Utils; - -/** - * Abstract class to provide additional logic to deal with optional {@link Preference} entries that - * are used only when work profile is enabled. - * - *

TODO(b/123376083): Consider merging this into {@link BasePreferenceController}.

- */ -public abstract class WorkProfilePreferenceController extends BasePreferenceController { - @Nullable - private final UserHandle mWorkProfileUser; - - /** - * Constructor of {@link WorkProfilePreferenceController}. Called by - * {@link BasePreferenceController#createInstance(Context, String)} through reflection. - * - * @param context {@link Context} to instantiate this controller. - * @param preferenceKey Preference key to be associated with the {@link Preference}. - */ - public WorkProfilePreferenceController(Context context, String preferenceKey) { - super(context, preferenceKey); - mWorkProfileUser = Utils.getManagedProfile(UserManager.get(context)); - } - - /** - * @return Non-{@code null} {@link UserHandle} when a work profile is enabled. - * Otherwise {@code null}. - */ - @Nullable - protected UserHandle getWorkProfileUser() { - return mWorkProfileUser; - } - - /** - * Called back from {@link #handlePreferenceTreeClick(Preference)} to associate source metrics - * category. - * - * @return One of {@link android.app.settings.SettingsEnums}. - */ - protected abstract int getSourceMetricsCategory(); - - /** - * {@inheritDoc} - * - *

When you override this method, do not forget to check {@link #getWorkProfileUser()} to - * see if work profile user actually exists or not.

- */ - @AvailabilityStatus - @Override - public int getAvailabilityStatus() { - return mWorkProfileUser != null ? AVAILABLE : DISABLED_FOR_USER; - } - - /** - * Launches the specified fragment for the work profile user if the associated - * {@link Preference} is clicked. Otherwise just forward it to the super class. - * - * @param preference the preference being clicked. - * @return {@code true} if handled. - */ - public boolean handlePreferenceTreeClick(Preference preference) { - if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { - return super.handlePreferenceTreeClick(preference); - } - new SubSettingLauncher(preference.getContext()) - .setDestination(preference.getFragment()) - .setSourceMetricsCategory(getSourceMetricsCategory()) - .setArguments(preference.getExtras()) - .setUserHandle(mWorkProfileUser) - .launch(); - return true; - } -} diff --git a/src/com/android/settings/dashboard/DashboardFragment.java b/src/com/android/settings/dashboard/DashboardFragment.java index 92c23b9c61f..a8c4760e857 100644 --- a/src/com/android/settings/dashboard/DashboardFragment.java +++ b/src/com/android/settings/dashboard/DashboardFragment.java @@ -65,6 +65,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment implements SettingsBaseActivity.CategoryListener, Indexable, PreferenceGroup.OnExpandButtonClickListener, BasePreferenceController.UiBlockListener { + public static final String CATEGORY = "category"; private static final String TAG = "DashboardFragment"; @VisibleForTesting @@ -206,6 +207,7 @@ public abstract class DashboardFragment extends SettingsPreferenceFragment mMetricsFeatureProvider.logDashboardStartIntent( getContext(), preference.getIntent(), getMetricsCategory()); // Give all controllers a chance to handle click. + preference.getExtras().putInt(CATEGORY, getMetricsCategory()); for (List controllerList : controllers) { for (AbstractPreferenceController controller : controllerList) { if (controller.handlePreferenceTreeClick(preference)) { diff --git a/src/com/android/settings/inputmethod/SpellCheckerForWorkPreferenceController.java b/src/com/android/settings/inputmethod/SpellCheckerForWorkPreferenceController.java index 7988a649852..5e04a06b32b 100644 --- a/src/com/android/settings/inputmethod/SpellCheckerForWorkPreferenceController.java +++ b/src/com/android/settings/inputmethod/SpellCheckerForWorkPreferenceController.java @@ -16,34 +16,28 @@ package com.android.settings.inputmethod; -import android.app.settings.SettingsEnums; import android.content.Context; import com.android.settings.R; -import com.android.settings.core.WorkProfilePreferenceController; +import com.android.settings.core.BasePreferenceController; /** * Preference controller for "Spell checker for work". * * @see SpellCheckerPreferenceController */ -public final class SpellCheckerForWorkPreferenceController extends WorkProfilePreferenceController { +public final class SpellCheckerForWorkPreferenceController extends BasePreferenceController { public SpellCheckerForWorkPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } - @Override - protected int getSourceMetricsCategory() { - return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY; - } - @AvailabilityStatus @Override public int getAvailabilityStatus() { if (!mContext.getResources().getBoolean(R.bool.config_show_spellcheckers_settings)) { return UNSUPPORTED_ON_DEVICE; } - return super.getAvailabilityStatus(); + return AVAILABLE; } } diff --git a/src/com/android/settings/inputmethod/VirtualKeyboardForWorkPreferenceController.java b/src/com/android/settings/inputmethod/VirtualKeyboardForWorkPreferenceController.java index fe81a945d1b..9aa65f0b8ad 100644 --- a/src/com/android/settings/inputmethod/VirtualKeyboardForWorkPreferenceController.java +++ b/src/com/android/settings/inputmethod/VirtualKeyboardForWorkPreferenceController.java @@ -16,31 +16,25 @@ package com.android.settings.inputmethod; -import android.app.settings.SettingsEnums; import android.content.Context; import com.android.settings.R; -import com.android.settings.core.WorkProfilePreferenceController; +import com.android.settings.core.BasePreferenceController; public final class VirtualKeyboardForWorkPreferenceController - extends WorkProfilePreferenceController { + extends BasePreferenceController { public VirtualKeyboardForWorkPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } - @Override - protected int getSourceMetricsCategory() { - return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY; - } - @AvailabilityStatus @Override public int getAvailabilityStatus() { if (!mContext.getResources().getBoolean(R.bool.config_show_virtual_keyboard_pref)) { return UNSUPPORTED_ON_DEVICE; } - return super.getAvailabilityStatus(); + return AVAILABLE; } } diff --git a/src/com/android/settings/language/UserDictionaryForWorkPreferenceController.java b/src/com/android/settings/language/UserDictionaryForWorkPreferenceController.java index 5c1307248d4..99d1c2f35fe 100644 --- a/src/com/android/settings/language/UserDictionaryForWorkPreferenceController.java +++ b/src/com/android/settings/language/UserDictionaryForWorkPreferenceController.java @@ -16,11 +16,9 @@ package com.android.settings.language; -import android.app.settings.SettingsEnums; import android.content.Context; -import com.android.settings.R; -import com.android.settings.core.WorkProfilePreferenceController; +import com.android.settings.core.BasePreferenceController; /** * Preference controller for "UserDictionary for work". @@ -28,14 +26,15 @@ import com.android.settings.core.WorkProfilePreferenceController; * @see UserDictionaryPreferenceController */ public final class UserDictionaryForWorkPreferenceController - extends WorkProfilePreferenceController { + extends BasePreferenceController { public UserDictionaryForWorkPreferenceController(Context context, String preferenceKey) { super(context, preferenceKey); } + @AvailabilityStatus @Override - protected int getSourceMetricsCategory() { - return SettingsEnums.SETTINGS_LANGUAGE_CATEGORY; + public int getAvailabilityStatus() { + return AVAILABLE; } } diff --git a/src/com/android/settings/location/LocationForWorkPreferenceController.java b/src/com/android/settings/location/LocationForWorkPreferenceController.java index 342efff84f8..81639346db4 100644 --- a/src/com/android/settings/location/LocationForWorkPreferenceController.java +++ b/src/com/android/settings/location/LocationForWorkPreferenceController.java @@ -56,9 +56,7 @@ public class LocationForWorkPreferenceController extends LocationBasePreferenceC @Override public int getAvailabilityStatus() { - // Looking for a managed profile. If there are no managed profiles then we are removing the - // managed profile category. - return Utils.getManagedProfile(mUserManager) != null ? AVAILABLE : UNSUPPORTED_ON_DEVICE; + return AVAILABLE; } @Override diff --git a/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java index 5db5ed521ab..bd04b124093 100644 --- a/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/location/LocationForWorkPreferenceControllerTest.java @@ -15,8 +15,6 @@ */ package com.android.settings.location; -import static com.google.common.truth.Truth.assertThat; - import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.Mockito.doReturn; @@ -114,17 +112,6 @@ public class LocationForWorkPreferenceControllerTest { verify(mPreference).setSummary(R.string.switch_off_text); } - @Test - public void isAvailable_noManagedProfile_shouldReturnFalse() { - when(mUserManager.getUserProfiles()).thenReturn(new ArrayList<>()); - assertThat(mController.isAvailable()).isFalse(); - } - - @Test - public void isAvailable_hasManagedProfile_shouldReturnTrue() { - assertThat(mController.isAvailable()).isTrue(); - } - @Test public void onLocationModeChanged_disabledByAdmin_shouldDisablePreference() { mController.displayPreference(mScreen);