Merge "Add policy transparency for metered data related settings."
diff --git a/res/xml/app_data_usage.xml b/res/xml/app_data_usage.xml
index 3e94135..a4b2159 100644
--- a/res/xml/app_data_usage.xml
+++ b/res/xml/app_data_usage.xml
@@ -16,6 +16,8 @@
 
 <PreferenceScreen
     xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:settings="http://schemas.android.com/apk/res-auto"
+    android:key="app_data_usage_screen"
     android:title="@string/data_usage_app_summary_title">
 
     <com.android.settings.datausage.SpinnerPreference
@@ -50,15 +52,19 @@
             android:key="app_settings"
             android:title="@string/data_usage_app_settings" />
 
-        <SwitchPreference
+        <com.android.settingslib.RestrictedSwitchPreference
             android:key="restrict_background"
             android:title="@string/data_usage_app_restrict_background"
-            android:summary="@string/data_usage_app_restrict_background_summary" />
+            android:summary="@string/data_usage_app_restrict_background_summary"
+            settings:useAdditionalSummary="true"
+            settings:restrictedSwitchSummary="@string/disabled_by_admin" />
 
-        <SwitchPreference
+        <com.android.settingslib.RestrictedSwitchPreference
             android:key="unrestricted_data_saver"
             android:title="@string/unrestricted_app_title"
-            android:summary="@string/unrestricted_app_summary" />
+            android:summary="@string/unrestricted_app_summary"
+            settings:useAdditionalSummary="true"
+            settings:restrictedSwitchSummary="@string/disabled_by_admin" />
 
     </PreferenceCategory>
 
diff --git a/src/com/android/settings/datausage/AppDataUsage.java b/src/com/android/settings/datausage/AppDataUsage.java
index 5470e63..a0d0ec0 100644
--- a/src/com/android/settings/datausage/AppDataUsage.java
+++ b/src/com/android/settings/datausage/AppDataUsage.java
@@ -33,7 +33,6 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.support.annotation.VisibleForTesting;
-import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.Preference;
 import android.support.v7.preference.PreferenceCategory;
 import android.text.format.Formatter;
@@ -48,6 +47,9 @@
 import com.android.settings.applications.AppInfoBase;
 import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.AppItem;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.net.ChartData;
 import com.android.settingslib.net.ChartDataLoader;
 import com.android.settingslib.net.UidDetail;
@@ -80,7 +82,7 @@
     private Preference mForegroundUsage;
     private Preference mBackgroundUsage;
     private Preference mAppSettings;
-    private SwitchPreference mRestrictBackground;
+    private RestrictedSwitchPreference mRestrictBackground;
     private PreferenceCategory mAppList;
 
     private Drawable mIcon;
@@ -97,7 +99,7 @@
     private AppItem mAppItem;
     private Intent mAppSettingsIntent;
     private SpinnerPreference mCycle;
-    private SwitchPreference mUnrestrictedData;
+    private RestrictedSwitchPreference mUnrestrictedData;
     private DataSaverBackend mDataSaverBackend;
 
     @Override
@@ -160,9 +162,11 @@
                 removePreference(KEY_UNRESTRICTED_DATA);
                 removePreference(KEY_RESTRICT_BACKGROUND);
             } else {
-                mRestrictBackground = (SwitchPreference) findPreference(KEY_RESTRICT_BACKGROUND);
+                mRestrictBackground = (RestrictedSwitchPreference) findPreference(
+                        KEY_RESTRICT_BACKGROUND);
                 mRestrictBackground.setOnPreferenceChangeListener(this);
-                mUnrestrictedData = (SwitchPreference) findPreference(KEY_UNRESTRICTED_DATA);
+                mUnrestrictedData = (RestrictedSwitchPreference) findPreference(
+                        KEY_UNRESTRICTED_DATA);
                 mUnrestrictedData.setOnPreferenceChangeListener(this);
             }
             mDataSaverBackend = new DataSaverBackend(getContext());
