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

Commit cf207c30 authored by Winson's avatar Winson
Browse files

Add pm command for getOwnersForDomain API

Allows adb shell to print the domain approval owners for a specific
domain, a set of domains, or the domains of a package.

Bug: 184954099

Test: manual, try variants on device

Change-Id: I5c95945df9cbf8295d48e9815d2ad067f8e417e9
parent 05f4f582
Loading
Loading
Loading
Loading
+68 −14
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.pm.verify.domain;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledSince;
import android.content.Intent;
@@ -33,6 +34,8 @@ import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;

import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

@@ -44,6 +47,12 @@ public class DomainVerificationCollector {

    private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024;

    private static final BiFunction<ArraySet<String>, String, Boolean> ARRAY_SET_COLLECTOR =
            (set, domain) -> {
                set.add(domain);
                return null;
            };

    @NonNull
    private final PlatformCompat mPlatformCompat;

@@ -105,27 +114,62 @@ public class DomainVerificationCollector {
        return collectDomains(pkg, true /* checkAutoVerify */, false /* valid */);
    }

    public boolean containsWebDomain(@NonNull AndroidPackage pkg, @NonNull String targetDomain) {
        return collectDomains(pkg, false /* checkAutoVerify */, true /* valid */, null,
                (BiFunction<Void, String, Boolean>) (unused, domain) -> {
                    if (Objects.equals(targetDomain, domain)) {
                        return true;
                    }
                    return null;
                }) != null;
    }

    public boolean containsAutoVerifyDomain(@NonNull AndroidPackage pkg,
            @NonNull String targetDomain) {
        return collectDomains(pkg, true /* checkAutoVerify */, true /* valid */, null,
                (BiFunction<Void, String, Boolean>) (unused, domain) -> {
                    if (Objects.equals(targetDomain, domain)) {
                        return true;
                    }
                    return null;
                }) != null;
    }

    @NonNull
    private ArraySet<String> collectDomains(@NonNull AndroidPackage pkg,
            boolean checkAutoVerify, boolean valid) {
        ArraySet<String> domains = new ArraySet<>();
        collectDomains(pkg, checkAutoVerify, valid, domains, ARRAY_SET_COLLECTOR);
        return domains;
    }

    @NonNull
    private <InitialValue, ReturnValue> ReturnValue collectDomains(@NonNull AndroidPackage pkg,
            boolean checkAutoVerify, boolean valid, @Nullable InitialValue initialValue,
            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
        boolean restrictDomains =
                DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS);

        if (restrictDomains) {
            return collectDomainsInternal(pkg, checkAutoVerify, valid);
            return collectDomainsInternal(pkg, checkAutoVerify, valid, initialValue,
                    domainCollector);
        } else {
            return collectDomainsLegacy(pkg, checkAutoVerify, valid);
            return collectDomainsLegacy(pkg, checkAutoVerify, valid, initialValue, domainCollector);
        }
    }

    /**
     * @see #RESTRICT_DOMAINS
     */
    private ArraySet<String> collectDomainsLegacy(@NonNull AndroidPackage pkg,
            boolean checkAutoVerify, boolean valid) {
    @Nullable
    private <InitialValue, ReturnValue> ReturnValue collectDomainsLegacy(
            @NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
            @Nullable InitialValue initialValue,
            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
        if (!checkAutoVerify) {
            // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2
            return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */);
            return collectDomainsInternal(pkg, false /* checkAutoVerify */, true /* valid */,
                    initialValue, domainCollector);
        }

        List<ParsedActivity> activities = pkg.getActivities();
@@ -148,11 +192,10 @@ public class DomainVerificationCollector {
            }

            if (!needsAutoVerify) {
                return new ArraySet<>();
                return null;
            }
        }

        ArraySet<String> domains = new ArraySet<>();
        int totalSize = 0;
        boolean underMaxSize = true;
        for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize;
