Restrict toggle/slider slice when the preference restricted

Bug: 289980550
Test: robotest & manual
Change-Id: Id87fbf12a2722344dd07886e810e7c61a9f401aa
diff --git a/res/xml/sound_settings.xml b/res/xml/sound_settings.xml
index c3e0eef..57e3249 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"/>
 
     <!-- Media output switcher -->
@@ -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"/>
 
     <!-- Hands free profile output switcher -->
@@ -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"/>
 
     <!-- Notification volume -->
@@ -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"/>
 
     <!-- TODO(b/174964721): make this a PrimarySwitchPreference -->
diff --git a/src/com/android/settings/core/PreferenceXmlParserUtils.java b/src/com/android/settings/core/PreferenceXmlParserUtils.java
index a1a8d67..37d9144 100644
--- a/src/com/android/settings/core/PreferenceXmlParserUtils.java
+++ b/src/com/android/settings/core/PreferenceXmlParserUtils.java
@@ -74,7 +74,8 @@
             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 @@
         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 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 @@
                 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 @@
         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 6b69540..c9d5f23 100644
--- a/src/com/android/settings/slices/SliceBuilderUtils.java
+++ b/src/com/android/settings/slices/SliceBuilderUtils.java
@@ -28,6 +28,7 @@
 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.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 @@
             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 01b29b2..d97ebf5 100644
--- a/src/com/android/settings/slices/SliceData.java
+++ b/src/com/android/settings/slices/SliceData.java
@@ -73,6 +73,8 @@
 
     private final int mHighlightMenuRes;
 
+    private final String mUserRestriction;
+
     @SliceType
     private final int mSliceType;
 
@@ -132,6 +134,10 @@
         return mIsPublicSlice;
     }
 
+    public String getUserRestriction() {
+        return mUserRestriction;
+    }
+
     private SliceData(Builder builder) {
         mKey = builder.mKey;
         mTitle = builder.mTitle;
@@ -146,6 +152,7 @@
         mUnavailableSliceSubtitle = builder.mUnavailableSliceSubtitle;
         mIsPublicSlice = builder.mIsPublicSlice;
         mHighlightMenuRes = builder.mHighlightMenuRes;
+        mUserRestriction = builder.mUserRestriction;
     }
 
     @Override
@@ -189,6 +196,8 @@
 
         private boolean mIsPublicSlice;
 
+        private String mUserRestriction;
+
         public Builder setKey(String key) {
             mKey = key;
             return this;
@@ -255,6 +264,11 @@
             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 5177ff7..61165cd 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_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 @@
                             | 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 @@
                         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 @@
                         .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 75f0220..93aade1 100644
--- a/src/com/android/settings/slices/SlicesDatabaseAccessor.java
+++ b/src/com/android/settings/slices/SlicesDatabaseAccessor.java
@@ -51,6 +51,7 @@
             IndexColumns.SLICE_TYPE,
             IndexColumns.UNAVAILABLE_SLICE_SUBTITLE,
             IndexColumns.HIGHLIGHT_MENU_RESOURCE,
+            IndexColumns.USER_RESTRICTION,
     };
 
     private final Context mContext;
@@ -166,6 +167,8 @@
                 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 @@
                 .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 69ad702..cad045e 100644
--- a/src/com/android/settings/slices/SlicesDatabaseHelper.java
+++ b/src/com/android/settings/slices/SlicesDatabaseHelper.java
@@ -36,7 +36,7 @@
     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 @@
          * 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 @@
                     + 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 0160843..3e7f800 100644
--- a/src/com/android/settings/slices/SlicesIndexer.java
+++ b/src/com/android/settings/slices/SlicesIndexer.java
@@ -117,6 +117,7 @@
                     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 45fea57..67a55e6 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.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 @@
         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);
@@ -99,6 +109,27 @@
     }
 
     @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 =
                 SliceBuilderUtils.getPreferenceController(mContext, getMockData());
@@ -425,8 +456,19 @@
                 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 @@
                 .setPreferenceControllerClassName(prefController.getName())
                 .setSliceType(sliceType)
                 .setUnavailableSliceSubtitle(unavailableSliceSubtitle)
+                .setUserRestriction(userRestriction)
                 .build();
     }
 }