@@ -261,8 +265,11 @@
     }
 
     private void updatePrefs(boolean restrictBackground, boolean unrestrictData) {
+        final EnforcedAdmin admin = RestrictedLockUtils.checkIfMeteredDataRestricted(
+                getContext(), mPackageName, UserHandle.getUserId(mAppItem.key));
         if (mRestrictBackground != null) {
             mRestrictBackground.setChecked(!restrictBackground);
+            mRestrictBackground.setDisabledByAdmin(admin);
         }
         if (mUnrestrictedData != null) {
             if (restrictBackground) {
@@ -270,6 +277,7 @@
             } else {
                 mUnrestrictedData.setVisible(true);
                 mUnrestrictedData.setChecked(unrestrictData);
+                mUnrestrictedData.setDisabledByAdmin(admin);
             }
         }
     }
diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccess.java b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
index e8a7bbf..cff4a50 100644
--- a/src/com/android/settings/datausage/UnrestrictedDataAccess.java
+++ b/src/com/android/settings/datausage/UnrestrictedDataAccess.java
@@ -14,6 +14,8 @@
 
 package com.android.settings.datausage;
 
+import static com.android.settingslib.RestrictedLockUtils.checkIfMeteredDataRestricted;
+
 import android.app.Application;
 import android.content.Context;
 import android.os.Bundle;
@@ -37,6 +39,8 @@
 import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
 import com.android.settings.overlay.FeatureFactory;
 import com.android.settings.widget.AppSwitchPreference;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.RestrictedPreferenceHelper;
 import com.android.settingslib.applications.ApplicationsState;
 import com.android.settingslib.applications.ApplicationsState.AppEntry;
 import com.android.settingslib.applications.ApplicationsState.AppFilter;
@@ -172,6 +176,8 @@
                 preference.setOnPreferenceChangeListener(this);
                 getPreferenceScreen().addPreference(preference);
             } else {
+                preference.setDisabledByAdmin(checkIfMeteredDataRestricted(getContext(),
+                        entry.info.packageName, UserHandle.getUserId(entry.info.uid)));
                 preference.reuse();
             }
             preference.setOrder(i);
@@ -242,16 +248,22 @@
         return app != null && UserHandle.isApp(app.info.uid);
     }
 
-    private class AccessPreference extends AppSwitchPreference
+    @VisibleForTesting
+    class AccessPreference extends AppSwitchPreference
             implements DataSaverBackend.Listener {
         private final AppEntry mEntry;
         private final DataUsageState mState;
+        private final RestrictedPreferenceHelper mHelper;
 
         public AccessPreference(final Context context, AppEntry entry) {
             super(context);
+            setWidgetLayoutResource(R.layout.restricted_switch_widget);
+            mHelper = new RestrictedPreferenceHelper(context, this, null);
             mEntry = entry;
             mState = (DataUsageState) mEntry.extraInfo;
             mEntry.ensureLabel(getContext());
+            setDisabledByAdmin(checkIfMeteredDataRestricted(context, entry.info.packageName,
+                    UserHandle.getUserId(entry.info.uid)));
             setState();
             if (mEntry.icon != null) {
                 setIcon(mEntry.icon);
@@ -291,12 +303,21 @@
             }
         }
 
+        @Override
+        public void performClick() {
+            if (!mHelper.performClick()) {
+                super.performClick();
+            }
+        }
+
         // Sets UI state based on whitelist/blacklist status.
         private void setState() {
             setTitle(mEntry.label);
             if (mState != null) {
                 setChecked(mState.isDataSaverWhitelisted);
-                if (mState.isDataSaverBlacklisted) {
+                if (isDisabledByAdmin()) {
+                    setSummary(R.string.disabled_by_admin);
+                } else if (mState.isDataSaverBlacklisted) {
                     setSummary(R.string.restrict_background_blacklisted);
                 } else {
                     setSummary("");
@@ -323,10 +344,21 @@
                     }
                 });
             }
-            holder.findViewById(android.R.id.widget_frame)
-                    .setVisibility(mState != null && mState.isDataSaverBlacklisted
-                            ? View.INVISIBLE : View.VISIBLE);
+            final boolean disabledByAdmin = isDisabledByAdmin();
+            final View widgetFrame = holder.findViewById(android.R.id.widget_frame);
+            if (disabledByAdmin) {
+                widgetFrame.setVisibility(View.VISIBLE);
+            } else {
+                widgetFrame.setVisibility(mState != null && mState.isDataSaverBlacklisted
+                        ? View.INVISIBLE : View.VISIBLE);
+            }
             super.onBindViewHolder(holder);
+
+            mHelper.onBindViewHolder(holder);
+            holder.findViewById(R.id.restricted_icon).setVisibility(
+                    disabledByAdmin ? View.VISIBLE : View.GONE);
+            holder.findViewById(android.R.id.switch_widget).setVisibility(
+                    disabledByAdmin ? View.GONE : View.VISIBLE);
         }
 
         @Override
