Add a method for wiping flags per package in RescueParty
Provide a method for RollbackManager to clear flags when performing rollback. Mapping of packages to namespaces comes from both the runtime records already maintained by RescueParty and a preset DeviceConfig flag of namespace <-> package for Mainline modules(will be pushed from GMScore). Test: atest RescuePartyTest & manually trigger the action on local build Bug: 173120414 Change-Id: I4773a6124b07e3291cacf71dc7b702f84b69008c
This commit is contained in:
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
@@ -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();
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -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 =
|
||||
@@ -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;
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user