Merge changes I5e258e23,I72323430 into sc-dev
* changes: Update isLinkHandlingAllowed to mean all or nothing Update intent resolution logic with new rules
This commit is contained in:
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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() {}
|
||||
|
||||
@@ -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".
|
||||
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;
|
||||
final int count = candidates.size();
|
||||
// First, try to use approved apps.
|
||||
for (int n = 0; n < count; n++) {
|
||||
ResolveInfo info = candidates.get(n);
|
||||
// Add to the special match all list (Browser use case)
|
||||
if (info.handleAllWebDataURI) {
|
||||
matchAllList.add(info);
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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 ");
|
||||
|
||||
@@ -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.
|
||||
* 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.
|
||||
* 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
|
||||
*/
|
||||
boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
|
||||
@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. 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.
|
||||
*/
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package com.android.server.pm.verify.domain;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
@@ -26,6 +28,8 @@ 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.parsing.component.ParsedActivity;
|
||||
import android.content.pm.verify.domain.DomainVerificationInfo;
|
||||
import android.content.pm.verify.domain.DomainVerificationManager;
|
||||
import android.content.pm.verify.domain.DomainVerificationState;
|
||||
@@ -35,6 +39,7 @@ import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.IndentingPrintWriter;
|
||||
import android.util.Pair;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.SparseIntArray;
|
||||
@@ -62,6 +67,7 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Function;
|
||||
@@ -401,7 +407,7 @@ public class DomainVerificationService extends SystemService
|
||||
}
|
||||
|
||||
pkgState.getOrCreateUserSelectionState(userId)
|
||||
.setDisallowLinkHandling(!allowed);
|
||||
.setLinkHandlingAllowed(allowed);
|
||||
}
|
||||
|
||||
mConnection.scheduleWriteSettings();
|
||||
@@ -423,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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -440,7 +446,7 @@ public class DomainVerificationService extends SystemService
|
||||
}
|
||||
|
||||
pkgState.getOrCreateUserSelectionState(userId)
|
||||
.setDisallowLinkHandling(!allowed);
|
||||
.setLinkHandlingAllowed(allowed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -468,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);
|
||||
@@ -589,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++) {
|
||||
@@ -601,7 +618,7 @@ public class DomainVerificationService extends SystemService
|
||||
}
|
||||
|
||||
return new DomainVerificationUserSelection(pkgState.getId(), packageName,
|
||||
UserHandle.of(userId), openVerifiedLinks, hostToUserSelectionMap);
|
||||
UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -683,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);
|
||||
}
|
||||
}
|
||||
@@ -910,7 +927,7 @@ public class DomainVerificationService extends SystemService
|
||||
// This method is only used by DomainVerificationShell, which doesn't lock PMS, so it's
|
||||
// safe to pass mConnection directly here and lock PMS. This method is not exposed
|
||||
// to the general system server/PMS.
|
||||
printState(writer, packageName, userId, mConnection);
|
||||
printState(writer, packageName, userId, mConnection::getPackageSettingLocked);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -919,7 +936,7 @@ public class DomainVerificationService extends SystemService
|
||||
@Nullable Function<String, PackageSetting> pkgSettingFunction)
|
||||
throws NameNotFoundException {
|
||||
if (pkgSettingFunction == null) {
|
||||
pkgSettingFunction = mConnection;
|
||||
pkgSettingFunction = mConnection::getPackageSettingLocked;
|
||||
}
|
||||
|
||||
synchronized (mLock) {
|
||||
@@ -1156,46 +1173,211 @@ public class DomainVerificationService extends SystemService
|
||||
mConnection.scheduleWriteSettings();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*
|
||||
* Resolving an Intent to an approved app happens in stages:
|
||||
* <ol>
|
||||
* <li>Find all non-zero approved packages for the {@link Intent}'s domain</li>
|
||||
* <li>Filter to packages with the highest approval level, see {@link ApprovalLevel}</li>
|
||||
* <li>Filter out {@link ResolveInfo}s that don't match that approved packages</li>
|
||||
* <li>Take the approved packages with the latest install time</li>
|
||||
* <li>Take the ResolveInfo representing the Activity declared last in the manifest</li>
|
||||
* <li>Return remaining results if any exist</li>
|
||||
* </ol>
|
||||
*/
|
||||
@NonNull
|
||||
@Override
|
||||
public boolean isApprovedForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
|
||||
public Pair<List<ResolveInfo>, Integer> filterToApprovedApp(@NonNull Intent intent,
|
||||
@NonNull List<ResolveInfo> infos, @UserIdInt int userId,
|
||||
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
|
||||
String domain = intent.getData().getHost();
|
||||
|
||||
// Collect package names
|
||||
ArrayMap<String, Integer> packageApprovals = new ArrayMap<>();
|
||||
int infosSize = infos.size();
|
||||
for (int index = 0; index < infosSize; index++) {
|
||||
packageApprovals.put(infos.get(index).getComponentInfo().packageName,
|
||||
APPROVAL_LEVEL_NONE);
|
||||
}
|
||||
|
||||
// Find all approval levels
|
||||
int highestApproval = fillMapWithApprovalLevels(packageApprovals, domain, userId,
|
||||
pkgSettingFunction);
|
||||
if (highestApproval == APPROVAL_LEVEL_NONE) {
|
||||
return Pair.create(emptyList(), highestApproval);
|
||||
}
|
||||
|
||||
// Filter to highest, non-zero packages
|
||||
ArraySet<String> approvedPackages = new ArraySet<>();
|
||||
for (int index = 0; index < infosSize; index++) {
|
||||
if (packageApprovals.valueAt(index) == highestApproval) {
|
||||
approvedPackages.add(packageApprovals.keyAt(index));
|
||||
}
|
||||
}
|
||||
|
||||
ArraySet<String> filteredPackages = new ArraySet<>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// Find the last declared ResolveInfo per package
|
||||
finalList = filterToLastDeclared(approvedInfos, pkgSettingFunction);
|
||||
}
|
||||
|
||||
return Pair.create(finalList, highestApproval);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return highest approval level found
|
||||
*/
|
||||
private int fillMapWithApprovalLevels(@NonNull ArrayMap<String, 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);
|
||||
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
|
||||
if (pkgSetting == null) {
|
||||
inputMap.setValueAt(index, APPROVAL_LEVEL_NONE);
|
||||
continue;
|
||||
}
|
||||
int approval = approvalLevelForDomain(pkgSetting, domain, userId, domain);
|
||||
highestApproval = Math.max(highestApproval, approval);
|
||||
inputMap.setValueAt(index, approval);
|
||||
}
|
||||
|
||||
return highestApproval;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
private List<ResolveInfo> filterToLastDeclared(
|
||||
@NonNull ArrayMap<String, List<ResolveInfo>> 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);
|
||||
PackageSetting pkgSetting = pkgSettingFunction.apply(packageName);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shouldn't be null, but might as well be safe
|
||||
if (result != null) {
|
||||
finalList.add(result);
|
||||
}
|
||||
}
|
||||
|
||||
return finalList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
|
||||
@UserIdInt int userId) {
|
||||
String packageName = pkgSetting.name;
|
||||
if (!DomainVerificationUtils.isDomainVerificationIntent(intent)) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, false, "not valid intent");
|
||||
}
|
||||
return false;
|
||||
return APPROVAL_LEVEL_NONE;
|
||||
}
|
||||
|
||||
String host = intent.getData().getHost();
|
||||
return approvalLevelForDomain(pkgSetting, intent.getData().getHost(), userId, intent);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param debugObject Should be an {@link Intent} if checking for resolution or a {@link String}
|
||||
* otherwise.
|
||||
*/
|
||||
private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
|
||||
@UserIdInt int userId, @NonNull Object debugObject) {
|
||||
String packageName = pkgSetting.name;
|
||||
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 true;
|
||||
}
|
||||
|
||||
if (!DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, SETTINGS_API_V2)) {
|
||||
int legacyState = mLegacySettings.getUserState(packageName, userId);
|
||||
switch (legacyState) {
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED:
|
||||
// If nothing specifically set, assume v2 rules
|
||||
break;
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
|
||||
// With v2 split into 2 lists, always and undefined, the concept of whether
|
||||
// or not to ask is irrelevant. Assume the user wants this application to
|
||||
// open the domain.
|
||||
return true;
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
|
||||
// Never has the same semantics are before
|
||||
return false;
|
||||
}
|
||||
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;
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_NEVER:
|
||||
return APPROVAL_LEVEL_NONE;
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ASK:
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS_ASK:
|
||||
return APPROVAL_LEVEL_LEGACY_ASK;
|
||||
case PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_ALWAYS:
|
||||
return APPROVAL_LEVEL_LEGACY_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1203,59 +1385,76 @@ public class DomainVerificationService extends SystemService
|
||||
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
|
||||
if (pkgState == null) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, false, "pkgState unavailable");
|
||||
debugApproval(packageName, debugObject, userId, false, "pkgState unavailable");
|
||||
}
|
||||
return APPROVAL_LEVEL_NONE;
|
||||
}
|
||||
|
||||
DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
|
||||
|
||||
if (userState != null && !userState.isLinkHandlingAllowed()) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, debugObject, userId, false,
|
||||
"link handling not allowed");
|
||||
}
|
||||
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;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
|
||||
DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
|
||||
// Check if the exact host matches
|
||||
Integer state = stateMap.get(host);
|
||||
if (state != null && DomainVerificationManager.isStateVerified(state)) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, debugObject, userId, true,
|
||||
"host verified exactly");
|
||||
}
|
||||
return APPROVAL_LEVEL_VERIFIED;
|
||||
}
|
||||
|
||||
// Only allow autoVerify approval if the user hasn't disabled it
|
||||
if (userState == null || !userState.isDisallowLinkHandling()) {
|
||||
// Check if the exact host matches
|
||||
Integer state = stateMap.get(host);
|
||||
if (state != null && DomainVerificationManager.isStateVerified(state)) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, true, "host verified exactly");
|
||||
}
|
||||
return true;
|
||||
// Otherwise see if the host matches a verified domain by wildcard
|
||||
int stateMapSize = stateMap.size();
|
||||
for (int index = 0; index < stateMapSize; index++) {
|
||||
if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Otherwise see if the host matches a verified domain by wildcard
|
||||
int stateMapSize = stateMap.size();
|
||||
for (int index = 0; index < stateMapSize; index++) {
|
||||
if (!DomainVerificationManager.isStateVerified(stateMap.valueAt(index))) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String domain = stateMap.keyAt(index);
|
||||
if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, true,
|
||||
"host verified by wildcard");
|
||||
}
|
||||
return true;
|
||||
String domain = stateMap.keyAt(index);
|
||||
if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, debugObject, userId, true,
|
||||
"host verified by wildcard");
|
||||
}
|
||||
return APPROVAL_LEVEL_VERIFIED;
|
||||
}
|
||||
}
|
||||
|
||||
// Check user state if available
|
||||
if (userState == null) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, false, "userState unavailable");
|
||||
debugApproval(packageName, debugObject, userId, false, "userState unavailable");
|
||||
}
|
||||
return false;
|
||||
return APPROVAL_LEVEL_NONE;
|
||||
}
|
||||
|
||||
// See if the user has approved the exact host
|
||||
ArraySet<String> enabledHosts = userState.getEnabledHosts();
|
||||
if (enabledHosts.contains(host)) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, true,
|
||||
debugApproval(packageName, debugObject, userId, true,
|
||||
"host enabled by user exactly");
|
||||
}
|
||||
return true;
|
||||
return APPROVAL_LEVEL_SELECTION;
|
||||
}
|
||||
|
||||
// See if the host matches a user selection by wildcard
|
||||
@@ -1264,24 +1463,85 @@ public class DomainVerificationService extends SystemService
|
||||
String domain = enabledHosts.valueAt(index);
|
||||
if (domain.startsWith("*.") && host.endsWith(domain.substring(2))) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, true,
|
||||
debugApproval(packageName, debugObject, userId, true,
|
||||
"host enabled by user through wildcard");
|
||||
}
|
||||
return true;
|
||||
return APPROVAL_LEVEL_SELECTION;
|
||||
}
|
||||
}
|
||||
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, false, "not approved");
|
||||
debugApproval(packageName, debugObject, userId, false, "not approved");
|
||||
}
|
||||
return false;
|
||||
return APPROVAL_LEVEL_NONE;
|
||||
}
|
||||
}
|
||||
|
||||
private void debugApproval(@NonNull String packageName, @NonNull Intent intent,
|
||||
/**
|
||||
* @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";
|
||||
Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for " + intent
|
||||
+ " for user " + userId + ": " + reason);
|
||||
Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
|
||||
+ debugObject + " for user " + userId + ": " + reason);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,6 @@ import android.content.pm.verify.domain.DomainVerificationState;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
import android.util.Pair;
|
||||
import android.util.SparseArray;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
@@ -229,14 +228,14 @@ class DomainVerificationSettings {
|
||||
DomainVerificationUserState oldUserState =
|
||||
oldSelectionStates.get(UserHandle.USER_SYSTEM);
|
||||
|
||||
boolean disallowLinkHandling = newUserState.isDisallowLinkHandling();
|
||||
boolean linkHandlingAllowed = newUserState.isLinkHandlingAllowed();
|
||||
if (oldUserState == null) {
|
||||
oldUserState = new DomainVerificationUserState(UserHandle.USER_SYSTEM,
|
||||
newEnabledHosts, disallowLinkHandling);
|
||||
newEnabledHosts, linkHandlingAllowed);
|
||||
oldSelectionStates.put(UserHandle.USER_SYSTEM, oldUserState);
|
||||
} else {
|
||||
oldUserState.addHosts(newEnabledHosts)
|
||||
.setDisallowLinkHandling(disallowLinkHandling);
|
||||
.setLinkHandlingAllowed(linkHandlingAllowed);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ import com.android.internal.util.DataClass;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Tracks which domains have been explicitly enabled by the user, allowing it to automatically open
|
||||
* that domain when a web URL Intent is sent ft.
|
||||
* Tracks which domains have been explicitly enabled by the user, allowing it to open that domain
|
||||
* when a web URL Intent is sent and the application is the highest priority for that domain.
|
||||
*/
|
||||
@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true)
|
||||
@DataClass(genSetters = true, genEqualsHashCode = true, genToString = true, genBuilder = false)
|
||||
public class DomainVerificationUserState {
|
||||
|
||||
@UserIdInt
|
||||
@@ -38,8 +38,10 @@ public class DomainVerificationUserState {
|
||||
@NonNull
|
||||
private final ArraySet<String> mEnabledHosts;
|
||||
|
||||
/** Whether to disallow this package from automatically opening links by auto verification. */
|
||||
private boolean mDisallowLinkHandling;
|
||||
/**
|
||||
* Whether to allow this package to automatically open links by auto verification.
|
||||
*/
|
||||
private boolean mLinkHandlingAllowed = true;
|
||||
|
||||
public DomainVerificationUserState(@UserIdInt int userId) {
|
||||
mUserId = userId;
|
||||
@@ -67,13 +69,15 @@ public class DomainVerificationUserState {
|
||||
}
|
||||
|
||||
|
||||
|
||||
// Code below generated by codegen v1.0.22.
|
||||
//
|
||||
// DO NOT MODIFY!
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm
|
||||
// /verify/domain/models/DomainVerificationUserState.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
@@ -85,19 +89,21 @@ public class DomainVerificationUserState {
|
||||
*
|
||||
* @param enabledHosts
|
||||
* List of domains which have been enabled by the user. *
|
||||
* @param linkHandlingAllowed
|
||||
* Whether to allow this package to automatically open links by auto verification.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public DomainVerificationUserState(
|
||||
@UserIdInt int userId,
|
||||
@NonNull ArraySet<String> enabledHosts,
|
||||
boolean disallowLinkHandling) {
|
||||
boolean linkHandlingAllowed) {
|
||||
this.mUserId = userId;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
UserIdInt.class, null, mUserId);
|
||||
this.mEnabledHosts = enabledHosts;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mEnabledHosts);
|
||||
this.mDisallowLinkHandling = disallowLinkHandling;
|
||||
this.mLinkHandlingAllowed = linkHandlingAllowed;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
@@ -115,14 +121,20 @@ public class DomainVerificationUserState {
|
||||
return mEnabledHosts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to allow this package to automatically open links by auto verification.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public boolean isDisallowLinkHandling() {
|
||||
return mDisallowLinkHandling;
|
||||
public boolean isLinkHandlingAllowed() {
|
||||
return mLinkHandlingAllowed;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether to allow this package to automatically open links by auto verification.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public @NonNull DomainVerificationUserState setDisallowLinkHandling( boolean value) {
|
||||
mDisallowLinkHandling = value;
|
||||
public @NonNull DomainVerificationUserState setLinkHandlingAllowed( boolean value) {
|
||||
mLinkHandlingAllowed = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -135,7 +147,7 @@ public class DomainVerificationUserState {
|
||||
return "DomainVerificationUserState { " +
|
||||
"userId = " + mUserId + ", " +
|
||||
"enabledHosts = " + mEnabledHosts + ", " +
|
||||
"disallowLinkHandling = " + mDisallowLinkHandling +
|
||||
"linkHandlingAllowed = " + mLinkHandlingAllowed +
|
||||
" }";
|
||||
}
|
||||
|
||||
@@ -154,7 +166,7 @@ public class DomainVerificationUserState {
|
||||
return true
|
||||
&& mUserId == that.mUserId
|
||||
&& java.util.Objects.equals(mEnabledHosts, that.mEnabledHosts)
|
||||
&& mDisallowLinkHandling == that.mDisallowLinkHandling;
|
||||
&& mLinkHandlingAllowed == that.mLinkHandlingAllowed;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -166,15 +178,15 @@ public class DomainVerificationUserState {
|
||||
int _hash = 1;
|
||||
_hash = 31 * _hash + mUserId;
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mEnabledHosts);
|
||||
_hash = 31 * _hash + Boolean.hashCode(mDisallowLinkHandling);
|
||||
_hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
|
||||
return _hash;
|
||||
}
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1608234273324L,
|
||||
time = 1612894390039L,
|
||||
codegenVersion = "1.0.22",
|
||||
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/domain/verify/models/DomainVerificationUserState.java",
|
||||
inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mDisallowLinkHandling\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true)")
|
||||
sourceFile = "frameworks/base/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java",
|
||||
inputSignatures = "private final @android.annotation.UserIdInt int mUserId\nprivate final @android.annotation.NonNull android.util.ArraySet<java.lang.String> mEnabledHosts\nprivate boolean mLinkHandlingAllowed\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState addHosts(java.util.Set<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(android.util.ArraySet<java.lang.String>)\npublic com.android.server.pm.verify.domain.models.DomainVerificationUserState removeHosts(java.util.Set<java.lang.String>)\nclass DomainVerificationUserState extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genSetters=true, genEqualsHashCode=true, genToString=true, genBuilder=false)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
@@ -334,6 +334,7 @@ class DomainVerificationEnforcerTest {
|
||||
this[0] = PackageUserState()
|
||||
}
|
||||
}
|
||||
whenever(getInstantApp(anyInt())) { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -104,14 +104,14 @@ class DomainVerificationPersistenceTest {
|
||||
|
||||
userSelectionStates[1] = DomainVerificationUserState(1).apply {
|
||||
addHosts(setOf("example-user1.com", "example-user1.org"))
|
||||
isDisallowLinkHandling = false
|
||||
isLinkHandlingAllowed = true
|
||||
}
|
||||
}
|
||||
val stateOne = mockEmptyPkgState(1).apply {
|
||||
// It's valid to have a user selection without any autoVerify domains
|
||||
userSelectionStates[1] = DomainVerificationUserState(1).apply {
|
||||
addHosts(setOf("example-user1.com", "example-user1.org"))
|
||||
isDisallowLinkHandling = true
|
||||
isLinkHandlingAllowed = false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -137,13 +137,15 @@ class DomainVerificationPersistenceTest {
|
||||
hasAutoVerifyDomains="true"
|
||||
>
|
||||
<state>
|
||||
<domain name="example.com" state="${DomainVerificationManager.STATE_SUCCESS}"/>
|
||||
<domain name="example.org" state="${DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
|
||||
<domain name="example.com" state="${
|
||||
DomainVerificationManager.STATE_SUCCESS}"/>
|
||||
<domain name="example.org" state="${
|
||||
DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
|
||||
<not-domain name="not-domain.com" state="1"/>
|
||||
<domain name="missing-state.com"/>
|
||||
</state>
|
||||
<user-states>
|
||||
<user-state userId="1" disallowLinkHandling="false">
|
||||
<user-state userId="1" allowLinkHandling="true">
|
||||
<enabled-hosts>
|
||||
<host name="example-user1.com"/>
|
||||
<not-host name="not-host.com"/>
|
||||
@@ -171,7 +173,7 @@ class DomainVerificationPersistenceTest {
|
||||
>
|
||||
<state/>
|
||||
<user-states>
|
||||
<user-state userId="1" disallowLinkHandling="true">
|
||||
<user-state userId="1" allowLinkHandling="false">
|
||||
<enabled-hosts>
|
||||
<host name="example-user1.com"/>
|
||||
<host name="example-user1.org"/>
|
||||
@@ -214,9 +216,9 @@ class DomainVerificationPersistenceTest {
|
||||
stateMap["$packageName.com"] = id
|
||||
userSelectionStates[id] = DomainVerificationUserState(id).apply {
|
||||
addHosts(setOf("$packageName-user.com"))
|
||||
isDisallowLinkHandling = true
|
||||
isLinkHandlingAllowed = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun pkgName(id: Int) = "${PKG_PREFIX}.pkg$id"
|
||||
private fun pkgName(id: Int) = "$PKG_PREFIX.pkg$id"
|
||||
}
|
||||
|
||||
@@ -244,6 +244,7 @@ class DomainVerificationSettingsMutationTest {
|
||||
this[0] = PackageUserState()
|
||||
}
|
||||
}
|
||||
whenever(getInstantApp(anyInt())) { false }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user