@@ -348,6 +380,19 @@
                 reuse();
             }
         }
+
+        public void setDisabledByAdmin(EnforcedAdmin admin) {
+            mHelper.setDisabledByAdmin(admin);
+        }
+
+        public boolean isDisabledByAdmin() {
+            return mHelper.isDisabledByAdmin();
+        }
+
+        @VisibleForTesting
+        public AppEntry getEntryForTest() {
+            return mEntry;
+        }
     }
 
 }
diff --git a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
index 7cd09de..58643b6 100644
--- a/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/AppDataUsageTest.java
@@ -29,8 +29,8 @@
 import static org.mockito.Mockito.when;
 
 import android.content.pm.PackageManager;
+import android.net.NetworkPolicyManager;
 import android.os.Bundle;
-import android.support.v14.preference.SwitchPreference;
 import android.support.v7.preference.PreferenceManager;
 import android.support.v7.preference.PreferenceScreen;
 import android.util.ArraySet;
@@ -40,8 +40,11 @@
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
 import com.android.settings.testutils.shadow.ShadowEntityHeaderController;
+import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils;
 import com.android.settings.widget.EntityHeaderController;
 import com.android.settingslib.AppItem;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+import com.android.settingslib.RestrictedSwitchPreference;
 import com.android.settingslib.wrapper.PackageManagerWrapper;
 
 import org.junit.After;
@@ -57,7 +60,10 @@
 
 @RunWith(SettingsRobolectricTestRunner.class)
 @Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
-        shadows = ShadowEntityHeaderController.class)
+        shadows = {
+                ShadowEntityHeaderController.class,
+                ShadowRestrictedLockUtils.class
+        })
 public class AppDataUsageTest {
 
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
@@ -134,7 +140,7 @@
     public void changePreference_backgroundData_shouldUpdateUI() {
         mFragment = spy(new AppDataUsage());
         final AppItem appItem = new AppItem(123456789);
-        final SwitchPreference pref = mock(SwitchPreference.class);
+        final RestrictedSwitchPreference pref = mock(RestrictedSwitchPreference.class);
         final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class);
         ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
         ReflectionHelpers.setField(mFragment, "mRestrictBackground", pref);
@@ -146,4 +152,31 @@
 
         verify(mFragment).updatePrefs();
     }
