Loading services/core/java/com/android/server/RescueParty.java +92 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import static android.provider.DeviceConfig.Properties; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; Loading @@ -36,6 +38,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Log; Loading Loading @@ -93,6 +96,12 @@ public class RescueParty { static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); @VisibleForTesting static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS; // The DeviceConfig namespace containing all RescueParty switches. @VisibleForTesting static final String NAMESPACE_CONFIGURATION = "configuration"; @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; private static final String NAME = "rescue-party-observer"; Loading @@ -103,8 +112,6 @@ public class RescueParty { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; // The DeviceConfig namespace containing all RescueParty switches. private static final String NAMESPACE_CONFIGURATION = "configuration"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; Loading Loading @@ -170,6 +177,81 @@ public class RescueParty { })); } /** * Called when {@code RollbackManager} performs Mainline module rollbacks, * to avoid rolled back modules consuming flag values only expected to work * on modules of newer versions. */ public static void resetDeviceConfigForPackages(List<String> packageNames) { if (packageNames == null) { return; } Set<String> namespacesToReset = new ArraySet<String>(); Iterator<String> it = packageNames.iterator(); RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated(); // Get runtime package to namespace mapping if created. if (rescuePartyObserver != null) { while (it.hasNext()) { String packageName = it.next(); Set<String> runtimeAffectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet(packageName); if (runtimeAffectedNamespaces != null) { namespacesToReset.addAll(runtimeAffectedNamespaces); } } } // Get preset package to namespace mapping if created. Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages( packageNames); if (presetAffectedNamespaces != null) { namespacesToReset.addAll(presetAffectedNamespaces); } // Clear flags under the namespaces mapped to these packages. // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set. Iterator<String> namespaceIt = namespacesToReset.iterator(); while (namespaceIt.hasNext()) { String namespaceToReset = namespaceIt.next(); Properties properties = new Properties.Builder(namespaceToReset).build(); try { DeviceConfig.setProperties(properties); } catch (DeviceConfig.BadConfigException exception) { logCriticalInfo(Log.WARN, "namespace " + namespaceToReset + " is already banned, skip reset."); } } } private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) { Set<String> resultSet = new ArraySet<String>(); try { String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION, NAMESPACE_TO_PACKAGE_MAPPING_FLAG, ""); String[] mappingEntries = flagVal.split(","); for (int i = 0; i < mappingEntries.length; i++) { if (TextUtils.isEmpty(mappingEntries[i])) { continue; } String[] splittedEntry = mappingEntries[i].split(":"); if (splittedEntry.length != 2) { throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]); } String namespace = splittedEntry[0]; String packageName = splittedEntry[1]; if (packageNames.contains(packageName)) { resultSet.add(namespace); } } } catch (Exception e) { resultSet.clear(); Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e); } finally { return resultSet; } } @VisibleForTesting static long getElapsedRealtime() { return SystemClock.elapsedRealtime(); Loading Loading @@ -469,6 +551,14 @@ public class RescueParty { } } /** Gets singleton instance. It returns null if the instance is not created yet.*/ @Nullable public static RescuePartyObserver getInstanceIfCreated() { synchronized (RescuePartyObserver.class) { return sRescuePartyObserver; } } @VisibleForTesting static void reset() { synchronized (RescuePartyObserver.class) { Loading services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +134 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; Loading Loading @@ -44,6 +45,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.ArraySet; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; Loading @@ -63,6 +65,7 @@ import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; /** Loading @@ -79,9 +82,11 @@ public class RescuePartyTest { private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String CALLING_PACKAGE1 = "com.package.name1"; private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String CALLING_PACKAGE3 = "com.package.name3"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; private static final String NAMESPACE3 = "namespace3"; private static final String NAMESPACE4 = "namespace4"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = Loading @@ -89,6 +94,8 @@ public class RescuePartyTest { private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; //Records the namespaces wiped by setProperties(). private HashSet<String> mNamespacesWiped; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mMockContext; Loading Loading @@ -119,6 +126,7 @@ public class RescuePartyTest { .spyStatic(PackageWatchdog.class) .startMocking(); mSystemSettingsMap = new HashMap<>(); mNamespacesWiped = new HashSet<>(); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); // Reset observer instance to get new mock context on every run Loading Loading @@ -167,6 +175,16 @@ public class RescuePartyTest { anyBoolean())); doAnswer((Answer<Void>) invocationOnMock -> null) .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString())); doAnswer((Answer<Boolean>) invocationOnMock -> { DeviceConfig.Properties properties = invocationOnMock.getArgument(0); String namespace = properties.getNamespace(); // record a wipe if (properties.getKeyset().isEmpty()) { mNamespacesWiped.add(namespace); } return true; } ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class))); // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) Loading Loading @@ -450,6 +468,122 @@ public class RescuePartyTest { assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH); } @Test public void testResetDeviceConfigForPackagesOnlyRuntimeMap() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); doReturn("").when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigForPackagesOnlyPresetMap() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + "," + NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE3 + ":" + CALLING_PACKAGE1; doReturn(presetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigForPackagesBothMaps() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4)); String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + "," + NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE4 + ":" + CALLING_PACKAGE3; doReturn(presetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages( Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigNoExceptionWhenFlagMalformed() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4)); String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE1 + "." + CALLING_PACKAGE2; doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages( Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } private void verifySettingsResets(int resetMode, String[] resetNamespaces, HashMap<String, Integer> configResetVerifiedTimesMap) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, Loading Loading
services/core/java/com/android/server/RescueParty.java +92 −2 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import static android.provider.DeviceConfig.Properties; import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo; import android.annotation.NonNull; Loading @@ -36,6 +38,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.text.TextUtils; import android.util.ArraySet; import android.util.ExceptionUtils; import android.util.Log; Loading Loading @@ -93,6 +96,12 @@ public class RescueParty { static final long DEFAULT_OBSERVING_DURATION_MS = TimeUnit.DAYS.toMillis(2); @VisibleForTesting static final int DEVICE_CONFIG_RESET_MODE = Settings.RESET_MODE_TRUSTED_DEFAULTS; // The DeviceConfig namespace containing all RescueParty switches. @VisibleForTesting static final String NAMESPACE_CONFIGURATION = "configuration"; @VisibleForTesting static final String NAMESPACE_TO_PACKAGE_MAPPING_FLAG = "namespace_to_package_mapping"; private static final String NAME = "rescue-party-observer"; Loading @@ -103,8 +112,6 @@ public class RescueParty { "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = "persist.device_config.configuration.disable_rescue_party_factory_reset"; // The DeviceConfig namespace containing all RescueParty switches. private static final String NAMESPACE_CONFIGURATION = "configuration"; private static final int PERSISTENT_MASK = ApplicationInfo.FLAG_PERSISTENT | ApplicationInfo.FLAG_SYSTEM; Loading Loading @@ -170,6 +177,81 @@ public class RescueParty { })); } /** * Called when {@code RollbackManager} performs Mainline module rollbacks, * to avoid rolled back modules consuming flag values only expected to work * on modules of newer versions. */ public static void resetDeviceConfigForPackages(List<String> packageNames) { if (packageNames == null) { return; } Set<String> namespacesToReset = new ArraySet<String>(); Iterator<String> it = packageNames.iterator(); RescuePartyObserver rescuePartyObserver = RescuePartyObserver.getInstanceIfCreated(); // Get runtime package to namespace mapping if created. if (rescuePartyObserver != null) { while (it.hasNext()) { String packageName = it.next(); Set<String> runtimeAffectedNamespaces = rescuePartyObserver.getAffectedNamespaceSet(packageName); if (runtimeAffectedNamespaces != null) { namespacesToReset.addAll(runtimeAffectedNamespaces); } } } // Get preset package to namespace mapping if created. Set<String> presetAffectedNamespaces = getPresetNamespacesForPackages( packageNames); if (presetAffectedNamespaces != null) { namespacesToReset.addAll(presetAffectedNamespaces); } // Clear flags under the namespaces mapped to these packages. // Using setProperties since DeviceConfig.resetToDefaults bans the current flag set. Iterator<String> namespaceIt = namespacesToReset.iterator(); while (namespaceIt.hasNext()) { String namespaceToReset = namespaceIt.next(); Properties properties = new Properties.Builder(namespaceToReset).build(); try { DeviceConfig.setProperties(properties); } catch (DeviceConfig.BadConfigException exception) { logCriticalInfo(Log.WARN, "namespace " + namespaceToReset + " is already banned, skip reset."); } } } private static Set<String> getPresetNamespacesForPackages(List<String> packageNames) { Set<String> resultSet = new ArraySet<String>(); try { String flagVal = DeviceConfig.getString(NAMESPACE_CONFIGURATION, NAMESPACE_TO_PACKAGE_MAPPING_FLAG, ""); String[] mappingEntries = flagVal.split(","); for (int i = 0; i < mappingEntries.length; i++) { if (TextUtils.isEmpty(mappingEntries[i])) { continue; } String[] splittedEntry = mappingEntries[i].split(":"); if (splittedEntry.length != 2) { throw new RuntimeException("Invalid mapping entry: " + mappingEntries[i]); } String namespace = splittedEntry[0]; String packageName = splittedEntry[1]; if (packageNames.contains(packageName)) { resultSet.add(namespace); } } } catch (Exception e) { resultSet.clear(); Slog.e(TAG, "Failed to read preset package to namespaces mapping.", e); } finally { return resultSet; } } @VisibleForTesting static long getElapsedRealtime() { return SystemClock.elapsedRealtime(); Loading Loading @@ -469,6 +551,14 @@ public class RescueParty { } } /** Gets singleton instance. It returns null if the instance is not created yet.*/ @Nullable public static RescuePartyObserver getInstanceIfCreated() { synchronized (RescuePartyObserver.class) { return sRescuePartyObserver; } } @VisibleForTesting static void reset() { synchronized (RescuePartyObserver.class) { Loading
services/tests/mockingservicestests/src/com/android/server/RescuePartyTest.java +134 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.server; import static com.android.dx.mockito.inline.extended.ExtendedMockito.any; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyBoolean; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyInt; import static com.android.dx.mockito.inline.extended.ExtendedMockito.anyLong; Loading Loading @@ -44,6 +45,7 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.provider.DeviceConfig; import android.provider.Settings; import android.util.ArraySet; import com.android.dx.mockito.inline.extended.ExtendedMockito; import com.android.server.PackageWatchdog.PackageHealthObserverImpact; Loading @@ -63,6 +65,7 @@ import org.mockito.stubbing.Answer; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.List; /** Loading @@ -79,9 +82,11 @@ public class RescuePartyTest { private static final String PROP_DISABLE_RESCUE = "persist.sys.disable_rescue"; private static final String CALLING_PACKAGE1 = "com.package.name1"; private static final String CALLING_PACKAGE2 = "com.package.name2"; private static final String CALLING_PACKAGE3 = "com.package.name3"; private static final String NAMESPACE1 = "namespace1"; private static final String NAMESPACE2 = "namespace2"; private static final String NAMESPACE3 = "namespace3"; private static final String NAMESPACE4 = "namespace4"; private static final String PROP_DEVICE_CONFIG_DISABLE_FLAG = "persist.device_config.configuration.disable_rescue_party"; private static final String PROP_DISABLE_FACTORY_RESET_FLAG = Loading @@ -89,6 +94,8 @@ public class RescuePartyTest { private MockitoSession mSession; private HashMap<String, String> mSystemSettingsMap; //Records the namespaces wiped by setProperties(). private HashSet<String> mNamespacesWiped; @Mock(answer = Answers.RETURNS_DEEP_STUBS) private Context mMockContext; Loading Loading @@ -119,6 +126,7 @@ public class RescuePartyTest { .spyStatic(PackageWatchdog.class) .startMocking(); mSystemSettingsMap = new HashMap<>(); mNamespacesWiped = new HashSet<>(); when(mMockContext.getContentResolver()).thenReturn(mMockContentResolver); // Reset observer instance to get new mock context on every run Loading Loading @@ -167,6 +175,16 @@ public class RescuePartyTest { anyBoolean())); doAnswer((Answer<Void>) invocationOnMock -> null) .when(() -> DeviceConfig.resetToDefaults(anyInt(), anyString())); doAnswer((Answer<Boolean>) invocationOnMock -> { DeviceConfig.Properties properties = invocationOnMock.getArgument(0); String namespace = properties.getNamespace(); // record a wipe if (properties.getKeyset().isEmpty()) { mNamespacesWiped.add(namespace); } return true; } ).when(() -> DeviceConfig.setProperties(any(DeviceConfig.Properties.class))); // Mock PackageWatchdog doAnswer((Answer<PackageWatchdog>) invocationOnMock -> mMockPackageWatchdog) Loading Loading @@ -450,6 +468,122 @@ public class RescuePartyTest { assertEquals(observer.onBootLoop(5), PackageHealthObserverImpact.USER_IMPACT_HIGH); } @Test public void testResetDeviceConfigForPackagesOnlyRuntimeMap() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); doReturn("").when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigForPackagesOnlyPresetMap() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + "," + NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE3 + ":" + CALLING_PACKAGE1; doReturn(presetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages(Arrays.asList(new String[]{CALLING_PACKAGE1})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigForPackagesBothMaps() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE2)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4)); String presetMapping = NAMESPACE1 + ":" + CALLING_PACKAGE1 + "," + NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE4 + ":" + CALLING_PACKAGE3; doReturn(presetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages( Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE2, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } @Test public void testResetDeviceConfigNoExceptionWhenFlagMalformed() { RescueParty.onSettingsProviderPublished(mMockContext); verify(() -> Settings.Config.registerMonitorCallback(eq(mMockContentResolver), mMonitorCallbackCaptor.capture())); // Record DeviceConfig accesses RescuePartyObserver observer = RescuePartyObserver.getInstance(mMockContext); RemoteCallback monitorCallback = mMonitorCallbackCaptor.getValue(); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE1, NAMESPACE1)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE2, NAMESPACE3)); monitorCallback.sendResult(getConfigAccessBundle(CALLING_PACKAGE3, NAMESPACE4)); // Fake DeviceConfig value changes monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE1)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE2)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE3)); monitorCallback.sendResult(getConfigNamespaceUpdateBundle(NAMESPACE4)); String invalidPresetMapping = NAMESPACE2 + ":" + CALLING_PACKAGE2 + "," + NAMESPACE1 + "." + CALLING_PACKAGE2; doReturn(invalidPresetMapping).when(() -> DeviceConfig.getString( eq(RescueParty.NAMESPACE_CONFIGURATION), eq(RescueParty.NAMESPACE_TO_PACKAGE_MAPPING_FLAG), eq(""))); RescueParty.resetDeviceConfigForPackages( Arrays.asList(new String[]{CALLING_PACKAGE1, CALLING_PACKAGE2})); ArraySet<String> expectedNamespacesWiped = new ArraySet<String>( Arrays.asList(new String[]{NAMESPACE1, NAMESPACE3})); assertEquals(mNamespacesWiped, expectedNamespacesWiped); } private void verifySettingsResets(int resetMode, String[] resetNamespaces, HashMap<String, Integer> configResetVerifiedTimesMap) { verify(() -> Settings.Global.resetToDefaultsAsUser(mMockContentResolver, null, Loading