@@ -169,22 +212,30 @@ public class DomainVerificationCollector {
                        if (isValidHost(host) == valid) {
                            totalSize += byteSizeOf(host);
                            underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
                            domains.add(host);
                            ReturnValue returnValue = domainCollector.apply(initialValue, host);
                            if (returnValue != null) {
                                return returnValue;
                            }
                        }
                    }
                }
            }
        }

        return domains;
        return null;
    }

    /**
     * @see #RESTRICT_DOMAINS
     * @param domainCollector Function to call with initialValue and a valid host. Should return
     *                        a non-null value if the function should return immediately
     *                        after the currently processed host.
     */
    private ArraySet<String> collectDomainsInternal(@NonNull AndroidPackage pkg,
            boolean checkAutoVerify, boolean valid) {
        ArraySet<String> domains = new ArraySet<>();
    @Nullable
    private <InitialValue, ReturnValue> ReturnValue collectDomainsInternal(
            @NonNull AndroidPackage pkg, boolean checkAutoVerify, boolean valid,
            @Nullable InitialValue initialValue,
            @NonNull BiFunction<InitialValue, String, ReturnValue> domainCollector) {
        int totalSize = 0;
        boolean underMaxSize = true;

@@ -226,13 +277,16 @@ public class DomainVerificationCollector {
                    if (isValidHost(host) == valid) {
                        totalSize += byteSizeOf(host);
                        underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE;
                        domains.add(host);
                        ReturnValue returnValue = domainCollector.apply(initialValue, host);
                        if (returnValue != null) {
                            return returnValue;
                        }
                    }
                }
            }
        }

        return domains;
        return null;
    }

    /**
+65 −0
Original line number Diff line number Diff line
@@ -37,6 +37,7 @@ import com.android.server.pm.verify.domain.models.DomainVerificationPkgState;
import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;

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

@SuppressWarnings("PointlessBooleanExpression")
@@ -100,6 +101,70 @@ public class DomainVerificationDebug {
        }
    }

    /**
     * @param userIdToApprovalLevelToOwners Mapping of user ID to approval level to domain owners.
     */
    public void printOwners(@NonNull IndentingPrintWriter writer, @NonNull String domain,
            SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners) {
        writer.println(domain + ":");
        writer.increaseIndent();

        if (userIdToApprovalLevelToOwners.size() == 0) {
            writer.println("none");
            writer.decreaseIndent();
            return;
        }

        int usersSize = userIdToApprovalLevelToOwners.size();
        for (int userIndex = 0; userIndex < usersSize; userIndex++) {
            int userId = userIdToApprovalLevelToOwners.keyAt(userIndex);
            SparseArray<List<String>> approvalLevelToOwners =
                    userIdToApprovalLevelToOwners.valueAt(userIndex);

            if (approvalLevelToOwners.size() == 0) {
                continue;
            }

            boolean printedUserHeader = false;
            int approvalsSize = approvalLevelToOwners.size();
            for (int approvalIndex = 0; approvalIndex < approvalsSize; approvalIndex++) {
                int approvalLevel = approvalLevelToOwners.keyAt(approvalIndex);
                if (approvalLevel < DomainVerificationManagerInternal.APPROVAL_LEVEL_UNVERIFIED) {
                    continue;
                }

                if (!printedUserHeader) {
                    writer.println("User " + userId + ":");
                    writer.increaseIndent();
                    printedUserHeader = true;
                }

                String approvalString =
                        DomainVerificationManagerInternal.approvalLevelToDebugString(approvalLevel);
                List<String> owners = approvalLevelToOwners.valueAt(approvalIndex);
                writer.println(approvalString + "[" + approvalLevel + "]" + ":");
                writer.increaseIndent();

                if (owners.size() == 0) {
                    writer.println("none");
                    writer.decreaseIndent();
                    continue;
                }

                int ownersSize = owners.size();
                for (int ownersIndex = 0; ownersIndex < ownersSize; ownersIndex++) {
                    writer.println(owners.get(ownersIndex));
                }
                writer.decreaseIndent();
            }

            if (printedUserHeader) {
                writer.decreaseIndent();
            }
        }
        writer.decreaseIndent();
    }

    boolean printState(@NonNull IndentingPrintWriter writer,
            @NonNull DomainVerificationPkgState pkgState, @NonNull AndroidPackage pkg,
            @NonNull ArrayMap<String, Integer> reusedMap, boolean wasHeaderPrinted) {
+55 −2
Original line number Diff line number Diff line
@@ -56,6 +56,28 @@ public interface DomainVerificationManagerInternal {

    UUID DISABLED_ID = new UUID(0, 0);

    /**
     * The app was not installed for the user.
     */
    int APPROVAL_LEVEL_NOT_INSTALLED = -4;

    /**
     * The app was not enabled for the user.
     */
    int APPROVAL_LEVEL_DISABLED = -3;

    /**
     * The app has not declared this domain in a valid web intent-filter in their manifest, and so
     * would never be able to be approved for this domain.
     */
    int APPROVAL_LEVEL_UNDECLARED = -2;

    /**
     * The app has declared this domain as a valid autoVerify domain, but it failed or has not
     * succeeded verification.
     */
    int APPROVAL_LEVEL_UNVERIFIED = -1;

    /**
     * The app has not been approved for this domain and should never be able to open it through
     * an implicit web intent.
@@ -117,10 +139,14 @@ public interface DomainVerificationManagerInternal {
     * by approval priority. A higher numerical value means the package should override all lower
     * values. This means that comparison using less/greater than IS valid.
     *
     * Negative values are possible, although not implemented, reserved if explicit disable of a
     * package for a domain needs to be tracked.
     * Negative values are possible, used for tracking specific reasons for why an app doesn't have
     * approval.
     */
    @IntDef({
            APPROVAL_LEVEL_NOT_INSTALLED,
            APPROVAL_LEVEL_DISABLED,
            APPROVAL_LEVEL_UNDECLARED,
            APPROVAL_LEVEL_UNVERIFIED,
            APPROVAL_LEVEL_NONE,
            APPROVAL_LEVEL_LEGACY_ASK,
            APPROVAL_LEVEL_LEGACY_ALWAYS,
@@ -131,6 +157,33 @@ public interface DomainVerificationManagerInternal {
    @interface ApprovalLevel {
    }

    static String approvalLevelToDebugString(@ApprovalLevel int level) {
        switch (level) {
            case APPROVAL_LEVEL_NOT_INSTALLED:
                return "NOT_INSTALLED";
            case APPROVAL_LEVEL_DISABLED:
                return "DISABLED";
            case APPROVAL_LEVEL_UNDECLARED:
                return "UNDECLARED";
            case APPROVAL_LEVEL_UNVERIFIED:
                return "UNVERIFIED";
            case APPROVAL_LEVEL_NONE:
                return "NONE";
            case APPROVAL_LEVEL_LEGACY_ASK:
                return "LEGACY_ASK";
            case APPROVAL_LEVEL_LEGACY_ALWAYS:
                return "LEGACY_ALWAYS";
            case APPROVAL_LEVEL_SELECTION:
                return "USER_SELECTION";
            case APPROVAL_LEVEL_VERIFIED:
                return "VERIFIED";
            case APPROVAL_LEVEL_INSTANT_APP:
                return "INSTANT_APP";
            default:
                return "UNKNOWN";
        }
    }

    /** @see DomainVerificationManager#getDomainVerificationInfo(String) */
    @Nullable
    @RequiresPermission(anyOf = {
+194 −53
Original line number Diff line number Diff line
@@ -741,26 +741,60 @@ public class DomainVerificationService extends SystemService
        });
    }

    @NonNull
    public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
        Objects.requireNonNull(domain);
        mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
                userId);

        SparseArray<List<String>> levelToPackages = new SparseArray<>();
        return mConnection.withPackageSettingsReturningThrowing(pkgSettings -> {
            SparseArray<List<String>> levelToPackages = getOwnersForDomainInternal(domain, false,
                    userId, pkgSettings);
            if (levelToPackages.size() == 0) {
                return emptyList();
            }

            List<DomainOwner> owners = new ArrayList<>();
            int size = levelToPackages.size();
            for (int index = 0; index < size; index++) {
                int level = levelToPackages.keyAt(index);
                boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
                List<String> packages = levelToPackages.valueAt(index);
                int packagesSize = packages.size();
                for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
                    owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
                }
            }

            return owners;
        });
    }

    /**
     * @param includeNegative See {@link #approvalLevelForDomain(PackageSetting, String, boolean,
     *                        int, Object)}.
     * @return Mapping of approval level to packages; packages are sorted by firstInstallTime. Null
     * if no owners were found.
     */
    @NonNull
    private SparseArray<List<String>> getOwnersForDomainInternal(@NonNull String domain,
            boolean includeNegative, @UserIdInt int userId,
            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
        SparseArray<List<String>> levelToPackages = new SparseArray<>();
        // First, collect the raw approval level values
        synchronized (mLock) {
            final int size = mAttachedPkgStates.size();
            for (int index = 0; index < size; index++) {
                DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
                String packageName = pkgState.getPackageName();
                    PackageSetting pkgSetting = pkgSettings.apply(packageName);
                PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
                if (pkgSetting == null) {
                    continue;
                }

                    int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
                    if (level <= APPROVAL_LEVEL_NONE) {
                int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
                        domain);
                if (!includeNegative && level <= APPROVAL_LEVEL_NONE) {
                    continue;
                }
                List<String> list = levelToPackages.get(level);
@@ -774,14 +808,14 @@ public class DomainVerificationService extends SystemService

        final int size = levelToPackages.size();
        if (size == 0) {
                return emptyList();
            return levelToPackages;
        }

        // Then sort them ascending by first installed time, with package name as tie breaker
        for (int index = 0; index < size; index++) {
            levelToPackages.valueAt(index).sort((first, second) -> {
                    PackageSetting firstPkgSetting = pkgSettings.apply(first);
                    PackageSetting secondPkgSetting = pkgSettings.apply(second);
                PackageSetting firstPkgSetting = pkgSettingFunction.apply(first);
                PackageSetting secondPkgSetting = pkgSettingFunction.apply(second);

                long firstInstallTime =
                        firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
@@ -796,19 +830,7 @@ public class DomainVerificationService extends SystemService
            });
        }

            List<DomainOwner> owners = new ArrayList<>();
            for (int index = 0; index < size; index++) {
                int level = levelToPackages.keyAt(index);
                boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
                List<String> packages = levelToPackages.valueAt(index);
                int packagesSize = packages.size();
                for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
                    owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
                }
            }

            return owners;
        });
        return levelToPackages;
    }

    @NonNull
