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

Commit ebf59827 authored by Winson's avatar Winson
Browse files

Update isLinkHandlingAllowed to mean all or nothing

This toggle is actually used to toggle whether or not the app can open
app links at all, not just verified app links.

Updates the persistence/data classes to be in line with the new default
behavior.

Also makes it so that unverified apps cannot be enabled if another
approved package already exists.

Bug: 178525735

Test: atest DomainVerificationPersistenceTest

Change-Id: I5e258e230e6d6b5de79aab32838496f2126f8451
parent 60bd6855
Loading
Loading
Loading
Loading
+7 −3
Original line number Diff line number Diff line
@@ -238,8 +238,8 @@ public interface DomainVerificationManager {
     * permissions must be acquired and
     * {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
     *
     * This will be combined with the verification status and other system state to determine which
     * application is launched to handle an app link.
     * Enabling an unverified domain will allow an application to open it, but this can only occur
     * if no other app on the device is approved for the domain.
     *
     * @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
     * @param domains     The domains to toggle the state of.
@@ -290,13 +290,15 @@ public interface DomainVerificationManager {
        public static final int REASON_ID_INVALID = 2;
        public static final int REASON_SET_NULL_OR_EMPTY = 3;
        public static final int REASON_UNKNOWN_DOMAIN = 4;
        public static final int REASON_UNABLE_TO_APPROVE = 5;

        /** @hide */
        @IntDef({
                REASON_ID_NULL,
                REASON_ID_INVALID,
                REASON_SET_NULL_OR_EMPTY,
                REASON_UNKNOWN_DOMAIN
                REASON_UNKNOWN_DOMAIN,
                REASON_UNABLE_TO_APPROVE
        })
        public @interface Reason {
        }
@@ -313,6 +315,8 @@ public interface DomainVerificationManager {
                case REASON_UNKNOWN_DOMAIN:
                    return "Domain set contains value that was not declared by the target package "
                            + packageName;
                case REASON_UNABLE_TO_APPROVE:
                    return "Domain set contains value that was owned by another package";
                default:
                    return "Unknown failure";
            }
+15 −13
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package android.content.pm.verify.domain;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.content.Context;
import android.os.Parcelable;
@@ -30,18 +31,18 @@ import java.util.Set;
import java.util.UUID;

/**
 * Contains the user selection state for a package. This means all web HTTP(S) domains
 * declared by a package in its manifest, whether or not they were marked for auto
 * verification.
 * Contains the user selection state for a package. This means all web HTTP(S) domains declared by a
 * package in its manifest, whether or not they were marked for auto verification.
 * <p>
 * By default, all apps are allowed to automatically open links with domains that they've
 * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}.
 * The user can decide to disable this, disallowing the application from opening these
 * links.
 * successfully verified against. This is reflected by {@link #isLinkHandlingAllowed()}. The user
 * can decide to disable this, disallowing the application from opening all links. Note that the
 * toggle affects <b>all</b> links and is not based on the verification state of the domains.
 * <p>
 * Separately, independent of this toggle, the user can choose specific domains to allow
 * an app to open, which is reflected as part of {@link #getHostToUserSelectionMap()},
 * which maps the domain name to the true/false state of whether it was enabled by the user.
 * Assuming the toggle is enabled, the user can also select additional unverified domains to grant
 * to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
 * a single application can be approved for a domain unless the applications are both approved. If
 * another application is approved, the user will not be allowed to enable the domain.
 * <p>
 * These values can be changed through the
 * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
@@ -105,7 +106,8 @@ public final class DomainVerificationUserSelection implements Parcelable {
    // CHECKSTYLE:OFF Generated code
    //
    // To regenerate run:
    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java
    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
    // /DomainVerificationUserSelection.java
    //
    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
    //   Settings > Editor > Code Style > Formatter Control
@@ -216,7 +218,7 @@ public final class DomainVerificationUserSelection implements Parcelable {

    @Override
    @DataClass.Generated.Member
    public boolean equals(@android.annotation.Nullable Object o) {
    public boolean equals(@Nullable Object o) {
        // You can override field equality logic by defining either of the methods like:
        // boolean fieldNameEquals(DomainVerificationUserSelection other) { ... }
        // boolean fieldNameEquals(FieldType otherValue) { ... }
@@ -328,9 +330,9 @@ public final class DomainVerificationUserSelection implements Parcelable {
    };

    @DataClass.Generated(
            time = 1611799495498L,
            time = 1612829797220L,
            codegenVersion = "1.0.22",
            sourceFile = "frameworks/base/core/java/android/content/pm/domain/verify/DomainVerificationUserSelection.java",
            sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
            inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
    @Deprecated
    private void __metadata() {}
+1 −4
Original line number Diff line number Diff line
@@ -38,8 +38,6 @@ import com.android.server.pm.verify.domain.models.DomainVerificationStateMap;
import com.android.server.pm.verify.domain.models.DomainVerificationUserState;

import java.util.Arrays;
import java.util.Objects;
import java.util.Set;
import java.util.function.Function;

@SuppressWarnings("PointlessBooleanExpression")
@@ -202,8 +200,7 @@ public class DomainVerificationDebug {
                printedHeader = true;
            }

            boolean isLinkHandlingAllowed = userState == null
                    || !userState.isDisallowLinkHandling();
            boolean isLinkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();

            writer.increaseIndent();
            writer.print("User ");
+5 −5
Original line number Diff line number Diff line
@@ -53,7 +53,7 @@ public class DomainVerificationPersistence {

    public static final String TAG_USER_STATE = "user-state";
    public static final String ATTR_USER_ID = "userId";
    public static final String ATTR_DISALLOW_LINK_HANDLING = "disallowLinkHandling";
    public static final String ATTR_ALLOW_LINK_HANDLING = "allowLinkHandling";
    public static final String TAG_ENABLED_HOSTS = "enabled-hosts";
    public static final String TAG_HOST = "host";

@@ -252,7 +252,7 @@ public class DomainVerificationPersistence {
            return null;
        }

        boolean disallowLinkHandling = section.getBoolean(ATTR_DISALLOW_LINK_HANDLING);
        boolean allowLinkHandling = section.getBoolean(ATTR_ALLOW_LINK_HANDLING, true);
        ArraySet<String> enabledHosts = new ArraySet<>();

        SettingsXml.ChildSection child = section.children();
@@ -260,7 +260,7 @@ public class DomainVerificationPersistence {
            readEnabledHosts(child, enabledHosts);
        }

        return new DomainVerificationUserState(userId, enabledHosts, disallowLinkHandling);
        return new DomainVerificationUserState(userId, enabledHosts, allowLinkHandling);
    }

    private static void readEnabledHosts(@NonNull SettingsXml.ReadSection section,
@@ -279,8 +279,8 @@ public class DomainVerificationPersistence {
        try (SettingsXml.WriteSection section =
                     parentSection.startSection(TAG_USER_STATE)
                             .attribute(ATTR_USER_ID, userState.getUserId())
                             .attribute(ATTR_DISALLOW_LINK_HANDLING,
                                     userState.isDisallowLinkHandling())) {
                             .attribute(ATTR_ALLOW_LINK_HANDLING,
                                     userState.isLinkHandlingAllowed())) {
            ArraySet<String> enabledHosts = userState.getEnabledHosts();
            if (!enabledHosts.isEmpty()) {
                try (SettingsXml.WriteSection enabledHostsSection =
+105 −31
Original line number Diff line number Diff line
@@ -407,7 +407,7 @@ public class DomainVerificationService extends SystemService
            }

            pkgState.getOrCreateUserSelectionState(userId)
                    .setDisallowLinkHandling(!allowed);
                    .setLinkHandlingAllowed(allowed);
        }

        mConnection.scheduleWriteSettings();
@@ -429,11 +429,11 @@ public class DomainVerificationService extends SystemService
                        for (int userStateIndex = 0; userStateIndex < userStatesSize;
                                userStateIndex++) {
                            userStates.valueAt(userStateIndex)
                                    .setDisallowLinkHandling(!allowed);
                                    .setLinkHandlingAllowed(allowed);
                        }
                    } else {
                        pkgState.getOrCreateUserSelectionState(userId)
                                .setDisallowLinkHandling(!allowed);
                                .setLinkHandlingAllowed(allowed);
                    }
                }

@@ -446,7 +446,7 @@ public class DomainVerificationService extends SystemService
                }

                pkgState.getOrCreateUserSelectionState(userId)
                        .setDisallowLinkHandling(!allowed);
                        .setLinkHandlingAllowed(allowed);
            }
        }

@@ -474,6 +474,17 @@ public class DomainVerificationService extends SystemService
                throw new InvalidDomainSetException(domainSetId, null,
                        InvalidDomainSetException.REASON_ID_INVALID);
            }

            if (enabled) {
                for (String domain : domains) {
                    if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
                            mConnection::getPackageSettingLocked).first.isEmpty()) {
                        throw new InvalidDomainSetException(domainSetId, null,
                                InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
                    }
                }
            }

            DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
                    false /* forAutoVerify */, callingUid, userId);
            DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
@@ -595,10 +606,10 @@ public class DomainVerificationService extends SystemService
                hostToUserSelectionMap.put(domains.valueAt(index), false);
            }

            boolean openVerifiedLinks = false;
            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
            boolean linkHandlingAllowed = true;
            if (userState != null) {
                openVerifiedLinks = !userState.isDisallowLinkHandling();
                linkHandlingAllowed = userState.isLinkHandlingAllowed();
                ArraySet<String> enabledHosts = userState.getEnabledHosts();
                int hostsSize = enabledHosts.size();
                for (int index = 0; index < hostsSize; index++) {
@@ -607,7 +618,7 @@ public class DomainVerificationService extends SystemService
            }

            return new DomainVerificationUserSelection(pkgState.getId(), packageName,
                    UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
                    UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
        }
    }

@@ -689,7 +700,7 @@ public class DomainVerificationService extends SystemService
                    ArraySet<String> newEnabledHosts = new ArraySet<>(oldEnabledHosts);
                    newEnabledHosts.retainAll(newWebDomains);
                    DomainVerificationUserState newUserState = new DomainVerificationUserState(
                            userId, newEnabledHosts, oldUserState.isDisallowLinkHandling());
                            userId, newEnabledHosts, oldUserState.isLinkHandlingAllowed());
                    newUserStates.put(userId, newUserState);
                }
            }
