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

Commit 3532c6ac authored by Pavel Grafov's avatar Pavel Grafov
Browse files

Store admin suspended packages in ActiveAdmin

Bug: 258823777
Test: atest PolicyVersionUpgraderTest
Change-Id: Ia41884124418453d63490f5fea637dbb7dada4ac
parent a2063a9d
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -162,6 +162,7 @@ class ActiveAdmin {
    private static final String TAG_PREFERENTIAL_NETWORK_SERVICE_CONFIG =
            "preferential_network_service_config";
    private static final String TAG_PROTECTED_PACKAGES = "protected_packages";
    private static final String TAG_SUSPENDED_PACKAGES = "suspended-packages";
    private static final String ATTR_VALUE = "value";
    private static final String ATTR_LAST_NETWORK_LOGGING_NOTIFICATION = "last-notification";
    private static final String ATTR_NUM_NETWORK_LOGGING_NOTIFICATIONS = "num-notifications";
@@ -257,6 +258,8 @@ class ActiveAdmin {
    // List of packages for which the user cannot invoke "clear data" or "force stop".
    List<String> protectedPackages;

    List<String> suspendedPackages;

    // Wi-Fi SSID restriction policy.
    WifiSsidPolicy mWifiSsidPolicy;

@@ -508,6 +511,7 @@ class ActiveAdmin {
        writePackageListToXml(out, TAG_KEEP_UNINSTALLED_PACKAGES, keepUninstalledPackages);
        writePackageListToXml(out, TAG_METERED_DATA_DISABLED_PACKAGES, meteredDisabledPackages);
        writePackageListToXml(out, TAG_PROTECTED_PACKAGES, protectedPackages);
        writePackageListToXml(out, TAG_SUSPENDED_PACKAGES, suspendedPackages);
        if (hasUserRestrictions()) {
            UserRestrictionsUtils.writeRestrictions(
                    out, userRestrictions, TAG_USER_RESTRICTIONS);
@@ -776,6 +780,8 @@ class ActiveAdmin {
                meteredDisabledPackages = readPackageList(parser, tag);
            } else if (TAG_PROTECTED_PACKAGES.equals(tag)) {
                protectedPackages = readPackageList(parser, tag);
            } else if (TAG_SUSPENDED_PACKAGES.equals(tag)) {
                suspendedPackages = readPackageList(parser, tag);
            } else if (TAG_USER_RESTRICTIONS.equals(tag)) {
                userRestrictions = UserRestrictionsUtils.readRestrictions(parser);
            } else if (TAG_DEFAULT_ENABLED_USER_RESTRICTIONS.equals(tag)) {
@@ -1225,6 +1231,11 @@ class ActiveAdmin {
            pw.println(protectedPackages);
        }

        if (suspendedPackages != null) {
            pw.print("suspendedPackages=");
            pw.println(suspendedPackages);
        }

        pw.print("organizationColor=");
        pw.println(organizationColor);

+44 −3
Original line number Diff line number Diff line
@@ -546,7 +546,7 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
    // to decide whether an existing policy in the {@link #DEVICE_POLICIES_XML} needs to
    // be upgraded. See {@link PolicyVersionUpgrader} on instructions how to add an upgrade
    // step.
    static final int DPMS_VERSION = 3;
    static final int DPMS_VERSION = 4;
    static {
        SECURE_SETTINGS_ALLOWLIST = new ArraySet<>();
@@ -1498,8 +1498,11 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
        }
        PackageManager getPackageManager(int userId) {
            return mContext
                    .createContextAsUser(UserHandle.of(userId), 0 /* flags */).getPackageManager();
            try {
                return createContextAsUser(UserHandle.of(userId)).getPackageManager();
            } catch (NameNotFoundException e) {
                throw new IllegalStateException(e);
            }
        }
        PowerManagerInternal getPowerManagerInternal() {
@@ -3111,6 +3114,20 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            List<UserInfo> allUsers = mUserManager.getUsers();
            return allUsers.stream().mapToInt(u -> u.id).toArray();
        }
        @Override
        public List<String> getPlatformSuspendedPackages(int userId) {
            PackageManagerInternal pmi = mInjector.getPackageManagerInternal();
            return mInjector.getPackageManager(userId)
                    .getInstalledPackages(PackageManager.PackageInfoFlags.of(
                            MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE))
                    .stream()
                    .map(packageInfo -> packageInfo.packageName)
                    .filter(pkg ->
                            PLATFORM_PACKAGE_NAME.equals(pmi.getSuspendingPackage(pkg, userId))
                    )
                    .collect(Collectors.toList());
        }
    }
    private void performPolicyVersionUpgrade() {
@@ -11383,6 +11400,30 @@ public class DevicePolicyManagerService extends BaseIDevicePolicyManager {
            Slogf.w(LOG_TAG, "PM failed to suspend packages (%s)", Arrays.toString(packageNames));
            return packageNames;
        }
        ArraySet<String> changed = new ArraySet<>(packageNames);
        if (suspended) {
            // Only save those packages that are actually suspended. If a package is exempt or is
            // unsuspendable, it is skipped.
            changed.removeAll(List.of(nonSuspendedPackages));
        } else {
            // If an admin tries to unsuspend a package that is either exempt or is not
            // suspendable, drop it from the stored list assuming it must be already unsuspended.
            changed.addAll(exemptApps);
        }
        synchronized (getLockObject()) {
            ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
            ArraySet<String> current = new ArraySet<>(admin.suspendedPackages);
            if (suspended) {
                current.addAll(changed);
            } else {
                current.removeAll(changed);
            }
            admin.suspendedPackages = current.isEmpty() ? null : new ArrayList<>(current);
            saveSettingsLocked(caller.getUserId());
        }
        if (exemptApps.isEmpty()) {
            return nonSuspendedPackages;
        }
+6 −0
Original line number Diff line number Diff line
@@ -21,6 +21,7 @@ import android.content.ComponentName;

import com.android.internal.util.JournaledFile;

import java.util.List;
import java.util.function.Function;

/**
@@ -48,4 +49,9 @@ public interface PolicyUpgraderDataProvider {
     * Returns the users to upgrade.
     */
    int[] getUsersForUpgrade();

    /**
     * Returns packages suspended by platform for a given user.
     */
    List<String> getPlatformSuspendedPackages(int userId);
}
+44 −0
Original line number Diff line number Diff line
@@ -104,6 +104,12 @@ public class PolicyVersionUpgrader {
            currentVersion = 3;
        }

        if (currentVersion == 3) {
            Slog.i(LOG_TAG, String.format("Upgrading from version %d", currentVersion));
            upgradePackageSuspension(allUsers, ownersData, allUsersData);
            currentVersion = 4;
        }

        writePoliciesAndVersion(allUsers, allUsersData, ownersData, currentVersion);
    }

@@ -170,6 +176,44 @@ public class PolicyVersionUpgrader {
        }
    }

    /**
     * This upgrade step stores packages suspended via DPM.setPackagesSuspended() into ActiveAdmin
     * data structure. Prior to this it was only persisted in PackageManager which doesn't have any
     * way of knowing which admin suspended it.
     */
    private void upgradePackageSuspension(
            int[] allUsers, OwnersData ownersData, SparseArray<DevicePolicyData> allUsersData) {
        if (ownersData.mDeviceOwner != null) {
            saveSuspendedPackages(allUsersData, ownersData.mDeviceOwnerUserId,
                    ownersData.mDeviceOwner.admin);
        }

        for (int i = 0; i < ownersData.mProfileOwners.size(); i++) {
            int ownerUserId = ownersData.mProfileOwners.keyAt(i);
            OwnersData.OwnerInfo ownerInfo = ownersData.mProfileOwners.valueAt(i);
            saveSuspendedPackages(allUsersData, ownerUserId, ownerInfo.admin);
        }
    }

    private void saveSuspendedPackages(SparseArray<DevicePolicyData> allUsersData, int ownerUserId,
            ComponentName ownerPackage) {
        DevicePolicyData ownerUserData = allUsersData.get(ownerUserId);
        if (ownerUserData == null) {
            Slog.e(LOG_TAG, "No policy data for owner user, cannot migrate suspended packages");
            return;
        }

        ActiveAdmin ownerAdmin = ownerUserData.mAdminMap.get(ownerPackage);
        if (ownerAdmin == null) {
            Slog.e(LOG_TAG, "No admin for owner, cannot migrate suspended packages");
            return;
        }

        ownerAdmin.suspendedPackages = mProvider.getPlatformSuspendedPackages(ownerUserId);
        Slog.i(LOG_TAG, String.format("Saved %d packages suspended by %s in user %d",
                ownerAdmin.suspendedPackages.size(), ownerPackage, ownerUserId));
    }

    private OwnersData loadOwners(int[] allUsers) {
        OwnersData ownersData = new OwnersData(mPathProvider);
        ownersData.load(allUsers);
+81 −1
Original line number Diff line number Diff line
@@ -33,6 +33,7 @@ import android.content.pm.ApplicationInfo;
import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.UserHandle;
import android.util.ArraySet;
import android.util.Xml;

import androidx.test.InstrumentationRegistry;
@@ -47,29 +48,42 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;

import javax.xml.parsers.DocumentBuilderFactory;

@RunWith(JUnit4.class)
public class PolicyVersionUpgraderTest extends DpmTestBase {
    // NOTE: Only change this value if the corresponding CL also adds a test to test the upgrade
    // to the new version.
    private static final int LATEST_TESTED_VERSION = 3;
    private static final int LATEST_TESTED_VERSION = 4;
    public static final String PERMISSIONS_TAG = "admin-can-grant-sensors-permissions";
    public static final String DEVICE_OWNER_XML = "device_owner_2.xml";
    private ComponentName mFakeAdmin;

    private class FakePolicyUpgraderDataProvider implements PolicyUpgraderDataProvider {
        Map<ComponentName, DeviceAdminInfo> mComponentToDeviceAdminInfo = new HashMap<>();
        ArrayList<String> mPlatformSuspendedPackages = new ArrayList<>();
        int[] mUsers;

        private JournaledFile makeJournaledFile(int userId, String fileName) {
@@ -98,6 +112,11 @@ public class PolicyVersionUpgraderTest extends DpmTestBase {
        public int[] getUsersForUpgrade() {
            return mUsers;
        }

        @Override
        public List<String> getPlatformSuspendedPackages(int userId) {
            return mPlatformSuspendedPackages;
        }
    }

    private final Context mRealTestContext = InstrumentationRegistry.getTargetContext();
@@ -256,11 +275,72 @@ public class PolicyVersionUpgraderTest extends DpmTestBase {
        assertThat(isTagPresent(readPoliciesFileToStream(ownerUser), newTag)).isTrue();
    }

    @Test
    public void testAdminPackageSuspensionSaved() throws Exception {
        final int ownerUser = 0;
        mProvider.mUsers = new int[]{ownerUser};
        getServices().addUser(ownerUser, FLAG_PRIMARY, USER_TYPE_FULL_SYSTEM);
        setUpPackageManagerForAdmin(admin1, UserHandle.getUid(ownerUser, 123 /* admin app ID */));
        writeVersionToXml(3);
        preparePoliciesFile(ownerUser, "device_policies.xml");
        prepareDeviceOwnerFile(ownerUser, "device_owner_2.xml");

        // Pretend package manager thinks these packages are suspended by the platform.
        Set<String> suspendedPkgs = Set.of("com.some.app", "foo.bar.baz");
        mProvider.mPlatformSuspendedPackages.addAll(suspendedPkgs);

        mUpgrader.upgradePolicy(4);

        assertThat(readVersionFromXml()).isAtLeast(4);

        assertAdminSuspendedPackages(ownerUser, suspendedPkgs);
    }

    private void assertAdminSuspendedPackages(int ownerUser, Set<String> suspendedPkgs)
            throws Exception {
        Document policies = readPolicies(ownerUser);
        Element adminElem =
                (Element) policies.getDocumentElement().getElementsByTagName("admin").item(0);
        Element suspendedElem =
                (Element) adminElem.getElementsByTagName("suspended-packages").item(0);
        NodeList pkgsNodes = suspendedElem.getElementsByTagName("item");
        Set<String> storedSuspendedPkgs = new ArraySet<>();
        for (int i = 0; i < pkgsNodes.getLength(); i++) {
            Element item = (Element) pkgsNodes.item(i);
            storedSuspendedPkgs.add(item.getAttribute("value"));
        }
        assertThat(storedSuspendedPkgs).isEqualTo(suspendedPkgs);
    }

    @Test
    public void isLatestVersionTested() {
        assertThat(DevicePolicyManagerService.DPMS_VERSION).isEqualTo(LATEST_TESTED_VERSION);
    }

    /**
     * Reads ABX binary XML, converts it to text, and returns as an input stream.
     */
    private InputStream abxToXmlStream(File file) throws Exception {
        FileInputStream fileIn = new FileInputStream(file);
        XmlPullParser in = Xml.newBinaryPullParser();
        in.setInput(fileIn, StandardCharsets.UTF_8.name());

        ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
        XmlSerializer out = Xml.newSerializer();
        out.setOutput(byteOut, StandardCharsets.UTF_8.name());

        Xml.copy(in, out);
        out.flush();

        return new ByteArrayInputStream(byteOut.toByteArray());
    }

    private Document readPolicies(int userId) throws Exception {
        File policiesFile = mProvider.makeDevicePoliciesJournaledFile(userId).chooseForRead();
        InputStream is = abxToXmlStream(policiesFile);
        return DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(is);
    }

    private void writeVersionToXml(int dpmsVersion) throws IOException {
        JournaledFile versionFile = mProvider.makePoliciesVersionJournaledFile(0);
        Files.asCharSink(versionFile.chooseForWrite(), Charset.defaultCharset()).write(