@@ -1157,6 +1179,88 @@ public class DomainVerificationService extends SystemService
        }
    }

    @Override
    public void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
            @Nullable String packageName, @Nullable @UserIdInt Integer userId)
            throws NameNotFoundException {
        mConnection.withPackageSettingsThrowing(pkgSettings -> {
            synchronized (mLock) {
                if (packageName == null) {
                    int size = mAttachedPkgStates.size();
                    for (int index = 0; index < size; index++) {
                        try {
                            printOwnersForPackage(writer,
                                    mAttachedPkgStates.valueAt(index).getPackageName(), userId,
                                    pkgSettings);
                        } catch (NameNotFoundException ignored) {
                            // When iterating packages, if one doesn't exist somehow, ignore
                        }
                    }
                } else {
                    printOwnersForPackage(writer, packageName, userId, pkgSettings);
                }
            }
        });
    }

    private void printOwnersForPackage(@NonNull IndentingPrintWriter writer,
            @NonNull String packageName, @Nullable @UserIdInt Integer userId,
            @NonNull Function<String, PackageSetting> pkgSettingFunction)
            throws NameNotFoundException {
        PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
        AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
        if (pkg == null) {
            throw DomainVerificationUtils.throwPackageUnavailable(packageName);
        }

        ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
        int size = domains.size();
        if (size == 0) {
            return;
        }

        writer.println(packageName + ":");
        writer.increaseIndent();

        for (int index = 0; index < size; index++) {
            printOwnersForDomain(writer, domains.valueAt(index), userId, pkgSettingFunction);
        }

        writer.decreaseIndent();
    }

    @Override
    public void printOwnersForDomains(@NonNull IndentingPrintWriter writer,
            @NonNull List<String> domains, @Nullable @UserIdInt Integer userId) {
        mConnection.withPackageSettings(pkgSettings -> {
            synchronized (mLock) {
                int size = domains.size();
                for (int index = 0; index < size; index++) {
                    printOwnersForDomain(writer, domains.get(index), userId, pkgSettings);
                }
            }
        });
    }

    private void printOwnersForDomain(@NonNull IndentingPrintWriter writer, @NonNull String domain,
            @Nullable @UserIdInt Integer userId,
            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
        SparseArray<SparseArray<List<String>>> userIdToApprovalLevelToOwners =
                new SparseArray<>();

        if (userId == null || userId == UserHandle.USER_ALL) {
            for (int aUserId : mConnection.getAllUserIds()) {
                userIdToApprovalLevelToOwners.put(aUserId,
                        getOwnersForDomainInternal(domain, true, aUserId, pkgSettingFunction));
            }
        } else {
            userIdToApprovalLevelToOwners.put(userId,
                    getOwnersForDomainInternal(domain, true, userId, pkgSettingFunction));
        }

        mDebug.printOwners(writer, domain, userIdToApprovalLevelToOwners);
    }

    @NonNull
    @Override
    public DomainVerificationShell getShell() {
@@ -1431,7 +1535,7 @@ public class DomainVerificationService extends SystemService
        // Find all approval levels
        int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
                pkgSettingFunction);
        if (highestApproval == APPROVAL_LEVEL_NONE) {
        if (highestApproval <= APPROVAL_LEVEL_NONE) {
            return Pair.create(emptyList(), highestApproval);
        }

@@ -1488,7 +1592,7 @@ public class DomainVerificationService extends SystemService
                fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
                continue;
            }
            int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
            int approval = approvalLevelForDomain(pkgSetting, domain, false, userId, domain);
            highestApproval = Math.max(highestApproval, approval);
            fillInfoMapForSamePackage(inputMap, packageName, approval);
        }
