From 96ab2b9e2569e77568170be8bfbe766b3211350a Mon Sep 17 00:00:00 2001 From: Hani Kazmi Date: Fri, 10 Nov 2023 16:19:51 +0000 Subject: [PATCH] Wired remaining SpecialAppAccess for ECM. This change enables the remaining settings for ECM, and adds tests for both this and previous ECM changes. Bug: 297372999 Test: Tested on device Test: atest SpaPrivilegedLibTests Test: atest com.android.settings.applications.specialaccess.notificationaccess Test: atest com.android.settings.datausage Test: atest PremiumSmsAccessTest Test: atest RestrictedPreferenceHelperTest Change-Id: I73d39d765dba0c1a75111c37b29ccf1c85d2cdd8 --- .../premiumsms/PremiumSmsAccess.java | 8 +- .../UnrestrictedDataAccessPreference.java | 22 +++- ...trictedDataAccessPreferenceController.java | 2 + .../notification/zen/ZenAccessSettings.java | 6 +- .../utils/ManagedServiceSettings.java | 12 +- .../widget/RestrictedAppPreference.java | 29 ++++- .../RestrictedPreferenceHelperTest.java | 39 ++++++ .../premiumsms/PremiumSmsAccessTest.java | 123 +++++++++++++++++- ...tedDataAccessPreferenceControllerTest.java | 78 +++++++++++ .../ShadowRestrictedLockUtilsInternal.java | 28 ++++ .../ApprovalPreferenceControllerTest.java | 10 +- 11 files changed, 343 insertions(+), 14 deletions(-) diff --git a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java index 4c9f813fc5e..1cf4c3a8e26 100644 --- a/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java +++ b/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccess.java @@ -25,7 +25,6 @@ import android.view.View; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; -import androidx.preference.DropDownPreference; import androidx.preference.Preference; import androidx.preference.Preference.OnPreferenceChangeListener; import androidx.preference.PreferenceScreen; @@ -38,6 +37,7 @@ import com.android.settings.applications.AppStateSmsPremBridge.SmsState; import com.android.settings.overlay.FeatureFactory; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.EmptyTextSettings; +import com.android.settingslib.RestrictedDropDownPreference; import com.android.settingslib.applications.ApplicationsState; import com.android.settingslib.applications.ApplicationsState.AppEntry; import com.android.settingslib.applications.ApplicationsState.Callbacks; @@ -52,6 +52,8 @@ import java.util.ArrayList; public class PremiumSmsAccess extends EmptyTextSettings implements Callback, Callbacks, OnPreferenceChangeListener { + private static final String ECM_RESTRICTION_KEY = "android:premium_sms_access"; + private ApplicationsState mApplicationsState; private AppStateSmsPremBridge mSmsBackend; private Session mSession; @@ -205,7 +207,7 @@ public class PremiumSmsAccess extends EmptyTextSettings } - private class PremiumSmsPreference extends DropDownPreference { + private class PremiumSmsPreference extends RestrictedDropDownPreference { private final AppEntry mAppEntry; public PremiumSmsPreference(AppEntry appEntry, Context context) { @@ -224,6 +226,8 @@ public class PremiumSmsAccess extends EmptyTextSettings }); setValue(String.valueOf(getCurrentValue())); setSummary("%s"); + this.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, appEntry.info.packageName, + appEntry.info.uid); } private int getCurrentValue() { diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java index 7a7eb8cc189..953ada34a2e 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreference.java @@ -21,6 +21,7 @@ import android.os.UserHandle; import android.view.View; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import androidx.preference.PreferenceViewHolder; import com.android.settings.R; @@ -36,6 +37,7 @@ import com.android.settingslib.widget.AppSwitchPreference; public class UnrestrictedDataAccessPreference extends AppSwitchPreference implements DataSaverBackend.Listener { + private static final String ECM_RESTRICTION_KEY = "android:unrestricted_data_access"; private final ApplicationsState mApplicationsState; private final AppEntry mEntry; @@ -58,6 +60,8 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem mParentFragment = parentFragment; setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled( context, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); + mHelper.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, entry.info.packageName, + entry.info.uid); updateState(); setKey(generateKey(mEntry)); @@ -166,10 +170,25 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem return mHelper.isDisabledByAdmin(); } + @VisibleForTesting + boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public void setDisabledByAdmin(EnforcedAdmin admin) { mHelper.setDisabledByAdmin(admin); } + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(@Nullable String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(ECM_RESTRICTION_KEY, packageName, uid); + } + // Sets UI state based on allowlist/denylist status. public void updateState() { setTitle(mEntry.label); @@ -179,7 +198,8 @@ public class UnrestrictedDataAccessPreference extends AppSwitchPreference implem setSummary(com.android.settingslib.widget.restricted.R.string.disabled_by_admin); } else if (mDataUsageState.isDataSaverDenylisted) { setSummary(R.string.restrict_background_blocklisted); - } else { + // If disabled by ECM, the summary is set directly by the switch. + } else if (!isDisabledByEcm()) { setSummary(""); } } diff --git a/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java index fd2fcda71e1..3e0ee468be4 100644 --- a/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java +++ b/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceController.java @@ -151,6 +151,8 @@ public class UnrestrictedDataAccessPreferenceController extends BasePreferenceCo } else { preference.setDisabledByAdmin(checkIfMeteredDataUsageUserControlDisabled(mContext, entry.info.packageName, UserHandle.getUserId(entry.info.uid))); + preference.checkEcmRestrictionAndSetDisabled(entry.info.packageName, + entry.info.uid); preference.updateState(); } preference.setOrder(i); diff --git a/src/com/android/settings/notification/zen/ZenAccessSettings.java b/src/com/android/settings/notification/zen/ZenAccessSettings.java index 418a5719b8a..0dcffbaf628 100644 --- a/src/com/android/settings/notification/zen/ZenAccessSettings.java +++ b/src/com/android/settings/notification/zen/ZenAccessSettings.java @@ -39,8 +39,8 @@ import com.android.settings.applications.specialaccess.zenaccess.ZenAccessDetail import com.android.settings.applications.specialaccess.zenaccess.ZenAccessSettingObserverMixin; import com.android.settings.search.BaseSearchIndexProvider; import com.android.settings.widget.EmptyTextSettings; +import com.android.settings.widget.RestrictedAppPreference; import com.android.settingslib.search.SearchIndexable; -import com.android.settingslib.widget.AppPreference; import java.util.ArrayList; import java.util.Collections; @@ -122,7 +122,7 @@ public class ZenAccessSettings extends EmptyTextSettings implements for (ApplicationInfo app : apps) { final String pkg = app.packageName; final CharSequence label = app.loadLabel(mPkgMan); - final AppPreference pref = new AppPreference(getPrefContext()); + final RestrictedAppPreference pref = new RestrictedAppPreference(getPrefContext()); pref.setKey(pkg); pref.setIcon(app.loadIcon(mPkgMan)); pref.setTitle(label); @@ -133,6 +133,8 @@ public class ZenAccessSettings extends EmptyTextSettings implements } else { // Not auto approved, update summary according to notification backend. pref.setSummary(getPreferenceSummary(pkg)); + pref.checkEcmRestrictionAndSetDisabled( + android.Manifest.permission.MANAGE_NOTIFICATIONS, app.packageName, app.uid); } pref.setOnPreferenceClickListener(preference -> { AppInfoBase.startAppInfoFragment( diff --git a/src/com/android/settings/utils/ManagedServiceSettings.java b/src/com/android/settings/utils/ManagedServiceSettings.java index d5f00408fd5..b6b1e79c32b 100644 --- a/src/com/android/settings/utils/ManagedServiceSettings.java +++ b/src/com/android/settings/utils/ManagedServiceSettings.java @@ -37,14 +37,14 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceScreen; -import androidx.preference.TwoStatePreference; import com.android.settings.R; import com.android.settings.Utils; import com.android.settings.core.instrumentation.InstrumentedDialogFragment; import com.android.settings.widget.EmptyTextSettings; +import com.android.settingslib.RestrictedSwitchPreference; import com.android.settingslib.applications.ServiceListing; -import com.android.settingslib.widget.AppSwitchPreference; +import com.android.settingslib.widget.TwoTargetPreference; import java.util.List; @@ -121,10 +121,12 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings { } final CharSequence finalTitle = title; final String summary = service.loadLabel(mPm).toString(); - final TwoStatePreference pref = new AppSwitchPreference(getPrefContext()); + final RestrictedSwitchPreference pref = + new RestrictedSwitchPreference(getPrefContext()); pref.setPersistent(false); pref.setIcon(mIconDrawableFactory.getBadgedIcon(service, service.applicationInfo, UserHandle.getUserId(service.applicationInfo.uid))); + pref.setIconSize(TwoTargetPreference.ICON_SIZE_MEDIUM); if (title != null && !title.equals(summary)) { pref.setTitle(title); pref.setSummary(summary); @@ -150,6 +152,10 @@ public abstract class ManagedServiceSettings extends EmptyTextSettings { } }); pref.setKey(cn.flattenToString()); + if (!pref.isChecked()) { + pref.checkEcmRestrictionAndSetDisabled(mConfig.permission, service.packageName, + service.applicationInfo.uid); + } screen.addPreference(pref); } highlightPreferenceIfNeeded(); diff --git a/src/com/android/settings/widget/RestrictedAppPreference.java b/src/com/android/settings/widget/RestrictedAppPreference.java index f93b935d5b0..5b6fe5e1cc8 100644 --- a/src/com/android/settings/widget/RestrictedAppPreference.java +++ b/src/com/android/settings/widget/RestrictedAppPreference.java @@ -72,10 +72,18 @@ public class RestrictedAppPreference extends AppPreference { @Override public void setEnabled(boolean enabled) { - if (isDisabledByAdmin() && enabled) { - return; + boolean changed = false; + if (enabled && isDisabledByAdmin()) { + mHelper.setDisabledByAdmin(null); + changed = true; + } + if (enabled && isDisabledByEcm()) { + mHelper.setDisabledByEcm(null); + changed = true; + } + if (!changed) { + super.setEnabled(enabled); } - super.setEnabled(enabled); } public void setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin admin) { @@ -88,6 +96,10 @@ public class RestrictedAppPreference extends AppPreference { return mHelper.isDisabledByAdmin(); } + public boolean isDisabledByEcm() { + return mHelper.isDisabledByEcm(); + } + public void useAdminDisabledSummary(boolean useSummary) { mHelper.useAdminDisabledSummary(useSummary); } @@ -112,4 +124,15 @@ public class RestrictedAppPreference extends AppPreference { public void checkRestrictionAndSetDisabled(String userRestriction, int userId) { mHelper.checkRestrictionAndSetDisabled(userRestriction, userId); } + + /** + * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this + * package. Marks the preference as disabled if so. + * @param restriction The key identifying the setting + * @param packageName the package to check the restriction for + * @param uid the uid of the package + */ + public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) { + mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid); + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java index 99a78cfc408..14c11ec7567 100644 --- a/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/RestrictedPreferenceHelperTest.java @@ -31,9 +31,12 @@ import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; +import android.platform.test.flag.junit.SetFlagsRule; +import android.security.Flags; import androidx.test.core.app.ApplicationProvider; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; import com.android.settingslib.RestrictedPreference; import org.junit.Rule; @@ -45,6 +48,7 @@ import org.mockito.Spy; import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; import org.robolectric.RobolectricTestRunner; +import org.robolectric.annotation.Config; import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; @@ -53,6 +57,9 @@ import java.util.List; /** Test for {@link RestrictedPreferenceHelper}. */ @RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowRestrictedLockUtilsInternal.class +}) public class RestrictedPreferenceHelperTest { private static final String PACKAGE_NAME = "com.android.test"; @@ -72,6 +79,9 @@ public class RestrictedPreferenceHelperTest { private AccessibilityShortcutInfo mShortcutInfo; private final RestrictedPreferenceHelper mHelper = new RestrictedPreferenceHelper(mContext); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + @Test public void createAccessibilityServicePreferenceList_hasOneInfo_containsSameKey() { final String key = COMPONENT_NAME.flattenToString(); @@ -85,6 +95,35 @@ public class RestrictedPreferenceHelperTest { assertThat(preference.getKey()).isEqualTo(key); } + @Test + public void createAccessibilityServicePreferenceList_ecmRestricted_prefIsEcmRestricted() { + mSetFlagsRule.enableFlags(Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs( + mServiceInfo.getResolveInfo().serviceInfo.packageName); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isTrue(); + } + + @Test + public void createAccessibilityServicePreferenceList_ecmNotRestricted_prefIsNotEcmRestricted() { + mSetFlagsRule.enableFlags(Flags.FLAG_EXTEND_ECM_TO_ALL_SETTINGS); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + final List infoList = new ArrayList<>( + singletonList(mServiceInfo)); + + final List preferenceList = + mHelper.createAccessibilityServicePreferenceList(infoList); + final RestrictedPreference preference = preferenceList.get(0); + + assertThat(preference.isDisabledByEcm()).isFalse(); + } + @Test public void createAccessibilityActivityPreferenceList_hasOneInfo_containsSameKey() { final String key = COMPONENT_NAME.flattenToString(); diff --git a/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java b/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java index e91c0fa571f..69d6bac0024 100644 --- a/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java +++ b/tests/robotests/src/com/android/settings/applications/specialaccess/premiumsms/PremiumSmsAccessTest.java @@ -16,13 +16,35 @@ package com.android.settings.applications.specialaccess.premiumsms; +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import android.app.settings.SettingsEnums; +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.os.Process; import android.telephony.SmsManager; +import android.view.LayoutInflater; +import android.view.View; + +import androidx.preference.PreferenceManager; +import androidx.preference.PreferenceScreen; +import androidx.preference.PreferenceViewHolder; +import androidx.preference.R; import com.android.internal.logging.nano.MetricsProto; +import com.android.settings.datausage.AppStateDataUsageBridge; import com.android.settings.testutils.FakeFeatureFactory; +import com.android.settings.testutils.shadow.ShadowRestrictedLockUtilsInternal; +import com.android.settingslib.RestrictedDropDownPreference; +import com.android.settingslib.applications.ApplicationsState; import org.junit.Before; import org.junit.Test; @@ -30,19 +52,28 @@ import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; +import org.robolectric.util.ReflectionHelpers; + +import java.util.ArrayList; @RunWith(RobolectricTestRunner.class) +@Config(shadows = { + ShadowRestrictedLockUtilsInternal.class +}) public class PremiumSmsAccessTest { private FakeFeatureFactory mFeatureFactory; private PremiumSmsAccess mFragment; + private Context mContext; @Before public void setUp() { MockitoAnnotations.initMocks(this); mFeatureFactory = FakeFeatureFactory.setupForTest(); mFragment = new PremiumSmsAccess(); - mFragment.onAttach(RuntimeEnvironment.application); + mContext = RuntimeEnvironment.application; + mFragment.onAttach(mContext); } @Test @@ -74,4 +105,94 @@ public class PremiumSmsAccessTest { "app", SmsManager.PREMIUM_SMS_CONSENT_ALWAYS_ALLOW); } + + @Test + public void onRebuildComplete_ecmRestricted_shouldBeDisabled() { + mFragment = spy(mFragment); + mContext = spy(mContext); + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.preference_dropdown, null); + PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = spy(preferenceManager.createPreferenceScreen(mContext)); + doReturn(preferenceManager).when(mFragment).getPreferenceManager(); + doReturn(preferenceScreen).when(mFragment).getPreferenceScreen(); + final String testPkg = "com.example.disabled"; + doNothing().when(mContext).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(testPkg); + + doAnswer((invocation) -> { + final RestrictedDropDownPreference preference = invocation.getArgument(0); + final ApplicationsState.AppEntry entry = ReflectionHelpers + .getField(preference, "mAppEntry"); + // Verify preference is disabled by ecm and the summary is changed accordingly. + assertThat(preference.isDisabledByEcm()).isTrue(); + assertThat(preference.getSummary().toString()).isEqualTo( + mContext.getString( + com.android.settingslib.R.string.disabled_by_app_ops_text)); + preference.onBindViewHolder(holder); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is launched + verify(mContext).startActivity(any()); + + return null; + }).when(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + + mFragment.onRebuildComplete(createAppEntries(testPkg)); + verify(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + } + + @Test + public void onRebuildComplete_ecmNotRestricted_notDisabled() { + mFragment = spy(mFragment); + mContext = spy(mContext); + LayoutInflater inflater = LayoutInflater.from(mContext); + View view = inflater.inflate(R.layout.preference_dropdown, null); + PreferenceViewHolder holder = PreferenceViewHolder.createInstanceForTests(view); + + PreferenceManager preferenceManager = new PreferenceManager(mContext); + PreferenceScreen preferenceScreen = spy(preferenceManager.createPreferenceScreen(mContext)); + doReturn(preferenceManager).when(mFragment).getPreferenceManager(); + doReturn(preferenceScreen).when(mFragment).getPreferenceScreen(); + final String testPkg = "com.example.enabled"; + doNothing().when(mContext).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + + doAnswer((invocation) -> { + final RestrictedDropDownPreference preference = invocation.getArgument(0); + final ApplicationsState.AppEntry entry = ReflectionHelpers + .getField(preference, "mAppEntry"); + assertThat(preference.isDisabledByEcm()).isFalse(); + assertThat(preference.getSummary().toString()).isEqualTo(""); + preference.onBindViewHolder(holder); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is not launched + verify(mContext, never()).startActivity(any()); + + return null; + }).when(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + + mFragment.onRebuildComplete(createAppEntries(testPkg)); + verify(preferenceScreen).addPreference(any(RestrictedDropDownPreference.class)); + } + + private ArrayList createAppEntries(String... packageNames) { + final ArrayList 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 ApplicationsState.AppEntry appEntry = + spy(new ApplicationsState.AppEntry(mContext, info, i)); + appEntry.extraInfo = new AppStateDataUsageBridge + .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/datausage/UnrestrictedDataAccessPreferenceControllerTest.java b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java index 8aa2e5aaabf..3b01e7ece22 100644 --- a/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java +++ b/tests/robotests/src/com/android/settings/datausage/UnrestrictedDataAccessPreferenceControllerTest.java @@ -25,6 +25,7 @@ import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -163,6 +164,83 @@ public class UnrestrictedDataAccessPreferenceControllerTest { mController.onRebuildComplete(createAppEntries(testPkg1, testPkg2)); } + @Test + public void onRebuildComplete_ecmRestricted_shouldBeDisabled() { + mFragment = spy(new UnrestrictedDataAccess()); + doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); + mController.setParentFragment(mFragment); + final Context context = spy(mContext); + mPreferenceManager = new PreferenceManager(context); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(context)); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + doReturn(0).when(mPreferenceScreen).getPreferenceCount(); + final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); + ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); + ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); + + final String testPkg = "com.example.disabled"; + doNothing().when(context).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(testPkg); + + doAnswer((invocation) -> { + final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); + final AppEntry entry = preference.getEntry(); + // Verify preference is disabled by ecm and the summary is changed accordingly. + assertThat(preference.isDisabledByEcm()).isTrue(); + assertThat(preference.getSummary().toString()).isEqualTo( + mContext.getString( + com.android.settingslib.R.string.disabled_by_app_ops_text)); + assertThat(preference.isChecked()).isFalse(); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is launched + assertThat(preference.isChecked()).isFalse(); + verify(context).startActivity(any()); + + return null; + }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + + mController.onRebuildComplete(createAppEntries(testPkg)); + verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + } + + @Test + public void onRebuildComplete_ecmNotRestricted_notDisabled() { + mFragment = spy(new UnrestrictedDataAccess()); + doNothing().when(mFragment).setLoading(anyBoolean(), anyBoolean()); + mController.setParentFragment(mFragment); + final Context context = spy(mContext); + mPreferenceManager = new PreferenceManager(context); + mPreferenceScreen = spy(mPreferenceManager.createPreferenceScreen(context)); + doReturn(mPreferenceManager).when(mFragment).getPreferenceManager(); + doReturn(mPreferenceScreen).when(mFragment).getPreferenceScreen(); + doReturn(0).when(mPreferenceScreen).getPreferenceCount(); + final DataSaverBackend dataSaverBackend = mock(DataSaverBackend.class); + ReflectionHelpers.setField(mController, "mDataSaverBackend", dataSaverBackend); + ReflectionHelpers.setField(mController, "mScreen", mPreferenceScreen); + + final String testPkg = "com.example.enabled"; + doNothing().when(context).startActivity(any()); + ShadowRestrictedLockUtilsInternal.setEcmRestrictedPkgs(); + + doAnswer((invocation) -> { + final UnrestrictedDataAccessPreference preference = invocation.getArgument(0); + final AppEntry entry = preference.getEntry(); + assertThat(preference.isDisabledByEcm()).isFalse(); + assertThat(preference.getSummary()).isEqualTo(""); + assertThat(preference.isChecked()).isFalse(); + preference.performClick(); + // Verify that when the preference is clicked, ecm details intent is not launched + assertThat(preference.isChecked()).isTrue(); + verify(context, never()).startActivity(any()); + + return null; + }).when(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + + mController.onRebuildComplete(createAppEntries(testPkg)); + verify(mPreferenceScreen).addPreference(any(UnrestrictedDataAccessPreference.class)); + } + private ArrayList createAppEntries(String... packageNames) { final ArrayList appEntries = new ArrayList<>(); for (int i = 0; i < packageNames.length; ++i) { diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java index 5989d49c7fd..6630fee6520 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowRestrictedLockUtilsInternal.java @@ -15,9 +15,12 @@ */ package com.android.settings.testutils.shadow; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.UserIdInt; import android.app.admin.DevicePolicyManager; import android.content.Context; +import android.content.Intent; import com.android.internal.util.ArrayUtils; import com.android.settingslib.RestrictedLockUtils.EnforcedAdmin; @@ -38,6 +41,8 @@ public class ShadowRestrictedLockUtilsInternal { private static DevicePolicyManager sDevicePolicyManager; private static String[] sDisabledTypes; private static int sKeyguardDisabledFeatures; + private static String[] sEcmRestrictedPkgs; + private static boolean sAccessibilityServiceRestricted; @Resetter public static void reset() { @@ -47,6 +52,8 @@ public class ShadowRestrictedLockUtilsInternal { sDisabledTypes = new String[0]; sMaximumTimeToLockIsSet = false; sMteOverridden = false; + sEcmRestrictedPkgs = new String[0]; + sAccessibilityServiceRestricted = false; } @Implementation @@ -108,10 +115,31 @@ public class ShadowRestrictedLockUtilsInternal { return sMteOverridden ? new EnforcedAdmin() : null; } + public static EnforcedAdmin checkIfAccessibilityServiceDisallowed(Context context, + String packageName, int userId) { + return sAccessibilityServiceRestricted ? new EnforcedAdmin() : null; + } + + @Implementation + public static Intent checkIfRequiresEnhancedConfirmation(@NonNull Context context, + @NonNull String restriction, + int uid, + @Nullable String packageName) { + if (ArrayUtils.contains(sEcmRestrictedPkgs, packageName)) { + return new Intent(); + } + + return null; + } + public static void setRestricted(boolean restricted) { sIsRestricted = restricted; } + public static void setEcmRestrictedPkgs(String... pkgs) { + sEcmRestrictedPkgs = pkgs; + } + public static void setRestrictedPkgs(String... pkgs) { sRestrictedPkgs = pkgs; } diff --git a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java index fa5af6d4f9e..32a2fbf9d28 100644 --- a/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java +++ b/tests/unit/src/com/android/settings/applications/specialaccess/notificationaccess/ApprovalPreferenceControllerTest.java @@ -21,6 +21,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; @@ -137,14 +138,19 @@ public class ApprovalPreferenceControllerTest { @Test public void restrictedSettings_appOpsDisabled() { - when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString())).thenReturn( - AppOpsManager.MODE_ERRORED); + when(mAppOpsManager.noteOpNoThrow(anyInt(), anyInt(), anyString(), isNull(), isNull())) + .thenReturn(AppOpsManager.MODE_ERRORED); + doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE); when(mNm.isNotificationListenerAccessGranted(mCn)).thenReturn(false); RestrictedSwitchPreference pref = new RestrictedSwitchPreference( mContext); pref.setAppOps(mAppOpsManager); + mController.setAppOpStr(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS); mController.updateState(pref); + + verify(mContext).getSystemService(Context.APP_OPS_SERVICE); + verify(mAppOpsManager).noteOpNoThrow(anyInt(), anyInt(), anyString(), isNull(), isNull()); assertThat(pref.isEnabled()).isFalse(); }