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
This commit is contained in:
Winson
2021-03-01 13:48:57 -08:00
parent 8760b38f80
commit 2db8c825d2
4 changed files with 74 additions and 17 deletions

View File

@@ -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<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
return manager.approvalLevelForDomain(pkgSetting, intent, candidates, resolveInfoFlags,
userId) > DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE;
}
/**

View File

@@ -310,6 +310,7 @@ public interface DomainVerificationManagerInternal extends DomainVerificationMan
*/
@ApprovalLevel
int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
@NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId);
/**

View File

@@ -1494,9 +1494,11 @@ public class DomainVerificationService extends SystemService
@Override
public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
@NonNull List<ResolveInfo> 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");
}

View File

@@ -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<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags) {
if (!intent.isWebIntent()) {
return false;
}
return ((resolveInfoFlags & PackageManager.MATCH_DEFAULT_ONLY) != 0)
|| intent.hasCategory(Intent.CATEGORY_DEFAULT);
Set<String> 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,