@@ -1604,18 +1708,53 @@ public class DomainVerificationService extends SystemService
            return APPROVAL_LEVEL_NONE;
        }

        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
        return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), false, userId,
                intent);
    }

    /**
     * @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
     *                    otherwise.
     * @param includeNegative Whether to include negative values, which requires an expensive
     *                          domain comparison operation.
     * @param debugObject       Should be an {@link Intent} if checking for resolution or a
     *                          {@link String} otherwise.
     */
    private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
            @UserIdInt int userId, @NonNull Object debugObject) {
            boolean includeNegative, @UserIdInt int userId, @NonNull Object debugObject) {
        int approvalLevel = approvalLevelForDomainInternal(pkgSetting, host, includeNegative,
                userId, debugObject);
        if (includeNegative && approvalLevel == APPROVAL_LEVEL_NONE) {
            PackageUserState pkgUserState = pkgSetting.readUserState(userId);
            if (!pkgUserState.installed) {
                return APPROVAL_LEVEL_NOT_INSTALLED;
            }

            AndroidPackage pkg = pkgSetting.getPkg();
            if (pkg != null) {
                if (!pkgUserState.isPackageEnabled(pkg)) {
                    return APPROVAL_LEVEL_DISABLED;
                } else if (mCollector.containsAutoVerifyDomain(pkgSetting.getPkg(), host)) {
                    return APPROVAL_LEVEL_UNVERIFIED;
                }
            }
        }

        return approvalLevel;
    }

    private int approvalLevelForDomainInternal(@NonNull PackageSetting pkgSetting,
            @NonNull String host, boolean includeNegative, @UserIdInt int userId,
            @NonNull Object debugObject) {
        String packageName = pkgSetting.getName();
        final AndroidPackage pkg = pkgSetting.getPkg();

        if (pkg != null && includeNegative && !mCollector.containsWebDomain(pkg, host)) {
            if (DEBUG_APPROVAL) {
                debugApproval(packageName, debugObject, userId, false,
                        "domain not declared");
            }
            return APPROVAL_LEVEL_UNDECLARED;
        }

        final PackageUserState pkgUserState = pkgSetting.readUserState(userId);
        if (pkgUserState == null) {
            if (DEBUG_APPROVAL) {
@@ -1753,6 +1892,7 @@ public class DomainVerificationService extends SystemService
    private Pair<List<String>, Integer> getApprovedPackagesLocked(@NonNull String domain,
            @UserIdInt int userId, int minimumApproval,
            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
        boolean includeNegative = minimumApproval < APPROVAL_LEVEL_NONE;
        int highestApproval = minimumApproval;
        List<String> approvedPackages = emptyList();

@@ -1766,7 +1906,8 @@ public class DomainVerificationService extends SystemService
                    continue;
                }

                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
                int level = approvalLevelForDomain(pkgSetting, domain, includeNegative, userId,
                        domain);
                if (level < minimumApproval) {
                    continue;
                }
+83 −0

File changed.

Preview size limit exceeded, changes collapsed.