diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml index c3e0eef87b1..57e324941c2 100644 --- a/res/xml/sound_settings.xml +++ b/res/xml/sound_settings.xml @@ -36,6 +36,7 @@ android:icon="@drawable/ic_media_stream" android:title="@string/media_volume_option_title" android:order="-180" + settings:userRestriction="no_adjust_volume" settings:controller="com.android.settings.notification.MediaVolumePreferenceController"/> @@ -53,6 +54,7 @@ android:icon="@drawable/ic_local_phone_24_lib" android:title="@string/call_volume_option_title" android:order="-170" + settings:userRestriction="no_adjust_volume" settings:controller="com.android.settings.notification.CallVolumePreferenceController"/> @@ -70,6 +72,7 @@ android:icon="@drawable/ic_ring_volume" android:title="@string/separate_ring_volume_option_title" android:order="-155" + settings:userRestriction="no_adjust_volume" settings:controller="com.android.settings.notification.SeparateRingVolumePreferenceController"/> @@ -78,6 +81,7 @@ android:icon="@drawable/ic_notifications" android:title="@string/notification_volume_option_title" android:order="-150" + settings:userRestriction="no_adjust_volume" settings:controller="com.android.settings.notification.NotificationVolumePreferenceController" settings:unavailableSliceSubtitle="@string/notification_volume_disabled_summary"/> @@ -87,6 +91,7 @@ android:icon="@*android:drawable/ic_audio_alarm" android:title="@string/alarm_volume_option_title" android:order="-140" + settings:userRestriction="no_adjust_volume" settings:controller="com.android.settings.notification.AlarmVolumePreferenceController"/> diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java index a1a8d6731a7..37d91443912 100644 --- a/src/com/android/settings/core/PreferenceXmlParserUtils.java +++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java @@ -74,7 +74,8 @@ public class PreferenceXmlParserUtils { MetadataFlag.FLAG_NEED_SEARCHABLE, MetadataFlag.FLAG_UNAVAILABLE_SLICE_SUBTITLE, MetadataFlag.FLAG_FOR_WORK, - MetadataFlag.FLAG_NEED_HIGHLIGHTABLE_MENU_KEY}) + MetadataFlag.FLAG_NEED_HIGHLIGHTABLE_MENU_KEY, + MetadataFlag.FLAG_NEED_USER_RESTRICTION}) @Retention(RetentionPolicy.SOURCE) public @interface MetadataFlag { @@ -91,6 +92,7 @@ public class PreferenceXmlParserUtils { int FLAG_UNAVAILABLE_SLICE_SUBTITLE = 1 << 11; int FLAG_FOR_WORK = 1 << 12; int FLAG_NEED_HIGHLIGHTABLE_MENU_KEY = 1 << 13; + int FLAG_NEED_USER_RESTRICTION = 1 << 14; } public static final String METADATA_PREF_TYPE = "type"; @@ -105,6 +107,7 @@ public class PreferenceXmlParserUtils { public static final String METADATA_UNAVAILABLE_SLICE_SUBTITLE = "unavailable_slice_subtitle"; public static final String METADATA_FOR_WORK = "for_work"; public static final String METADATA_HIGHLIGHTABLE_MENU_KEY = "highlightable_menu_key"; + public static final String METADATA_USER_RESTRICTION = "userRestriction"; private static final String ENTRIES_SEPARATOR = "|"; @@ -257,9 +260,16 @@ public class PreferenceXmlParserUtils { preferenceMetadata.putString(METADATA_HIGHLIGHTABLE_MENU_KEY, getHighlightableMenuKey(preferenceAttributes)); } + if (hasFlag(flags, MetadataFlag.FLAG_NEED_USER_RESTRICTION)) { + preferenceMetadata.putString(METADATA_USER_RESTRICTION, + getUserRestriction(context, attrs)); + } metadata.add(preferenceMetadata); preferenceAttributes.recycle(); + if (preferenceScreenAttributes != null) { + preferenceScreenAttributes.recycle(); + } } while ((type = parser.next()) != XmlPullParser.END_DOCUMENT && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)); parser.close(); @@ -351,4 +361,13 @@ public class PreferenceXmlParserUtils { return styledAttributes.getBoolean( R.styleable.Preference_forWork, false); } + + private static String getUserRestriction(Context context, AttributeSet attrs) { + TypedArray preferenceAttributes = context.obtainStyledAttributes(attrs, + R.styleable.RestrictedPreference); + String userRestriction = preferenceAttributes.getString( + R.styleable.RestrictedPreference_userRestriction); + preferenceAttributes.recycle(); + return userRestriction; + } } \ No newline at end of file diff --git a/src/com/android/settings/slices/SliceBuilderUtils.java b/src/com/android/settings/slices/SliceBuilderUtils.java index 6b69540ace1..c9d5f23f8d4 100644 --- a/src/com/android/settings/slices/SliceBuilderUtils.java +++ b/src/com/android/settings/slices/SliceBuilderUtils.java @@ -28,6 +28,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.os.UserHandle; import android.provider.SettingsSlicesContract; import android.text.TextUtils; import android.util.ArraySet; @@ -50,6 +51,8 @@ import com.android.settings.core.BasePreferenceController; import com.android.settings.core.SliderPreferenceController; import com.android.settings.core.SubSettingLauncher; import com.android.settings.core.TogglePreferenceController; +import com.android.settingslib.RestrictedLockUtils; +import com.android.settingslib.RestrictedLockUtilsInternal; import com.android.settingslib.core.AbstractPreferenceController; import java.util.Arrays; @@ -86,6 +89,16 @@ public class SliceBuilderUtils { return buildUnavailableSlice(context, sliceData); } + String userRestriction = sliceData.getUserRestriction(); + if (!TextUtils.isEmpty(userRestriction)) { + RestrictedLockUtils.EnforcedAdmin admin = + RestrictedLockUtilsInternal.checkIfRestrictionEnforced(context, + userRestriction, UserHandle.myUserId()); + if (admin != null) { + return buildIntentSlice(context, sliceData, controller); + } + } + switch (sliceData.getSliceType()) { case SliceData.SliceType.INTENT: return buildIntentSlice(context, sliceData, controller); diff --git a/src/com/android/settings/slices/SliceData.java b/src/com/android/settings/slices/SliceData.java index 01b29b23ce5..d97ebf5c5a6 100644 --- a/src/com/android/settings/slices/SliceData.java +++ b/src/com/android/settings/slices/SliceData.java @@ -73,6 +73,8 @@ public class SliceData { private final int mHighlightMenuRes; + private final String mUserRestriction; + @SliceType private final int mSliceType; @@ -132,6 +134,10 @@ public class SliceData { return mIsPublicSlice; } + public String getUserRestriction() { + return mUserRestriction; + } + private SliceData(Builder builder) { mKey = builder.mKey; mTitle = builder.mTitle; @@ -146,6 +152,7 @@ public class SliceData { mUnavailableSliceSubtitle = builder.mUnavailableSliceSubtitle; mIsPublicSlice = builder.mIsPublicSlice; mHighlightMenuRes = builder.mHighlightMenuRes; + mUserRestriction = builder.mUserRestriction; } @Override @@ -189,6 +196,8 @@ public class SliceData { private boolean mIsPublicSlice; + private String mUserRestriction; + public Builder setKey(String key) { mKey = key; return this; @@ -255,6 +264,11 @@ public class SliceData { return this; } + public Builder setUserRestriction(String userRestriction) { + mUserRestriction = userRestriction; + return this; + } + public SliceData build() { if (TextUtils.isEmpty(mKey)) { throw new InvalidSliceDataException("Key cannot be empty"); diff --git a/src/com/android/settings/slices/SliceDataConverter.java b/src/com/android/settings/slices/SliceDataConverter.java index 5177ff70505..61165cde315 100644 --- a/src/com/android/settings/slices/SliceDataConverter.java +++ b/src/com/android/settings/slices/SliceDataConverter.java @@ -22,6 +22,7 @@ import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_KEY; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_SUMMARY; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_TITLE; import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_UNAVAILABLE_SLICE_SUBTITLE; +import static com.android.settings.core.PreferenceXmlParserUtils.METADATA_USER_RESTRICTION; import android.accessibilityservice.AccessibilityServiceInfo; import android.app.settings.SettingsEnums; @@ -189,7 +190,8 @@ class SliceDataConverter { | MetadataFlag.FLAG_NEED_PREF_TITLE | MetadataFlag.FLAG_NEED_PREF_ICON | MetadataFlag.FLAG_NEED_PREF_SUMMARY - | MetadataFlag.FLAG_UNAVAILABLE_SLICE_SUBTITLE); + | MetadataFlag.FLAG_UNAVAILABLE_SLICE_SUBTITLE + | MetadataFlag.FLAG_NEED_USER_RESTRICTION); for (Bundle bundle : metadata) { // TODO (b/67996923) Non-controller Slices should become intent-only slices. @@ -218,6 +220,7 @@ class SliceDataConverter { METADATA_UNAVAILABLE_SLICE_SUBTITLE); final boolean isPublicSlice = controller.isPublicSlice(); final int highlightMenuRes = controller.getSliceHighlightMenuRes(); + final String userRestriction = bundle.getString(METADATA_USER_RESTRICTION); final SliceData xmlSlice = new SliceData.Builder() .setKey(key) @@ -232,6 +235,7 @@ class SliceDataConverter { .setUnavailableSliceSubtitle(unavailableSliceSubtitle) .setIsPublicSlice(isPublicSlice) .setHighlightMenuRes(highlightMenuRes) + .setUserRestriction(userRestriction) .build(); xmlSliceData.add(xmlSlice); diff --git a/src/com/android/settings/slices/SlicesDatabaseAccessor.java b/src/com/android/settings/slices/SlicesDatabaseAccessor.java index 75f0220f7ba..93aade18810 100644 --- a/src/com/android/settings/slices/SlicesDatabaseAccessor.java +++ b/src/com/android/settings/slices/SlicesDatabaseAccessor.java @@ -51,6 +51,7 @@ public class SlicesDatabaseAccessor { IndexColumns.SLICE_TYPE, IndexColumns.UNAVAILABLE_SLICE_SUBTITLE, IndexColumns.HIGHLIGHT_MENU_RESOURCE, + IndexColumns.USER_RESTRICTION, }; private final Context mContext; @@ -166,6 +167,8 @@ public class SlicesDatabaseAccessor { cursor.getColumnIndex(IndexColumns.UNAVAILABLE_SLICE_SUBTITLE)); final int highlightMenuRes = cursor.getInt( cursor.getColumnIndex(IndexColumns.HIGHLIGHT_MENU_RESOURCE)); + final String userRestriction = cursor.getString( + cursor.getColumnIndex(IndexColumns.USER_RESTRICTION)); if (isIntentOnly) { sliceType = SliceData.SliceType.INTENT; @@ -184,6 +187,7 @@ public class SlicesDatabaseAccessor { .setSliceType(sliceType) .setUnavailableSliceSubtitle(unavailableSliceSubtitle) .setHighlightMenuRes(highlightMenuRes) + .setUserRestriction(userRestriction) .build(); } diff --git a/src/com/android/settings/slices/SlicesDatabaseHelper.java b/src/com/android/settings/slices/SlicesDatabaseHelper.java index 69ad702913c..cad045e549b 100644 --- a/src/com/android/settings/slices/SlicesDatabaseHelper.java +++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java @@ -36,7 +36,7 @@ public class SlicesDatabaseHelper extends SQLiteOpenHelper { private static final String DATABASE_NAME = "slices_index.db"; private static final String SHARED_PREFS_TAG = "slices_shared_prefs"; - private static final int DATABASE_VERSION = 9; + private static final int DATABASE_VERSION = 10; public interface Tables { String TABLE_SLICES_INDEX = "slices_index"; @@ -108,6 +108,11 @@ public class SlicesDatabaseHelper extends SQLiteOpenHelper { * Resource ID for the menu entry of the setting. */ String HIGHLIGHT_MENU_RESOURCE = "highlight_menu"; + + /** + * The name of user restriction for the setting. + */ + String USER_RESTRICTION = "user_restriction"; } private static final String CREATE_SLICES_TABLE = @@ -138,6 +143,8 @@ public class SlicesDatabaseHelper extends SQLiteOpenHelper { + IndexColumns.PUBLIC_SLICE + ", " + IndexColumns.HIGHLIGHT_MENU_RESOURCE + + ", " + + IndexColumns.USER_RESTRICTION + " INTEGER DEFAULT 0 " + ");"; diff --git a/src/com/android/settings/slices/SlicesIndexer.java b/src/com/android/settings/slices/SlicesIndexer.java index 0160843d8e7..3e7f8005ef0 100644 --- a/src/com/android/settings/slices/SlicesIndexer.java +++ b/src/com/android/settings/slices/SlicesIndexer.java @@ -117,6 +117,7 @@ class SlicesIndexer implements Runnable { dataRow.getUnavailableSliceSubtitle()); values.put(IndexColumns.PUBLIC_SLICE, dataRow.isPublicSlice()); values.put(IndexColumns.HIGHLIGHT_MENU_RESOURCE, dataRow.getHighlightMenuRes()); + values.put(IndexColumns.USER_RESTRICTION, dataRow.getUserRestriction()); database.replaceOrThrow(Tables.TABLE_SLICES_INDEX, null /* nullColumnHack */, values); diff --git a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java index 45fea570970..67a55e6e1d9 100644 --- a/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java +++ b/tests/robotests/src/com/android/settings/slices/SliceBuilderUtilsTest.java @@ -42,28 +42,33 @@ import com.android.settings.testutils.FakeSliderController; import com.android.settings.testutils.FakeToggleController; import com.android.settings.testutils.FakeUnavailablePreferenceController; import com.android.settings.testutils.SliceTester; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; +import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; @RunWith(RobolectricTestRunner.class) +@Config(shadows = ShadowRestrictedLockUtilsInternal.class) public class SliceBuilderUtilsTest { - private final String KEY = "KEY"; - private final String TITLE = "title"; - private final String SUMMARY = "summary"; - private final String SCREEN_TITLE = "screen title"; - private final String KEYWORDS = "a, b, c"; - private final String FRAGMENT_NAME = "fragment name"; - private final int ICON = R.drawable.ic_settings_accent; - private final Uri URI = Uri.parse("content://com.android.settings.slices/test"); - private final Class TOGGLE_CONTROLLER = FakeToggleController.class; - private final Class SLIDER_CONTROLLER = FakeSliderController.class; - private final Class INVALID_SLIDER_CONTROLLER = FakeInvalidSliderController.class; - private final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class; + private static final String KEY = "KEY"; + private static final String TITLE = "title"; + private static final String SUMMARY = "summary"; + private static final String SCREEN_TITLE = "screen title"; + private static final String KEYWORDS = "a, b, c"; + private static final String FRAGMENT_NAME = "fragment name"; + private static final String RESTRICTION = "no_brightness"; + private static final int ICON = R.drawable.ic_settings_accent; + private static final Uri URI = Uri.parse("content://com.android.settings.slices/test"); + private static final Class TOGGLE_CONTROLLER = FakeToggleController.class; + private static final Class SLIDER_CONTROLLER = FakeSliderController.class; + private static final Class INVALID_SLIDER_CONTROLLER = FakeInvalidSliderController.class; + private static final Class CONTEXT_CONTROLLER = FakeContextOnlyPreferenceController.class; private Context mContext; @@ -74,6 +79,11 @@ public class SliceBuilderUtilsTest { SliceProvider.setSpecs(SliceLiveData.SUPPORTED_SPECS); } + @After + public void tearDown() { + ShadowRestrictedLockUtilsInternal.reset(); + } + @Test public void buildIntentSlice_returnsMatchingSlice() { final SliceData sliceData = getMockData(CONTEXT_CONTROLLER, SliceData.SliceType.INTENT); @@ -98,6 +108,27 @@ public class SliceBuilderUtilsTest { SliceTester.testSettingsSliderSlice(mContext, slice, data); } + @Test + public void buildToggleSlice_withUserRestriction_shouldReturnToggleSlice() { + final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH, + RESTRICTION); + + final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData); + + SliceTester.testSettingsToggleSlice(mContext, slice, mockData); + } + + @Test + public void buildToggleSlice_withUserRestrictionAndRestricted_shouldReturnIntentSlice() { + final SliceData mockData = getMockData(TOGGLE_CONTROLLER, SliceData.SliceType.SWITCH, + RESTRICTION); + ShadowRestrictedLockUtilsInternal.setRestricted(true); + + final Slice slice = SliceBuilderUtils.buildSlice(mContext, mockData); + + SliceTester.testSettingsIntentSlice(mContext, slice, mockData); + } + @Test public void testGetPreferenceController_buildsMatchingController() { final BasePreferenceController controller = @@ -425,8 +456,19 @@ public class SliceBuilderUtilsTest { null /* unavailableSliceSubtitle */); } + private SliceData getMockData(Class prefController, int sliceType, String userRestriction) { + return getMockData(prefController, SUMMARY, sliceType, SCREEN_TITLE, ICON, + null /* unavailableSliceSubtitle */, userRestriction); + } + private SliceData getMockData(Class prefController, String summary, int sliceType, String screenTitle, int icon, String unavailableSliceSubtitle) { + return getMockData(prefController, summary, sliceType, screenTitle, icon, + unavailableSliceSubtitle, null /* userRestriction */); + } + + private SliceData getMockData(Class prefController, String summary, int sliceType, + String screenTitle, int icon, String unavailableSliceSubtitle, String userRestriction) { return new SliceData.Builder() .setKey(KEY) .setTitle(TITLE) @@ -439,6 +481,7 @@ public class SliceBuilderUtilsTest { .setPreferenceControllerClassName(prefController.getName()) .setSliceType(sliceType) .setUnavailableSliceSubtitle(unavailableSliceSubtitle) + .setUserRestriction(userRestriction) .build(); } }