summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/ResolveInfo.java33
-rw-r--r--services/core/java/com/android/server/pm/ComponentResolver.java2
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java205
-rw-r--r--services/core/java/com/android/server/pm/verify/domain/DomainVerificationUtils.java35
5 files changed, 154 insertions, 124 deletions
diff --git a/core/java/android/content/pm/ResolveInfo.java b/core/java/android/content/pm/ResolveInfo.java
index fe8e4d7b1bb2..6f07dd7a24e8 100644
--- a/core/java/android/content/pm/ResolveInfo.java
+++ b/core/java/android/content/pm/ResolveInfo.java
@@ -19,6 +19,7 @@ package android.content.pm;
import android.annotation.SystemApi;
import android.compat.annotation.UnsupportedAppUsage;
import android.content.ComponentName;
+import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.drawable.Drawable;
import android.os.Build;
@@ -183,6 +184,17 @@ public class ResolveInfo implements Parcelable {
@SystemApi
public boolean handleAllWebDataURI;
+ /**
+ * Whether the resolved {@link IntentFilter} declares {@link Intent#CATEGORY_BROWSABLE} and is
+ * thus allowed to automatically resolve an {@link Intent} as it's assumed the action is safe
+ * for the user.
+ *
+ * Note that the above doesn't apply when this is the only result is returned in the candidate
+ * set, as the system will not prompt before opening the result. It only applies when there are
+ * multiple candidates.
+ */
+ private final boolean mAutoResolutionAllowed;
+
/** {@hide} */
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public ComponentInfo getComponentInfo() {
@@ -364,8 +376,26 @@ public class ResolveInfo implements Parcelable {
&& INTENT_FORWARDER_ACTIVITY.equals(activityInfo.targetActivity);
}
+ /**
+ * @see #mAutoResolutionAllowed
+ * @hide
+ */
+ public boolean isAutoResolutionAllowed() {
+ return mAutoResolutionAllowed;
+ }
+
public ResolveInfo() {
targetUserId = UserHandle.USER_CURRENT;
+
+ // It's safer to assume that an unaware caller that constructs a ResolveInfo doesn't
+ // accidentally mark a result as auto resolveable.
+ mAutoResolutionAllowed = false;
+ }
+
+ /** @hide */
+ public ResolveInfo(boolean autoResolutionAllowed) {
+ targetUserId = UserHandle.USER_CURRENT;
+ mAutoResolutionAllowed = autoResolutionAllowed;
}
public ResolveInfo(ResolveInfo orig) {
@@ -386,6 +416,7 @@ public class ResolveInfo implements Parcelable {
system = orig.system;
targetUserId = orig.targetUserId;
handleAllWebDataURI = orig.handleAllWebDataURI;
+ mAutoResolutionAllowed = orig.mAutoResolutionAllowed;
isInstantAppAvailable = orig.isInstantAppAvailable;
}
@@ -450,6 +481,7 @@ public class ResolveInfo implements Parcelable {
dest.writeInt(noResourceId ? 1 : 0);
dest.writeInt(iconResourceId);
dest.writeInt(handleAllWebDataURI ? 1 : 0);
+ dest.writeInt(mAutoResolutionAllowed ? 1 : 0);
dest.writeInt(isInstantAppAvailable ? 1 : 0);
}
@@ -498,6 +530,7 @@ public class ResolveInfo implements Parcelable {
noResourceId = source.readInt() != 0;
iconResourceId = source.readInt();
handleAllWebDataURI = source.readInt() != 0;
+ mAutoResolutionAllowed = source.readInt() != 0;
isInstantAppAvailable = source.readInt() != 0;
}
diff --git a/services/core/java/com/android/server/pm/ComponentResolver.java b/services/core/java/com/android/server/pm/ComponentResolver.java
index 4be509b3f464..1d556fec31ea 100644
--- a/services/core/java/com/android/server/pm/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/ComponentResolver.java
@@ -1492,7 +1492,7 @@ public class ComponentResolver {
}
return null;
}
- final ResolveInfo res = new ResolveInfo();
+ final ResolveInfo res = new ResolveInfo(info.hasCategory(Intent.CATEGORY_BROWSABLE));
res.activityInfo = ai;
if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) {
res.filter = info;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 317effab3750..1530e41917c7 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -2644,8 +2644,7 @@ public class PackageManagerService extends IPackageManager.Stub
// We'll want to include browser possibilities in a few cases
boolean includeBrowser = false;
- if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates,
- matchFlags)) {
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent, matchFlags)) {
result.addAll(undefinedList);
// Maybe add one for the other profile.
if (xpDomainInfo != null && xpDomainInfo.highestApprovalLevel
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 76d5b7ba88b1..a0e252a8a28a 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
@@ -1330,83 +1330,50 @@ public class DomainVerificationService extends SystemService
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
String domain = intent.getData().getHost();
- // Collect package names
- ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
+ // Collect valid infos
+ ArrayMap<ResolveInfo, Integer> infoApprovals = new ArrayMap<>();
int infosSize = infos.size();
for (int index = 0; index < infosSize; index++) {
- packageApprovals.put(infos.get(index).getComponentInfo().packageName,
- APPROVAL_LEVEL_NONE);
+ final ResolveInfo info = infos.get(index);
+ // Only collect for intent filters that can auto resolve
+ if (info.isAutoResolutionAllowed()) {
+ infoApprovals.put(info, null);
+ }
}
// Find all approval levels
- int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
+ int highestApproval = fillMapWithApprovalLevels(infoApprovals, domain, userId,
pkgSettingFunction);
if (highestApproval == APPROVAL_LEVEL_NONE) {
return Pair.create(emptyList(), highestApproval);
}
- // Filter to highest, non-zero packages
- ArraySet<String> approvedPackages = new ArraySet<>();
- int approvalsSize = packageApprovals.size();
- for (int index = 0; index < approvalsSize; index++) {
- if (packageApprovals.valueAt(index) == highestApproval) {
- approvedPackages.add(packageApprovals.keyAt(index));
+ // Filter to highest, non-zero infos
+ for (int index = infoApprovals.size() - 1; index >= 0; index--) {
+ if (infoApprovals.valueAt(index) != highestApproval) {
+ infoApprovals.removeAt(index);
}
}
- ArraySet<String> filteredPackages = new ArraySet<>();
- if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
+ if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) {
// To maintain legacy behavior while the Settings API is not implemented,
// show the chooser if all approved apps are marked ask, skipping the
// last app, last declaration filtering.
- filteredPackages.addAll(approvedPackages);
- } else {
- // Filter to last installed package
- long latestInstall = Long.MIN_VALUE;
- int approvedSize = approvedPackages.size();
- for (int index = 0; index < approvedSize; index++) {
- String packageName = approvedPackages.valueAt(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);
- }
- }
+ filterToLastFirstInstalled(infoApprovals, pkgSettingFunction);
}
- // Filter to approved ResolveInfos
- ArrayMap<String, List<ResolveInfo>> approvedInfos = new ArrayMap<>();
- for (int index = 0; index < infosSize; index++) {
- ResolveInfo info = infos.get(index);
- String packageName = info.getComponentInfo().packageName;
- if (filteredPackages.contains(packageName)) {
- List<ResolveInfo> infosPerPackage = approvedInfos.get(packageName);
- if (infosPerPackage == null) {
- infosPerPackage = new ArrayList<>();
- approvedInfos.put(packageName, infosPerPackage);
- }
- infosPerPackage.add(info);
- }
+ // Easier to transform into list as the filterToLastDeclared method
+ // requires swapping indexes, which doesn't work with ArrayMap keys
+ final int size = infoApprovals.size();
+ List<ResolveInfo> finalList = new ArrayList<>(size);
+ for (int index = 0; index < size; index++) {
+ finalList.add(infoApprovals.keyAt(index));
}
- List<ResolveInfo> finalList;
- if (highestApproval == APPROVAL_LEVEL_LEGACY_ASK) {
- // If legacy ask, skip the last declaration filtering
- finalList = new ArrayList<>();
- int size = approvedInfos.size();
- for (int index = 0; index < size; index++) {
- finalList.addAll(approvedInfos.valueAt(index));
- }
- } else {
+ // If legacy ask, skip the last declaration filtering
+ if (highestApproval != APPROVAL_LEVEL_LEGACY_ASK) {
// Find the last declared ResolveInfo per package
- finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
+ filterToLastDeclared(finalList, pkgSettingFunction);
}
return Pair.create(finalList, highestApproval);
@@ -1415,68 +1382,127 @@ public class DomainVerificationService extends SystemService
/**
* @return highest approval level found
*/
- private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, Integer> inputMap,
+ @ApprovalLevel
+ private int fillMapWithApprovalLevels(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
@NonNull String domain, @UserIdInt int userId,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
int highestApproval = APPROVAL_LEVEL_NONE;
int size = inputMap.size();
for (int index = 0; index < size; index++) {
- String packageName = inputMap.keyAt(index);
+ if (inputMap.valueAt(index) != null) {
+ // Already filled by previous iteration
+ continue;
+ }
+
+ ResolveInfo info = inputMap.keyAt(index);
+ final String packageName = info.getComponentInfo().packageName;
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
if (pkgSetting == null) {
- inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
+ fillInfoMapForSamePackage(inputMap, packageName, APPROVAL_LEVEL_NONE);
continue;
}
int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
highestApproval = Math.max(highestApproval, approval);
- inputMap.setValueAt(index, approval);
+ fillInfoMapForSamePackage(inputMap, packageName, approval);
}
return highestApproval;
}
+ private void fillInfoMapForSamePackage(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
+ @NonNull String targetPackageName, @ApprovalLevel int level) {
+ final int size = inputMap.size();
+ for (int index = 0; index < size; index++) {
+ final String packageName = inputMap.keyAt(index).getComponentInfo().packageName;
+ if (Objects.equals(targetPackageName, packageName)) {
+ inputMap.setValueAt(index, level);
+ }
+ }
+ }
+
@NonNull
- private List<ResolveInfo> filterToLastDeclared(
- @NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
+ private void filterToLastFirstInstalled(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
- List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
-
- int inputSize = inputMap.size();
- for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
- String packageName = inputMap.keyAt(inputIndex);
- List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
+ // First, find the package with the latest first install time
+ String targetPackageName = null;
+ long latestInstall = Long.MIN_VALUE;
+ final int size = inputMap.size();
+ for (int index = 0; index < size; index++) {
+ ResolveInfo info = inputMap.keyAt(index);
+ String packageName = info.getComponentInfo().packageName;
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
+ if (pkgSetting == null) {
+ continue;
+ }
+
+ long installTime = pkgSetting.getFirstInstallTime();
+ if (installTime > latestInstall) {
+ latestInstall = installTime;
+ targetPackageName = packageName;
+ }
+ }
+
+ // Then, remove all infos that don't match the package
+ for (int index = inputMap.size() - 1; index >= 0; index--) {
+ ResolveInfo info = inputMap.keyAt(index);
+ if (!Objects.equals(targetPackageName, info.getComponentInfo().packageName)) {
+ inputMap.removeAt(index);
+ }
+ }
+ }
+
+ @NonNull
+ private void filterToLastDeclared(@NonNull List<ResolveInfo> inputList,
+ @NonNull Function<String, PackageSetting> pkgSettingFunction) {
+ // Must call size each time as the size of the list will decrease
+ for (int index = 0; index < inputList.size(); index++) {
+ ResolveInfo info = inputList.get(index);
+ String targetPackageName = info.getComponentInfo().packageName;
+ PackageSetting pkgSetting = pkgSettingFunction.apply(targetPackageName);
AndroidPackage pkg = pkgSetting == null ? null : pkgSetting.getPkg();
if (pkg == null) {
continue;
}
- ResolveInfo result = null;
- int highestIndex = -1;
- int infosSize = infos.size();
- for (int infoIndex = 0; infoIndex < infosSize; infoIndex++) {
- ResolveInfo info = infos.get(infoIndex);
- List<ParsedActivity> activities = pkg.getActivities();
- int activitiesSize = activities.size();
- for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
- if (Objects.equals(activities.get(activityIndex).getComponentName(),
- info.getComponentInfo().getComponentName())) {
- if (activityIndex > highestIndex) {
- highestIndex = activityIndex;
- result = info;
- }
- break;
- }
+ ResolveInfo result = info;
+ int highestIndex = indexOfIntentFilterEntry(pkg, result);
+
+ // Search backwards so that lower results can be removed as they're found
+ for (int searchIndex = inputList.size() - 1; searchIndex >= index + 1; searchIndex--) {
+ ResolveInfo searchInfo = inputList.get(searchIndex);
+ if (!Objects.equals(targetPackageName, searchInfo.getComponentInfo().packageName)) {
+ continue;
}
+
+ int entryIndex = indexOfIntentFilterEntry(pkg, searchInfo);
+ if (entryIndex > highestIndex) {
+ highestIndex = entryIndex;
+ result = searchInfo;
+ }
+
+ // Always remove the entry so that the current index
+ // is left as the sole candidate of the target package
+ inputList.remove(searchIndex);
}
- // Shouldn't be null, but might as well be safe
- if (result != null) {
- finalList.add(result);
+ // Swap the current index for the result, leaving this as
+ // the only entry with the target package name
+ inputList.set(index, result);
+ }
+ }
+
+ private int indexOfIntentFilterEntry(@NonNull AndroidPackage pkg,
+ @NonNull ResolveInfo target) {
+ List<ParsedActivity> activities = pkg.getActivities();
+ int activitiesSize = activities.size();
+ for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) {
+ if (Objects.equals(activities.get(activityIndex).getComponentName(),
+ target.getComponentInfo().getComponentName())) {
+ return activityIndex;
}
}
- return finalList;
+ return -1;
}
@Override
@@ -1484,8 +1510,7 @@ public class DomainVerificationService extends SystemService
@NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
String packageName = pkgSetting.getName();
- if (!DomainVerificationUtils.isDomainVerificationIntent(intent, candidates,
- resolveInfoFlags)) {
+ if (!DomainVerificationUtils.isDomainVerificationIntent(intent, 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 883bbad1bd2d..cb3b5c9db7e7 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,7 +22,6 @@ 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;
@@ -30,7 +29,6 @@ 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 {
@@ -46,7 +44,6 @@ public final class DomainVerificationUtils {
}
public static boolean isDomainVerificationIntent(Intent intent,
- @NonNull List<ResolveInfo> candidates,
@PackageManager.ResolveInfoFlags int resolveInfoFlags) {
if (!intent.isWebIntent()) {
return false;
@@ -63,42 +60,18 @@ public final class DomainVerificationUtils {
&& 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;
+ // No categories, only allow matching DEFAULT by flags
+ return matchDefaultByFlags;
} 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;
+ // Otherwise only needs to have DEFAULT
+ return intent.hasCategory(Intent.CATEGORY_DEFAULT);
}
}