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

Commit 4eb5ec4a authored by Hongyi Zhang's avatar Hongyi Zhang Committed by Android (Google) Code Review
Browse files

Merge "Add a method for wiping flags per package in RescueParty"

parents 9b53f765 89dfb988
Loading
Loading
Loading
Loading
+92 −2
Original line number Diff line number Diff line
@@ -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) {
+134 −0
Original line number Diff line number Diff line
@@ -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,