+
+    @Test
+    public void updatePrefs_restrictedByAdmin_shouldDisablePreference() {
+        mFragment = spy(new AppDataUsage());
+        final int testUid = 123123;
+        final AppItem appItem = new AppItem(testUid);
+        final RestrictedSwitchPreference restrictBackgroundPref
+                = mock(RestrictedSwitchPreference.class);
+        final RestrictedSwitchPreference unrestrictedDataPref
+                = mock(RestrictedSwitchPreference.class);
+        final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class);
+        final NetworkPolicyManager networkPolicyManager = mock(NetworkPolicyManager.class);
+        ReflectionHelpers.setField(mFragment, "mAppItem", appItem);
+        ReflectionHelpers.setField(mFragment, "mRestrictBackground", restrictBackgroundPref);
+        ReflectionHelpers.setField(mFragment, "mUnrestrictedData", unrestrictedDataPref);
+        ReflectionHelpers.setField(mFragment, "mDataSaverBackend", dataSaverBackend);
+        ReflectionHelpers.setField(mFragment.services, "mPolicyManager", networkPolicyManager);
+
+        ShadowRestrictedLockUtils.setRestricted(true);
+        doReturn(NetworkPolicyManager.POLICY_NONE).when(networkPolicyManager)
+                .getUidPolicy(testUid);
+
+        mFragment.updatePrefs();
+
+        verify(restrictBackgroundPref).setDisabledByAdmin(any(EnforcedAdmin.class));
+        verify(unrestrictedDataPref).setDisabledByAdmin(any(EnforcedAdmin.class));
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
index 53cb7ed..fff879f 100644
--- a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
+++ b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessTest.java
@@ -16,41 +16,68 @@
 package com.android.settings.datausage;
 
 import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.fail;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.os.Process;
+import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceScreen;
 
 import com.android.internal.logging.nano.MetricsProto;
+import com.android.settings.R;
 import com.android.settings.TestConfig;
+import com.android.settings.datausage.AppStateDataUsageBridge.DataUsageState;
+import com.android.settings.datausage.UnrestrictedDataAccess.AccessPreference;
 import com.android.settings.testutils.FakeFeatureFactory;
 import com.android.settings.testutils.SettingsRobolectricTestRunner;
-import com.android.settingslib.applications.ApplicationsState;
+import com.android.settings.testutils.shadow.ShadowRestrictedLockUtils;
+import com.android.settingslib.applications.ApplicationsState.AppEntry;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
+import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Config;
+import org.robolectric.util.ReflectionHelpers;
+
+import java.util.ArrayList;
 
 @RunWith(SettingsRobolectricTestRunner.class)
-@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION)
+@Config(manifest = TestConfig.MANIFEST_PATH, sdk = TestConfig.SDK_VERSION,
+        shadows = {
+                ShadowRestrictedLockUtils.class
+        })
 public class UnrestrictedDataAccessTest {
 
     @Mock
-    private ApplicationsState.AppEntry mAppEntry;
+    private AppEntry mAppEntry;
     private UnrestrictedDataAccess mFragment;
     private FakeFeatureFactory mFeatureFactory;
+    @Mock
+    private PreferenceScreen mPreferenceScreen;
+    @Mock
+    private PreferenceManager mPreferenceManager;
+    @Mock
+    private DataSaverBackend mDataSaverBackend;
 
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mFeatureFactory = FakeFeatureFactory.setupForTest();
-        mFragment = new UnrestrictedDataAccess();
+        mFragment = spy(new UnrestrictedDataAccess());
     }
 
     @Test
@@ -80,4 +107,66 @@
                 eq(MetricsProto.MetricsEvent.APP_SPECIAL_PERMISSION_UNL_DATA_DENY), eq("app"));
     }
 