@@ -1354,17 +1365,9 @@ public class DomainVerificationService extends SystemService
        final AndroidPackage pkg = pkgSetting.getPkg();

        // Should never be null, but if it is, skip this and assume that v2 is enabled
        if (pkg != null) {
            // To allow an instant app to immediately open domains after being installed by the
            // user, auto approve them for any declared autoVerify domains.
            if (pkgSetting.getInstantApp(userId)
                    && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
                return APPROVAL_LEVEL_INSTANT_APP;
            }

            if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
                int legacyState = mLegacySettings.getUserState(packageName, userId);
                switch (legacyState) {
        if (pkg != null && !DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg,
                SETTINGS_API_V2)) {
            switch (mLegacySettings.getUserState(packageName, userId)) {
                case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
                    // If nothing specifically set, assume v2 rules
                    break;
@@ -1377,7 +1380,6 @@ public class DomainVerificationService extends SystemService
                    return APPROVAL_LEVEL_LEGACY_ALWAYS;
            }
        }
        }

        synchronized (mLock) {
            DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
@@ -1390,7 +1392,7 @@ public class DomainVerificationService extends SystemService

            DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);

            if (userState != null && userState.isDisallowLinkHandling()) {
            if (userState != null && !userState.isLinkHandlingAllowed()) {
                if (DEBUG_APPROVAL) {
                    debugApproval(packageName, debugObject, userId, false,
                            "link handling not allowed");
@@ -1398,6 +1400,17 @@ public class DomainVerificationService extends SystemService
                return APPROVAL_LEVEL_NONE;
            }

            // The instant app branch must be run after the link handling check,
            // since that should also disable instant apps if toggled
            if (pkg != null) {
                // To allow an instant app to immediately open domains after being installed by the
                // user, auto approve them for any declared autoVerify domains.
                if (pkgSetting.getInstantApp(userId)
                        && mCollector.collectAutoVerifyDomains(pkg).contains(host)) {
                    return APPROVAL_LEVEL_INSTANT_APP;
                }
            }

            ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
            // Check if the exact host matches
            Integer state = stateMap.get(host);
@@ -1464,6 +1477,67 @@ public class DomainVerificationService extends SystemService
        }
    }

    /**
     * @return the filtered list paired with the corresponding approval level
     */
    @NonNull
    private Pair<List<String>, Integer> getApprovedPackages(@NonNull String domain,
            @UserIdInt int userId, int minimumApproval,
            @NonNull Function<String, PackageSetting> pkgSettingFunction) {
        int highestApproval = minimumApproval;
        List<String> approvedPackages = emptyList();

        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 = pkgSettingFunction.apply(packageName);
                if (pkgSetting == null) {
                    continue;
                }

                int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
                if (level < minimumApproval) {
                    continue;
                }

                if (level > highestApproval) {
                    approvedPackages.clear();
                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
                    highestApproval = level;
                } else if (level == highestApproval) {
                    approvedPackages = CollectionUtils.add(approvedPackages, packageName);
                }
            }
        }

        if (approvedPackages.isEmpty()) {
            return Pair.create(approvedPackages, APPROVAL_LEVEL_NONE);
        }

        List<String> filteredPackages = new ArrayList<>();
        long latestInstall = Long.MIN_VALUE;
        final int approvedSize = approvedPackages.size();
        for (int index = 0; index < approvedSize; index++) {
            String packageName = approvedPackages.get(index);
            PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
            if (pkgSetting == null) {
                continue;
            }
            long installTime = pkgSetting.getFirstInstallTime();
            if (installTime > latestInstall) {
                latestInstall = installTime;
                filteredPackages.clear();
                filteredPackages.add(packageName);
            } else if (installTime == latestInstall) {
                filteredPackages.add(packageName);
            }
        }

        return Pair.create(filteredPackages, highestApproval);
    }

    private void debugApproval(@NonNull String packageName, @NonNull Object debugObject,
            @UserIdInt int userId, boolean approved, @NonNull String reason) {
        String approvalString = approved ? "approved" : "denied";
Loading