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

Commit 60bd6855 authored by Winson's avatar Winson
Browse files

Update intent resolution logic with new rules

Introduces the concept of an approval level for a package for a
given domain. The resulting set is determined by taking the highest
approval, last installed, last declared Activity from the intent
resolution candidate set.

Bug: 178525922

Test: TODO with CTS test

Change-Id: I7232343058e2352b322a30e58c07a39b95dbfea4
parent e2c749b6
Loading
Loading
Loading
Loading
+52 −44
Original line number Diff line number Diff line
@@ -193,7 +193,6 @@ import android.content.pm.InstrumentationInfo;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.KeySet;
import android.content.pm.ModuleInfo;
import android.content.pm.overlay.OverlayPaths;
import android.content.pm.PackageChangeEvent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInfoLite;
@@ -462,6 +461,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
/**
@@ -2586,49 +2586,42 @@ public class PackageManagerService extends IPackageManager.Stub
                Intent intent, int matchFlags, List<ResolveInfo> candidates,
                CrossProfileDomainInfo xpDomainInfo, int userId, boolean debug) {
            final ArrayList<ResolveInfo> result = new ArrayList<>();
            final ArrayList<ResolveInfo> alwaysList = new ArrayList<>();
            final ArrayList<ResolveInfo> undefinedList = new ArrayList<>();
            final ArrayList<ResolveInfo> matchAllList = new ArrayList<>();
            final int count = candidates.size();
            // First, try to use linked apps. Partition the candidates into four lists:
            // one for the final results, one for the "do not use ever", one for "undefined status"
            // and finally one for "browser app type".
            // First, try to use approved apps.
            for (int n = 0; n < count; n++) {
                ResolveInfo info = candidates.get(n);
                String packageName = info.activityInfo.packageName;
                PackageSetting ps = mSettings.getPackageLPr(packageName);
                if (ps != null) {
                // Add to the special match all list (Browser use case)
                if (info.handleAllWebDataURI) {
                    matchAllList.add(info);
                        continue;
                    }
                    boolean isAlways = mDomainVerificationManager
                            .isApprovedForDomain(ps, intent, userId);
                    if (isAlways) {
                        alwaysList.add(info);
                    } else {
                        undefinedList.add(info);
                    }
                    continue;
                }
            }
            Pair<List<ResolveInfo>, Integer> infosAndLevel = mDomainVerificationManager
                    .filterToApprovedApp(intent, candidates, userId, mSettings::getPackageLPr);
            List<ResolveInfo> approvedInfos = infosAndLevel.first;
            Integer highestApproval = infosAndLevel.second;
            // We'll want to include browser possibilities in a few cases
            boolean includeBrowser = false;
            // First try to add the "always" resolution(s) for the current user, if any
            if (alwaysList.size() > 0) {
                result.addAll(alwaysList);
            // If no apps are approved for the domain, resolve only to browsers
            if (approvedInfos.isEmpty()) {
                // If the other profile has a result, include that and delegate to ResolveActivity
                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
                        > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
                    result.add(xpDomainInfo.resolveInfo);
                } else {
                    includeBrowser = true;
                }
            } else {
                // Add all undefined apps as we want them to appear in the disambiguation dialog.
                result.addAll(undefinedList);
                // Maybe add one for the other profile.
                if (xpDomainInfo != null && xpDomainInfo.wereAnyDomainsVerificationApproved) {
                result.addAll(approvedInfos);
                // If the other profile has an app that's of equal or higher approval, add it
                if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel >= highestApproval) {
                    result.add(xpDomainInfo.resolveInfo);
                }
                includeBrowser = true;
            }
            if (includeBrowser) {
@@ -2676,9 +2669,7 @@ public class PackageManagerService extends IPackageManager.Stub
                    }
                }
                // If there is nothing selected, add all candidates and remove the ones that the
                //user
                // has explicitly put into the INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER state
                // If there is nothing selected, add all candidates
                if (result.size() == 0) {
                    result.addAll(candidates);
                }
@@ -2780,10 +2771,12 @@ public class PackageManagerService extends IPackageManager.Stub
                            sourceUserId, parentUserId);
                }
                result.wereAnyDomainsVerificationApproved |= mDomainVerificationManager
                        .isApprovedForDomain(ps, intent, riTargetUser.targetUserId);
                result.highestApprovalLevel = Math.max(mDomainVerificationManager
                        .approvalLevelForDomain(ps, intent, riTargetUser.targetUserId),
                        result.highestApprovalLevel);
            }
            if (result != null && !result.wereAnyDomainsVerificationApproved) {
            if (result != null && result.highestApprovalLevel
                    <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) {
                return null;
            }
            return result;
@@ -3026,9 +3019,10 @@ public class PackageManagerService extends IPackageManager.Stub
                    final String packageName = info.activityInfo.packageName;
                    final PackageSetting ps = mSettings.getPackageLPr(packageName);
                    if (ps.getInstantApp(userId)) {
                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
                                userId)) {
                            if (DEBUG_INSTANT) {
                                Slog.v(TAG, "Instant app approvd for intent; pkg: "
                                Slog.v(TAG, "Instant app approved for intent; pkg: "
                                        + packageName);
                            }
                            localInstantApp = info;
@@ -3953,7 +3947,8 @@ public class PackageManagerService extends IPackageManager.Stub
                if (ps != null) {
                    // only check domain verification status if the app is not a browser
                    if (!info.handleAllWebDataURI) {
                        if (mDomainVerificationManager.isApprovedForDomain(ps, intent, userId)) {
                        if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent,
                                userId)) {
                            if (DEBUG_INSTANT) {
                                Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName
                                        + ", approved");
@@ -9369,8 +9364,8 @@ public class PackageManagerService extends IPackageManager.Stub
                    if (ri.activityInfo.applicationInfo.isInstantApp()) {
                        final String packageName = ri.activityInfo.packageName;
                        final PackageSetting ps = mSettings.getPackageLPr(packageName);
                        if (ps != null && mDomainVerificationManager
                                .isApprovedForDomain(ps, intent, userId)) {
                        if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps,
                                intent, userId)) {
                            return ri;
                        }
                    }
@@ -9419,6 +9414,19 @@ public class PackageManagerService extends IPackageManager.Stub
        return null;
    }
    /**
     * Do NOT use for intent resolution filtering. That should be done with
     * {@link DomainVerificationManagerInternal#filterToApprovedApp(Intent, List, int, Function)}.
     *
     * @return if the package is approved at any non-zero level for the domain in the intent
     */
    private static boolean hasAnyDomainApproval(
            @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting,
            @NonNull Intent intent, @UserIdInt int userId) {
        return manager.approvalLevelForDomain(pkgSetting, intent, userId)
                > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
    }
    /**
     * Return true if the given list is not empty and all of its contents have
     * an activityInfo with the given package name.
@@ -9862,7 +9870,7 @@ public class PackageManagerService extends IPackageManager.Stub
    private static class CrossProfileDomainInfo {
        /* ResolveInfo for IntentForwarderActivity to send the intent to the other profile */
        ResolveInfo resolveInfo;
        boolean wereAnyDomainsVerificationApproved;
        int highestApprovalLevel = DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
    }
    private CrossProfileDomainInfo getCrossProfileDomainPreferredLpr(Intent intent,
+4 −0
Original line number Diff line number Diff line
@@ -783,6 +783,10 @@ public abstract class PackageSettingBase extends SettingBase {
        incrementalStates.onStorageHealthStatusChanged(status);
    }

    public long getFirstInstallTime() {
        return firstInstallTime;
    }

    protected PackageSettingBase updateFrom(PackageSettingBase other) {
        super.copyFrom(other);
        setPath(other.getPath());
+98 −10
Original line number Diff line number Diff line
@@ -16,18 +16,22 @@

package com.android.server.pm.verify.domain;

import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.UserIdInt;
import android.content.Intent;
import android.content.pm.IntentFilterVerificationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.os.Binder;
import android.os.UserHandle;
import android.util.IndentingPrintWriter;
import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;

@@ -39,6 +43,7 @@ import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy;
import org.xmlpull.v1.XmlPullParserException;

import java.io.IOException;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import java.util.function.Function;
@@ -47,6 +52,78 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan

    UUID DISABLED_ID = new UUID(0, 0);

    /**
     * The app has not been approved for this domain and should never be able to open it through
     * an implicit web intent.
     */
    int APPROVAL_LEVEL_NONE = 0;

    /**
     * The app has been approved through the legacy
     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK} and
     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK}.
     *
     * This should be used as the cutoff for showing a picker if no better approved app exists
     * during the legacy transition period.
     *
     * TODO(b/177923646): The legacy values can be removed once the Settings API changes are
     *  shipped. These values are not stable, so just deleting the constant and shifting others is
     *  fine.
     */
    int APPROVAL_LEVEL_LEGACY_ASK = 1;

    /**
     * The app has been approved through the legacy
     * {@link PackageManager#updateIntentVerificationStatusAsUser(String, int, int)} API, which has
     * been preserved for migration purposes, but is otherwise ignored. Corresponds to
     * {@link PackageManager#INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS}.
     */
    int APPROVAL_LEVEL_LEGACY_ALWAYS = 1;

    /**
     * The app has been chosen by the user through
     * {@link #setDomainVerificationUserSelection(UUID, Set, boolean)}, indictag an explicit
     * choice to use this app to open an unverified domain.
     */
    int APPROVAL_LEVEL_SELECTION = 2;

    /**
     * The app is approved through the digital asset link statement being hosted at the domain
     * it is capturing. This is set through {@link #setDomainVerificationStatus(UUID, Set, int)} by
     * the domain verification agent on device.
     */
    int APPROVAL_LEVEL_VERIFIED = 3;

    /**
     * The app has been installed as an instant app, which grants it total authority on the domains
     * that it declares. It is expected that the package installer validate the domains the app
     * declares against the digital asset link statements before allowing it to be installed.
     *
     * The user is still able to disable instant app link handling through
     * {@link #setDomainVerificationLinkHandlingAllowed(String, boolean)}.
     */
    int APPROVAL_LEVEL_INSTANT_APP = 4;

    /**
     * Defines the possible values for {@link #approvalLevelForDomain(PackageSetting, Intent, int)}
     * which sorts packages 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.
     */
    @IntDef({
            APPROVAL_LEVEL_NONE,
            APPROVAL_LEVEL_LEGACY_ASK,
            APPROVAL_LEVEL_LEGACY_ALWAYS,
            APPROVAL_LEVEL_SELECTION,
            APPROVAL_LEVEL_VERIFIED,
            APPROVAL_LEVEL_INSTANT_APP
    })
    @interface ApprovalLevel{}

    /**
     * Generate a new domain set ID to be used for attaching new packages.
     */
@@ -211,11 +288,28 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
    DomainVerificationCollector getCollector();

    /**
     * Check if a resolving URI is approved to takeover the domain as the sole resolved target.
     * Filters the provided list down to the {@link ResolveInfo} objects that should be allowed
     * to open the domain inside the {@link Intent}. It is possible for no packages represented in
     * the list to be approved, in which case an empty list will be returned.
     *
     * @return the filtered list and the corresponding approval level
     */
    @NonNull
    Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
            @NonNull List<ResolveInfo> infos, @UserIdInt int userId,
            @NonNull Function<String, PackageSetting> pkgSettingFunction);

    /**
     * Check at what precedence a package resolving a URI is approved to takeover the domain.
     * This can be because the domain was auto-verified for the package, or if the user manually
     * chose to enable the domain for the package.
     * chose to enable the domain for the package. If an app is auto-verified, it will be
     * preferred over apps that were manually selected.
     *
     * NOTE: This should not be used for filtering intent resolution. See
     * {@link #filterToApprovedApp(Intent, List, int, Function)} for that.
     */
    boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
    @ApprovalLevel
    int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
            @UserIdInt int userId);

    /**
@@ -231,8 +325,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
            throws IllegalArgumentException, NameNotFoundException;


    interface Connection extends DomainVerificationEnforcer.Callback,
            Function<String, PackageSetting> {
    interface Connection extends DomainVerificationEnforcer.Callback {

        /**
         * Notify that a settings change has been made and that eventually
@@ -265,10 +358,5 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan

        @Nullable
        AndroidPackage getPackageLocked(@NonNull String pkgName);

        @Override
        default PackageSetting apply(@NonNull String pkgName) {
            return getPackageSettingLocked(pkgName);
        }
    }
}
+236 −50

File changed.

Preview size limit exceeded, changes collapsed.