+    @Test
+    public void testOnRebuildComplete_restricted_shouldBeDisabled() {
+        final Context context = RuntimeEnvironment.application;
+        doReturn(context).when(mFragment).getContext();
+        doReturn(context).when(mPreferenceManager).getContext();
+        doReturn(true).when(mFragment).shouldAddPreference(any(AppEntry.class));
+        doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean());
+        doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen();
+        doReturn(mPreferenceManager).when(mFragment).getPreferenceManager();
+        ReflectionHelpers.setField(mFragment, "mDataSaverBackend", mDataSaverBackend);
+
+        final String testPkg1 = "com.example.one";
+        final String testPkg2 = "com.example.two";
+        ShadowRestrictedLockUtils.setRestrictedPkgs(testPkg2);
+
+        doAnswer((invocation) -> {
+            final AccessPreference preference = invocation.getArgument(0);
+            final AppEntry entry = preference.getEntryForTest();
+            // Verify preference is disabled by admin and the summary is changed accordingly.
+            if (testPkg1.equals(entry.info.packageName)) {
+                assertThat(preference.isDisabledByAdmin()).isFalse();
+                assertThat(preference.getSummary()).isEqualTo("");
+            } else if (testPkg2.equals(entry.info.packageName)) {
+                assertThat(preference.isDisabledByAdmin()).isTrue();
+                assertThat(preference.getSummary()).isEqualTo(
+                        context.getString(R.string.disabled_by_admin));
+            }
+            assertThat(preference.isChecked()).isFalse();
+            preference.performClick();
+            // Verify that when the preference is clicked, support details intent is launched
+            // if the preference is disabled by admin, otherwise the switch is toggled.
+            if (testPkg1.equals(entry.info.packageName)) {
+                assertThat(preference.isChecked()).isTrue();
+                assertThat(ShadowRestrictedLockUtils.hasAdminSupportDetailsIntentLaunched())
+                        .isFalse();
+            } else if (testPkg2.equals(entry.info.packageName)) {
+                assertThat(preference.isChecked()).isFalse();
+                assertThat(ShadowRestrictedLockUtils.hasAdminSupportDetailsIntentLaunched())
+                        .isTrue();
+            }
+            ShadowRestrictedLockUtils.clearAdminSupportDetailsIntentLaunch();
+            return null;
+        }).when(mPreferenceScreen).addPreference(any(AccessPreference.class));
+        mFragment.onRebuildComplete(createAppEntries(testPkg1, testPkg2));
+    }
+
+    private ArrayList<AppEntry> createAppEntries(String... packageNames) {
+        final ArrayList<AppEntry> appEntries = new ArrayList<>();
+        for (int i = 0; i < packageNames.length; ++i) {
+            final ApplicationInfo info = new ApplicationInfo();
+            info.packageName = packageNames[i];
+            info.uid = Process.FIRST_APPLICATION_UID + i;
+            info.sourceDir = info.packageName;
+            final AppEntry appEntry = spy(new AppEntry(RuntimeEnvironment.application,
+                    info, i));
+            appEntry.extraInfo = new DataUsageState(false, false);
+            doNothing().when(appEntry).ensureLabel(any(Context.class));
+            ReflectionHelpers.setField(appEntry, "info", info);
+            appEntries.add(appEntry);
+        }
+        return appEntries;
+    }
 }
diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java
new file mode 100644
index 0000000..afede1a
--- /dev/null
+++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtils.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2018 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.testutils.shadow;
+
+import android.content.Context;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.settingslib.RestrictedLockUtils;
+import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
+
+import org.robolectric.annotation.Implementation;
+import org.robolectric.annotation.Implements;
+
+@Implements(RestrictedLockUtils.class)
+public class ShadowRestrictedLockUtils {
+    private static boolean isRestricted;
+    private static String[] restrictedPkgs;
+    private static boolean adminSupportDetailsIntentLaunched;
+
+    @Implementation
+    public static RestrictedLockUtils.EnforcedAdmin checkIfMeteredDataRestricted(Context context,
+            String packageName, int userId) {
+        if (isRestricted) {
+            return new EnforcedAdmin();
+        }
+        if (ArrayUtils.contains(restrictedPkgs, packageName)) {
+            return new EnforcedAdmin();
+        }
+        return null;
+    }
+
+    @Implementation
+    public static void sendShowAdminSupportDetailsIntent(Context context, EnforcedAdmin admin) {
+        adminSupportDetailsIntentLaunched = true;
+    }
+
+    public static boolean hasAdminSupportDetailsIntentLaunched() {
+        return adminSupportDetailsIntentLaunched;
+    }
+
+    public static void clearAdminSupportDetailsIntentLaunch() {
+        adminSupportDetailsIntentLaunched = false;
+    }
+
+    public static void setRestricted(boolean restricted) {
+        isRestricted = restricted;
+    }
+
+    public static void setRestrictedPkgs(String... pkgs) {
+        restrictedPkgs = pkgs;
+    }
+}