From 2db8c825d2d9c44aab78d4cdbe81d5fd9bd33e74 Mon Sep 17 00:00:00 2001 From: Winson Date: Mon, 1 Mar 2021 13:48:57 -0800 Subject: [PATCH] Update app links resolution to handle no category Intents If an Intent has no categories except DEFAULT, and there is only 1 non-browser result resolved along with 1+ browsers, the set of browsers will be ignored and app links will be run as if the intent was resolved with the BROWSABLE category. This means callers who omit BROWSABLE but resolve to only 1 app + browsers will automatically launch into the app. Bug: 181570854 Test: manual, am start launch test intents Change-Id: I1adfd41c64abd46dc98930ba36409550955275a9 --- .../server/pm/PackageManagerService.java | 25 ++++---- .../DomainVerificationManagerInternal.java | 1 + .../domain/DomainVerificationService.java | 4 +- .../domain/DomainVerificationUtils.java | 61 +++++++++++++++++-- 4 files changed, 74 insertions(+), 17 deletions(-) diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index ec424f03ed75a..e89d552e359e5 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2631,7 +2631,8 @@ public class PackageManagerService extends IPackageManager.Stub // We'll want to include browser possibilities in a few cases boolean includeBrowser = false; - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, + matchFlags)) { result.addAll(undefinedList); // Maybe add one for the other profile. if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel @@ -2815,8 +2816,8 @@ public class PackageManagerService extends IPackageManager.Stub } result.highestApprovalLevel = Math.max(mDomainVerificationManager - .approvalLevelForDomain(ps, intent, flags, riTargetUser.targetUserId), - result.highestApprovalLevel); + .approvalLevelForDomain(ps, intent, resultTargetUser, flags, + riTargetUser.targetUserId), result.highestApprovalLevel); } if (result != null && result.highestApprovalLevel <= DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE) { @@ -3062,8 +3063,8 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = info.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps.getInstantApp(userId)) { - if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, - userId)) { + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + instantApps, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "Instant app approved for intent; pkg: " + packageName); @@ -3990,8 +3991,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 (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, flags, - userId)) { + if (hasAnyDomainApproval(mDomainVerificationManager, ps, intent, + resolvedActivities, flags, userId)) { if (DEBUG_INSTANT) { Slog.v(TAG, "DENY instant app;" + " pkg: " + packageName + ", approved"); @@ -9610,7 +9611,7 @@ public class PackageManagerService extends IPackageManager.Stub final String packageName = ri.activityInfo.packageName; final PackageSetting ps = mSettings.getPackageLPr(packageName); if (ps != null && hasAnyDomainApproval(mDomainVerificationManager, ps, - intent, flags, userId)) { + intent, query, flags, userId)) { return ri; } } @@ -9667,10 +9668,10 @@ public class PackageManagerService extends IPackageManager.Stub */ private static boolean hasAnyDomainApproval( @NonNull DomainVerificationManagerInternal manager, @NonNull PackageSetting pkgSetting, - @NonNull Intent intent, @PackageManager.ResolveInfoFlags int resolveInfoFlags, - @UserIdInt int userId) { - return manager.approvalLevelForDomain(pkgSetting, intent, resolveInfoFlags, userId) - > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; + @NonNull Intent intent, @NonNull List candidates, + @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { + return manager.approvalLevelForDomain(pkgSetting, intent, candidates, resolveInfoFlags, + userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE; } /** diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java index 9e22d82910df1..a68b3da0f0b68 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerInternal.java @@ -310,6 +310,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan */ @ApprovalLevel int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + @NonNull List candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId); /** diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index b58c1ff374d52..dbf55576e38b7 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -1494,9 +1494,11 @@ public class DomainVerificationService extends SystemService @Override public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent, + @NonNull List candidates, @PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) { String packageName = pkgSetting.getName(); - if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) { + if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates, + resolveInfoFlags)) { if (DEBUG_APPROVAL) { debugApproval(packageName, intent, userId, false, "not valid intent"); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java index 783aff6ccb55f..883bbad1bd2da 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java @@ -22,12 +22,17 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.ResolveInfo; import android.os.Binder; +import com.android.internal.util.CollectionUtils; import com.android.server.compat.PlatformCompat; import com.android.server.pm.PackageManagerService; import com.android.server.pm.parsing.pkg.AndroidPackage; +import java.util.List; +import java.util.Set; + public final class DomainVerificationUtils { /** @@ -40,13 +45,61 @@ public final class DomainVerificationUtils { throw new NameNotFoundException("Package " + packageName + " unavailable"); } - public static boolean isDomainVerificationIntent(Intent intent, int resolveInfoFlags) { - if (!intent.isWebIntent() || !intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { + public static boolean isDomainVerificationIntent(Intent intent, + @NonNull List candidates, + @PackageManager.ResolveInfoFlags int resolveInfoFlags) { + if (!intent.isWebIntent()) { return false; } - return ((resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0) - || intent.hasCategory(Intent.CATEGORY_DEFAULT); + Set categories = intent.getCategories(); + int categoriesSize = CollectionUtils.size(categories); + if (categoriesSize > 2) { + // Specifying at least one non-app-link category + return false; + } else if (categoriesSize == 2) { + // Check for explicit app link intent with exactly BROWSABLE && DEFAULT + return intent.hasCategory(Intent.CATEGORY_DEFAULT) + && intent.hasCategory(Intent.CATEGORY_BROWSABLE); + } + + // In cases where at least one browser is resolved and only one non-browser is resolved, + // the Intent is coerced into an app links intent, under the assumption the browser can + // be skipped if the app is approved at any level for the domain. + boolean foundBrowser = false; + boolean foundOneApp = false; + + final int candidatesSize = candidates.size(); + for (int index = 0; index < candidatesSize; index++) { + final ResolveInfo info = candidates.get(index); + if (info.handleAllWebDataURI) { + foundBrowser = true; + } else if (foundOneApp) { + // Already true, so duplicate app + foundOneApp = false; + break; + } else { + foundOneApp = true; + } + } + + boolean matchDefaultByFlags = (resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + boolean onlyOneNonBrowser = foundBrowser && foundOneApp; + + // Check if matches (BROWSABLE || none) && DEFAULT + if (categoriesSize == 0) { + // No categories, run coerce case, matching DEFAULT by flags + return onlyOneNonBrowser && matchDefaultByFlags; + } else if (intent.hasCategory(Intent.CATEGORY_DEFAULT)) { + // Run coerce case, matching by explicit DEFAULT + return onlyOneNonBrowser; + } else if (intent.hasCategory(Intent.CATEGORY_BROWSABLE)) { + // Intent matches BROWSABLE, must match DEFAULT by flags + return matchDefaultByFlags; + } else { + // Otherwise not matching any app link categories + return false; + } } static boolean isChangeEnabled(PlatformCompat platformCompat, AndroidPackage pkg,