Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b8b2e51e authored by Sudheer Shanka's avatar Sudheer Shanka
Browse files

Add policy transparency for metered data related settings.

With API Dpm.setMeteredDataDisabled, admins can restrict some
packages from accessing metered data and when admin does restrict
packages, user settings to enable usage of mobile data in background or
allow unrestricted access during data saver mode are not relevant. Add
policy transparency to these settings so that user knows that admin
disabled them.

Bug: 63700027
Test: make RunSettingsRoboTests
Test: manual
Change-Id: I450f7a91356ed8fb33f464620c73fa9407a1ff83
parent adb949da
Loading
Loading
Loading
Loading
+10 −4
Original line number Diff line number Diff line
@@ -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>

+13 −5
Original line number Diff line number Diff line
@@ -33,7 +33,6 @@ import android.os.Bundle;
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.R;
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 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
    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 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
    private AppItem mAppItem;
    private Intent mAppSettingsIntent;
    private SpinnerPreference mCycle;
    private SwitchPreference mUnrestrictedData;
    private RestrictedSwitchPreference mUnrestrictedData;
    private DataSaverBackend mDataSaverBackend;

    @Override
@@ -160,9 +162,11 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
                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 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
    }

    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 @@ public class AppDataUsage extends DataUsageBase implements Preference.OnPreferen
            } else {
                mUnrestrictedData.setVisible(true);
                mUnrestrictedData.setChecked(unrestrictData);
                mUnrestrictedData.setDisabledByAdmin(admin);
            }
        }
    }
+50 −5
Original line number Diff line number Diff line
@@ -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.core.FeatureFlags;
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 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment
                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 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment
        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 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment
            }
        }

        @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 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment
                    }
                });
            }
            holder.findViewById(android.R.id.widget_frame)
                    .setVisibility(mState != null && mState.isDataSaverBlacklisted
            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 @@ public class UnrestrictedDataAccess extends SettingsPreferenceFragment
                reuse();
            }
        }

        public void setDisabledByAdmin(EnforcedAdmin admin) {
            mHelper.setDisabledByAdmin(admin);
        }

        public boolean isDisabledByAdmin() {
            return mHelper.isDisabledByAdmin();
        }

        @VisibleForTesting
        public AppEntry getEntryForTest() {
            return mEntry;
        }
    }

}
+36 −3
Original line number Diff line number Diff line
@@ -29,8 +29,8 @@ import static org.mockito.Mockito.verify;
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.TestConfig;
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 @@ import org.robolectric.util.ReflectionHelpers;

@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 class AppDataUsageTest {
    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 @@ public class AppDataUsageTest {

        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));
    }
}
+93 −4
Original line number Diff line number Diff line
@@ -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 @@ public class UnrestrictedDataAccessTest {
                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;
    }
}
Loading