Merge changes I8d5a10c3,I3d629722,I35072d6e,I6cc270fb,I19b88fb5, ... into sc-dev

* changes:
  Remove app links caller BROWSABLE requirement
  Fix DomainVerificationShell user/allowed bugs
  Remove pending/restored domain state when attaching
  Check user ID for domain verification setters
  Expose error codes for domain verification setters
  Move domain state ints to DomainVerificationInfo
  Migrate approved/denied domain states and add tests
This commit is contained in:
Winson Chiu
2021-03-17 20:03:45 +00:00
committed by Android (Google) Code Review
32 changed files with 1751 additions and 674 deletions

View File

@@ -2837,21 +2837,29 @@ package android.content.pm.verify.domain {
method @NonNull public String getPackageName();
method public void writeToParcel(@NonNull android.os.Parcel, int);
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationInfo> CREATOR;
field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
field public static final int STATE_MODIFIABLE_UNVERIFIED = 3; // 0x3
field public static final int STATE_MODIFIABLE_VERIFIED = 4; // 0x4
field public static final int STATE_NO_RESPONSE = 0; // 0x0
field public static final int STATE_SUCCESS = 1; // 0x1
field public static final int STATE_UNMODIFIABLE = 2; // 0x2
}
public final class DomainVerificationManager {
method @Nullable @RequiresPermission(anyOf={android.Manifest.permission.DOMAIN_VERIFICATION_AGENT, android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION}) public android.content.pm.verify.domain.DomainVerificationInfo getDomainVerificationInfo(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
method @NonNull @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public java.util.List<android.content.pm.verify.domain.DomainOwner> getOwnersForDomain(@NonNull String);
method public static boolean isStateModifiable(int);
method public static boolean isStateVerified(int);
method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> queryValidVerificationPackageNames();
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationLinkHandlingAllowed(@NonNull String, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public void setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public void setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public int setDomainVerificationStatus(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, int) throws android.content.pm.PackageManager.NameNotFoundException;
method @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public int setDomainVerificationUserSelection(@NonNull java.util.UUID, @NonNull java.util.Set<java.lang.String>, boolean) throws android.content.pm.PackageManager.NameNotFoundException;
field public static final int ERROR_DOMAIN_SET_ID_INVALID = 1; // 0x1
field public static final int ERROR_DOMAIN_SET_ID_NULL = 2; // 0x2
field public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3; // 0x3
field public static final int ERROR_INVALID_STATE_CODE = 6; // 0x6
field public static final int ERROR_UNABLE_TO_APPROVE = 5; // 0x5
field public static final int ERROR_UNKNOWN_DOMAIN = 4; // 0x4
field public static final String EXTRA_VERIFICATION_REQUEST = "android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
field public static final int STATE_FIRST_VERIFIER_DEFINED = 1024; // 0x400
field public static final int STATE_NO_RESPONSE = 0; // 0x0
field public static final int STATE_SUCCESS = 1; // 0x1
field public static final int STATUS_OK = 0; // 0x0
}
public final class DomainVerificationRequest implements android.os.Parcelable {

View File

@@ -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;
}

View File

@@ -43,9 +43,50 @@ import java.util.UUID;
*/
@SystemApi
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
genEqualsHashCode = true)
genEqualsHashCode = true, genHiddenConstDefs = true)
public final class DomainVerificationInfo implements Parcelable {
// Implementation note: the following states are OUTPUT only. Any value that is synonymous with
// a value in DomainVerificationState must be the EXACT same integer, so that state
// transformation does not have to occur when sending input into the system, assuming that the
// system only accepts those synonymous values. The public API values declared here are only
// used when exiting the system server to prepare this data object for consumption by the
// verification agent. These constants should only be referenced inside public API classes.
// The server must use DomainVerificationState.
/**
* No response has been recorded by either the system or any verification agent.
*/
public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
/**
* The domain has been explicitly verified.
*/
public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
/**
* Indicates the host cannot be modified by the verification agent.
*/
public static final int STATE_UNMODIFIABLE = 2;
/**
* Indicates the host can be modified by the verification agent and is not considered verified.
*/
public static final int STATE_MODIFIABLE_UNVERIFIED = 3;
/**
* Indicates the host can be modified by the verification agent and is considered verified.
*/
public static final int STATE_MODIFIABLE_VERIFIED = 4;
/**
* The first available custom response code. This and any greater integer, along with {@link
* #STATE_SUCCESS} are the only values settable by the verification agent. All custom values
* will be treated as if the domain is unverified.
*/
public static final int STATE_FIRST_VERIFIER_DEFINED =
DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
/**
* A domain verification ID for use in later API calls. This represents the snapshot of the
* domains for a package on device, and will be invalidated whenever the package changes.
@@ -74,16 +115,12 @@ public final class DomainVerificationInfo implements Parcelable {
/**
* Map of host names to their current state. State is an integer, which defaults to {@link
* DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
* verification agent (the intended consumer of this API), which can be equal to {@link
* DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
* DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
* consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
* to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
* Any value non-inclusive between those 2 values are reserved for use by the system. The domain
* verification agent may be able to act on these reserved values, and this ability can be
* queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
* the agent attempt to verify all domains that it can modify the state of, even if it does not
* understand the meaning of those values.
* Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
* that the agent attempt to verify all domains that it can modify the state of.
*/
@NonNull
private final Map<String, Integer> mHostToStateMap;
@@ -112,6 +149,39 @@ public final class DomainVerificationInfo implements Parcelable {
//@formatter:off
/** @hide */
@android.annotation.IntDef(prefix = "STATE_", value = {
STATE_NO_RESPONSE,
STATE_SUCCESS,
STATE_UNMODIFIABLE,
STATE_MODIFIABLE_UNVERIFIED,
STATE_MODIFIABLE_VERIFIED,
STATE_FIRST_VERIFIER_DEFINED
})
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
@DataClass.Generated.Member
public @interface State {}
/** @hide */
@DataClass.Generated.Member
public static String stateToString(@State int value) {
switch (value) {
case STATE_NO_RESPONSE:
return "STATE_NO_RESPONSE";
case STATE_SUCCESS:
return "STATE_SUCCESS";
case STATE_UNMODIFIABLE:
return "STATE_UNMODIFIABLE";
case STATE_MODIFIABLE_UNVERIFIED:
return "STATE_MODIFIABLE_UNVERIFIED";
case STATE_MODIFIABLE_VERIFIED:
return "STATE_MODIFIABLE_VERIFIED";
case STATE_FIRST_VERIFIER_DEFINED:
return "STATE_FIRST_VERIFIER_DEFINED";
default: return Integer.toHexString(value);
}
}
/**
* Creates a new DomainVerificationInfo.
*
@@ -134,16 +204,12 @@ public final class DomainVerificationInfo implements Parcelable {
* The package name that this data corresponds to.
* @param hostToStateMap
* Map of host names to their current state. State is an integer, which defaults to {@link
* DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
* verification agent (the intended consumer of this API), which can be equal to {@link
* DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
* DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
* consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
* to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
* Any value non-inclusive between those 2 values are reserved for use by the system. The domain
* verification agent may be able to act on these reserved values, and this ability can be
* queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
* the agent attempt to verify all domains that it can modify the state of, even if it does not
* understand the meaning of those values.
* Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
* that the agent attempt to verify all domains that it can modify the state of.
* @hide
*/
@DataClass.Generated.Member
@@ -195,16 +261,12 @@ public final class DomainVerificationInfo implements Parcelable {
/**
* Map of host names to their current state. State is an integer, which defaults to {@link
* DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain
* verification agent (the intended consumer of this API), which can be equal to {@link
* DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link
* DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* #STATE_NO_RESPONSE}. State can be modified by the domain verification agent (the intended
* consumer of this API), which can be equal to {@link #STATE_SUCCESS} when verified, or equal
* to or greater than {@link #STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response.
* <p>
* Any value non-inclusive between those 2 values are reserved for use by the system. The domain
* verification agent may be able to act on these reserved values, and this ability can be
* queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that
* the agent attempt to verify all domains that it can modify the state of, even if it does not
* understand the meaning of those values.
* Hosts which cannot be edited will be assigned {@link #STATE_UNMODIFIABLE}. It is expected
* that the agent attempt to verify all domains that it can modify the state of.
*/
@DataClass.Generated.Member
public @NonNull Map<String,Integer> getHostToStateMap() {
@@ -320,10 +382,10 @@ public final class DomainVerificationInfo implements Parcelable {
};
@DataClass.Generated(
time = 1614721812023L,
time = 1615317187669L,
codegenVersion = "1.0.22",
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.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 java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
inputSignatures = "public static final int STATE_NO_RESPONSE\npublic static final int STATE_SUCCESS\npublic static final int STATE_UNMODIFIABLE\npublic static final int STATE_MODIFIABLE_UNVERIFIED\npublic static final int STATE_MODIFIABLE_VERIFIED\npublic static final int STATE_FIRST_VERIFIER_DEFINED\nprivate 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 java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
@Deprecated
private void __metadata() {}

View File

@@ -60,154 +60,97 @@ public final class DomainVerificationManager {
"android.content.pm.verify.domain.extra.VERIFICATION_REQUEST";
/**
* No response has been recorded by either the system or any verification agent.
* Default return code for when a method has succeeded.
*
* @hide
*/
@SystemApi
public static final int STATE_NO_RESPONSE = DomainVerificationState.STATE_NO_RESPONSE;
public static final int STATUS_OK = 0;
/**
* The verification agent has explicitly verified the domain at some point.
* The provided domain set ID was invalid, probably due to the package being updated between
* the initial request that provided the ID and the method call that used it. This usually
* means the work being processed by the verification agent is outdated and a new request
* should be scheduled, which should already be in progress as part of the
* {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
*
* @hide
*/
@SystemApi
public static final int STATE_SUCCESS = DomainVerificationState.STATE_SUCCESS;
public static final int ERROR_DOMAIN_SET_ID_INVALID = 1;
/**
* The first available custom response code. This and any greater integer, along with {@link
* #STATE_SUCCESS} are the only values settable by the verification agent. All values will be
* treated as if the domain is unverified.
* The provided domain set ID was null. This is a developer error.
*
* @hide
*/
@SystemApi
public static final int STATE_FIRST_VERIFIER_DEFINED =
DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
public static final int ERROR_DOMAIN_SET_ID_NULL = 2;
/**
* @hide
*/
@NonNull
public static String stateToDebugString(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
return "none";
case DomainVerificationState.STATE_SUCCESS:
return "verified";
case DomainVerificationState.STATE_APPROVED:
return "approved";
case DomainVerificationState.STATE_DENIED:
return "denied";
case DomainVerificationState.STATE_MIGRATED:
return "migrated";
case DomainVerificationState.STATE_RESTORED:
return "restored";
case DomainVerificationState.STATE_LEGACY_FAILURE:
return "legacy_failure";
case DomainVerificationState.STATE_SYS_CONFIG:
return "system_configured";
default:
return String.valueOf(state);
}
}
/**
* Checks if a state considers the corresponding domain to be successfully verified. The domain
* verification agent may use this to determine whether or not to re-verify a domain.
* The provided set of domains was null or empty. This is a developer error.
*
* @hide
*/
@SystemApi
public static boolean isStateVerified(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_SYS_CONFIG:
return true;
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_DENIED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
default:
return false;
}
}
public static final int ERROR_DOMAIN_SET_NULL_OR_EMPTY = 3;
/**
* Checks if a state is modifiable by the domain verification agent. This is useful as the
* platform may add new state codes in newer versions, and older verification agents can use
* this method to determine if a state can be changed without having to be aware of what the new
* state means.
* The provided set of domains contains a domain not declared by the target package. This
* usually means the work being processed by the verification agent is outdated and a new
* request should be scheduled, which should already be in progress as part of the
* {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
*
* @hide
*/
@SystemApi
public static boolean isStateModifiable(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
return true;
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_DENIED:
case DomainVerificationState.STATE_SYS_CONFIG:
return false;
default:
return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
}
}
public static final int ERROR_UNKNOWN_DOMAIN = 4;
/**
* For determine re-verify policy. This is hidden from the domain verification agent so that no
* behavior is made based on the result.
* The system was unable to select the domain for approval. This indicates another application
* has been granted a higher approval, usually through domain verification, and the target
* package is unable to override it.
*
* @hide
*/
public static boolean isStateDefault(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
return true;
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_DENIED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
case DomainVerificationState.STATE_SYS_CONFIG:
default:
return false;
}
}
@SystemApi
public static final int ERROR_UNABLE_TO_APPROVE = 5;
/**
* The provided state code is incorrect. The domain verification agent is only allowed to
* assign {@link DomainVerificationInfo#STATE_SUCCESS} or error codes equal to or greater than
* {@link DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED}.
*
* @hide
*/
public static final int ERROR_INVALID_DOMAIN_SET = 1;
@SystemApi
public static final int ERROR_INVALID_STATE_CODE = 6;
/**
* Used to communicate through {@link ServiceSpecificException}. Should not be exposed as API.
*
* @hide
*/
public static final int ERROR_NAME_NOT_FOUND = 2;
public static final int INTERNAL_ERROR_NAME_NOT_FOUND = 1;
/**
* @hide
*/
@IntDef(prefix = {"ERROR_"}, value = {
ERROR_INVALID_DOMAIN_SET,
ERROR_NAME_NOT_FOUND,
ERROR_DOMAIN_SET_ID_INVALID,
ERROR_DOMAIN_SET_ID_NULL,
ERROR_DOMAIN_SET_NULL_OR_EMPTY,
ERROR_UNKNOWN_DOMAIN,
ERROR_UNABLE_TO_APPROVE,
ERROR_INVALID_STATE_CODE
})
private @interface Error {
public @interface Error {
}
private final Context mContext;
private final IDomainVerificationManager mDomainVerificationManager;
/**
* System service to access the domain verification APIs.
* <p>
@@ -289,27 +232,24 @@ public final class DomainVerificationManager {
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains List of host names to change the state of.
* @param state See {@link DomainVerificationInfo#getHostToStateMap()}.
* @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
* invalid. This usually means the work being processed by the
* verification agent is outdated and a new request should be
* scheduled, if one has not already been done as part of the
* {@link Intent#ACTION_DOMAINS_NEED_VERIFICATION} broadcast.
* @throws NameNotFoundException If the ID is known to be good, but the package is
* unavailable. This may be because the package is installed on
* a volume that is no longer mounted. This error is
* unrecoverable until the package is available again, and
* should not be re-tried except on a time scheduled basis.
* @return error code or {@link #STATUS_OK} if successful
*
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
@DomainVerificationState.State int state) throws NameNotFoundException {
public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
int state) throws NameNotFoundException {
try {
mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
return mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(),
new DomainSet(domains), state);
} catch (Exception e) {
Exception converted = rethrow(e, domainSetId);
Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -338,7 +278,7 @@ public final class DomainVerificationManager {
mDomainVerificationManager.setDomainVerificationLinkHandlingAllowed(packageName,
allowed, mContext.getUserId());
} catch (Exception e) {
Exception converted = rethrow(e, packageName);
Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -372,24 +312,24 @@ public final class DomainVerificationManager {
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
* @param domains The domains to toggle the state of.
* @param enabled Whether or not the app should automatically open the domains specified.
* @throws IllegalArgumentException If the ID is invalidated or the {@param domains} are
* invalid.
* @throws NameNotFoundException If the ID is known to be good, but the package is
* unavailable. This may be because the package is installed on
* a volume that is no longer mounted. This error is
* unrecoverable until the package is available again, and
* should not be re-tried except on a time scheduled basis.
* @return error code or {@link #STATUS_OK} if successful
*
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled) throws NameNotFoundException {
try {
mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(),
new DomainSet(domains), enabled, mContext.getUserId());
return mDomainVerificationManager.setDomainVerificationUserSelection(
domainSetId.toString(), new DomainSet(domains), enabled, mContext.getUserId());
} catch (Exception e) {
Exception converted = rethrow(e, domainSetId);
Exception converted = rethrow(e, null);
if (converted instanceof NameNotFoundException) {
throw (NameNotFoundException) converted;
} else if (converted instanceof RuntimeException) {
@@ -447,123 +387,22 @@ public final class DomainVerificationManager {
}
}
private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
return rethrow(exception, domainSetId, null);
}
private Exception rethrow(Exception exception, @Nullable String packageName) {
return rethrow(exception, null, packageName);
}
private Exception rethrow(Exception exception, @Nullable UUID domainSetId,
@Nullable String packageName) {
if (exception instanceof ServiceSpecificException) {
int packedErrorCode = ((ServiceSpecificException) exception).errorCode;
int serviceSpecificErrorCode = ((ServiceSpecificException) exception).errorCode;
if (packageName == null) {
packageName = exception.getMessage();
}
@Error int managerErrorCode = packedErrorCode & 0xFFFF;
switch (managerErrorCode) {
case ERROR_INVALID_DOMAIN_SET:
int errorSpecificCode = packedErrorCode >> 16;
return new IllegalArgumentException(InvalidDomainSetException.buildMessage(
domainSetId, packageName, errorSpecificCode));
case ERROR_NAME_NOT_FOUND:
return new NameNotFoundException(packageName);
default:
return exception;
if (serviceSpecificErrorCode == INTERNAL_ERROR_NAME_NOT_FOUND) {
return new NameNotFoundException(packageName);
}
return exception;
} else if (exception instanceof RemoteException) {
return ((RemoteException) exception).rethrowFromSystemServer();
} else {
return exception;
}
}
/**
* Thrown if a {@link DomainVerificationInfo#getIdentifier()}} or an associated set of domains
* provided by the caller is no longer valid. This may be recoverable, and the caller should
* re-query the package name associated with the ID using
* {@link #getDomainVerificationInfo(String)}
* in order to check. If that also fails, then the package is no longer known to the device and
* thus all pending work for it should be dropped.
*
* @hide
*/
public static class InvalidDomainSetException extends IllegalArgumentException {
public static final int REASON_ID_NULL = 1;
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_UNABLE_TO_APPROVE
})
public @interface Reason {
}
public static String buildMessage(@Nullable UUID domainSetId, @Nullable String packageName,
@Reason int reason) {
switch (reason) {
case REASON_ID_NULL:
return "Domain set ID cannot be null";
case REASON_ID_INVALID:
return "Domain set ID " + domainSetId + " has been invalidated";
case REASON_SET_NULL_OR_EMPTY:
return "Domain set cannot be null or empty";
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";
}
}
@Reason
private final int mReason;
@Nullable
private final UUID mDomainSetId;
@Nullable
private final String mPackageName;
/**
* @hide
*/
public InvalidDomainSetException(@Nullable UUID domainSetId, @Nullable String packageName,
@Reason int reason) {
super(buildMessage(domainSetId, packageName, reason));
mDomainSetId = domainSetId;
mPackageName = packageName;
mReason = reason;
}
@Nullable
public UUID getDomainSetId() {
return mDomainSetId;
}
@Nullable
public String getPackageName() {
return mPackageName;
}
@Reason
public int getReason() {
return mReason;
}
}
}

View File

@@ -17,15 +17,13 @@
package android.content.pm.verify.domain;
import android.annotation.IntDef;
import android.annotation.NonNull;
/**
* @hide
*/
public interface DomainVerificationState {
/**
* @hide
*/
@IntDef({
STATE_NO_RESPONSE,
STATE_SUCCESS,
@@ -42,12 +40,12 @@ public interface DomainVerificationState {
// TODO(b/159952358): Document all the places that states need to be updated when one is added
/**
* @see DomainVerificationManager#STATE_NO_RESPONSE
* @see DomainVerificationInfo#STATE_NO_RESPONSE
*/
int STATE_NO_RESPONSE = 0;
/**
* @see DomainVerificationManager#STATE_SUCCESS
* @see DomainVerificationInfo#STATE_SUCCESS
*/
int STATE_SUCCESS = 1;
@@ -94,7 +92,132 @@ public interface DomainVerificationState {
int STATE_SYS_CONFIG = 7;
/**
* @see DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED
* @see DomainVerificationInfo#STATE_FIRST_VERIFIER_DEFINED
*/
int STATE_FIRST_VERIFIER_DEFINED = 0b10000000000;
@NonNull
static String stateToDebugString(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
return "none";
case DomainVerificationState.STATE_SUCCESS:
return "verified";
case DomainVerificationState.STATE_APPROVED:
return "approved";
case DomainVerificationState.STATE_DENIED:
return "denied";
case DomainVerificationState.STATE_MIGRATED:
return "migrated";
case DomainVerificationState.STATE_RESTORED:
return "restored";
case DomainVerificationState.STATE_LEGACY_FAILURE:
return "legacy_failure";
case DomainVerificationState.STATE_SYS_CONFIG:
return "system_configured";
default:
return String.valueOf(state);
}
}
/**
* For determining re-verify policy. This is hidden from the domain verification agent so that
* no behavior is made based on the result.
*/
static boolean isDefault(@State int state) {
switch (state) {
case STATE_NO_RESPONSE:
case STATE_MIGRATED:
case STATE_RESTORED:
return true;
case STATE_SUCCESS:
case STATE_APPROVED:
case STATE_DENIED:
case STATE_LEGACY_FAILURE:
case STATE_SYS_CONFIG:
default:
return false;
}
}
/**
* Checks if a state considers the corresponding domain to be successfully verified. The domain
* verification agent may use this to determine whether or not to re-verify a domain.
*/
static boolean isVerified(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_SYS_CONFIG:
return true;
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_DENIED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
default:
return false;
}
}
/**
* Checks if a state is modifiable by the domain verification agent. This is useful as the
* platform may add new state codes in newer versions, and older verification agents can use
* this method to determine if a state can be changed without having to be aware of what the new
* state means.
*/
static boolean isModifiable(@DomainVerificationState.State int state) {
switch (state) {
case DomainVerificationState.STATE_NO_RESPONSE:
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_MIGRATED:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_LEGACY_FAILURE:
return true;
case DomainVerificationState.STATE_APPROVED:
case DomainVerificationState.STATE_DENIED:
case DomainVerificationState.STATE_SYS_CONFIG:
return false;
default:
return state >= DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED;
}
}
/**
* Whether the state is migrated when updating a package. Generally this is only for states
* that maintain verification state or were set by an explicit user or developer action.
*/
static boolean shouldMigrate(@State int state) {
switch (state) {
case STATE_SUCCESS:
case STATE_MIGRATED:
case STATE_RESTORED:
case STATE_APPROVED:
case STATE_DENIED:
return true;
case STATE_NO_RESPONSE:
case STATE_LEGACY_FAILURE:
case STATE_SYS_CONFIG:
case STATE_FIRST_VERIFIER_DEFINED:
default:
return false;
}
}
@DomainVerificationInfo.State
static int convertToInfoState(@State int internalState) {
if (internalState >= STATE_FIRST_VERIFIER_DEFINED) {
return internalState;
} else if (internalState == STATE_NO_RESPONSE) {
return DomainVerificationInfo.STATE_NO_RESPONSE;
} else if (internalState == STATE_SUCCESS) {
return DomainVerificationInfo.STATE_SUCCESS;
} else if (!isModifiable(internalState)) {
return DomainVerificationInfo.STATE_UNMODIFIABLE;
} else if (isVerified(internalState)) {
return DomainVerificationInfo.STATE_MODIFIABLE_VERIFIED;
} else {
return DomainVerificationInfo.STATE_MODIFIABLE_UNVERIFIED;
}
}
}

View File

@@ -40,10 +40,10 @@ interface IDomainVerificationManager {
@nullable
List<DomainOwner> getOwnersForDomain(String domain, int userId);
void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
int setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
int setDomainVerificationUserSelection(String domainSetId, in DomainSet domains,
boolean enabled, int userId);
}

View File

@@ -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;

View File

@@ -1768,6 +1768,11 @@ public class PackageManagerService extends IPackageManager.Stub
public int[] getAllUserIds() {
return mUserManager.getUserIds();
}
@Override
public boolean doesUserExist(@UserIdInt int userId) {
return mUserManager.exists(userId);
}
}
/**
@@ -2639,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
@@ -25693,6 +25697,7 @@ public class PackageManagerService extends IPackageManager.Stub
if (!convertedFromPreCreated || !readPermissionStateForUser(userId)) {
mPermissionManager.onUserCreated(userId);
mLegacyPermissionManager.grantDefaultPermissions(userId);
mDomainVerificationManager.clearUser(userId);
}
}

View File

@@ -21,7 +21,6 @@ import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationState;
import android.os.UserHandle;
import android.util.ArrayMap;
@@ -151,7 +150,7 @@ public class DomainVerificationDebug {
Integer state = reusedMap.valueAt(stateIndex);
writer.print(domain);
writer.print(": ");
writer.println(DomainVerificationManager.stateToDebugString(state));
writer.println(DomainVerificationState.stateToDebugString(state));
}
writer.decreaseIndent();
writer.decreaseIndent();

View File

@@ -141,6 +141,12 @@ public class DomainVerificationEnforcer {
"Caller is not allowed to edit other users");
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -161,6 +167,12 @@ public class DomainVerificationEnforcer {
Binder.getCallingPid(), callingUid,
"Caller is not allowed to edit user selections");
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
if (packageName == null) {
return true;
}
@@ -184,6 +196,12 @@ public class DomainVerificationEnforcer {
}
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -197,6 +215,12 @@ public class DomainVerificationEnforcer {
"Caller is not allowed to edit other users");
}
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
}
@@ -221,6 +245,12 @@ public class DomainVerificationEnforcer {
mContext.enforcePermission(
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
callingPid, callingUid, "Caller is not allowed to query user selections");
if (!mCallback.doesUserExist(callingUserId)) {
throw new SecurityException("User " + callingUserId + " does not exist");
} else if (!mCallback.doesUserExist(targetUserId)) {
throw new SecurityException("User " + targetUserId + " does not exist");
}
}
public interface Callback {
@@ -229,5 +259,7 @@ public class DomainVerificationEnforcer {
* if the package was not installed
*/
boolean filterAppAccess(@NonNull String packageName, int callingUid, @UserIdInt int userId);
boolean doesUserExist(@UserIdInt int userId);
}
}

View File

@@ -333,10 +333,10 @@ public interface DomainVerificationManagerInternal {
@Nullable
UUID getDomainVerificationInfoId(@NonNull String packageName);
@DomainVerificationManager.Error
@RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT)
void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
@NonNull Set<String> domains, int state)
throws IllegalArgumentException, NameNotFoundException;
int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
@NonNull Set<String> domains, int state) throws NameNotFoundException;
interface Connection extends DomainVerificationEnforcer.Callback {

View File

@@ -24,7 +24,6 @@ import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainSet;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
import android.os.ServiceSpecificException;
@@ -61,11 +60,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St
}
}
@DomainVerificationManager.Error
@Override
public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet,
public int setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet,
int state) {
try {
mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
return mService.setDomainVerificationStatus(UUID.fromString(domainSetId),
domainSet.getDomains(), state);
} catch (Exception e) {
throw rethrow(e);
@@ -82,11 +82,12 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St
}
}
@DomainVerificationManager.Error
@Override
public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet,
public int setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet,
boolean enabled, @UserIdInt int userId) {
try {
mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
return mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId),
domainSet.getDomains(), enabled, userId);
} catch (Exception e) {
throw rethrow(e);
@@ -116,14 +117,9 @@ public class DomainVerificationManagerStub extends IDomainVerificationManager.St
}
private RuntimeException rethrow(Exception exception) throws RuntimeException {
if (exception instanceof InvalidDomainSetException) {
int packedErrorCode = DomainVerificationManager.ERROR_INVALID_DOMAIN_SET;
packedErrorCode |= ((InvalidDomainSetException) exception).getReason() << 16;
return new ServiceSpecificException(packedErrorCode,
((InvalidDomainSetException) exception).getPackageName());
} else if (exception instanceof NameNotFoundException) {
if (exception instanceof NameNotFoundException) {
return new ServiceSpecificException(
DomainVerificationManager.ERROR_NAME_NOT_FOUND);
DomainVerificationManager.INTERNAL_ERROR_NAME_NOT_FOUND);
} else if (exception instanceof RuntimeException) {
return (RuntimeException) exception;
} else {

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.verify.domain;
import static java.util.Collections.emptyList;
import static java.util.Collections.emptySet;
import android.annotation.CheckResult;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
@@ -34,7 +35,6 @@ import android.content.pm.parsing.component.ParsedActivity;
import android.content.pm.verify.domain.DomainOwner;
import android.content.pm.verify.domain.DomainVerificationInfo;
import android.content.pm.verify.domain.DomainVerificationManager;
import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException;
import android.content.pm.verify.domain.DomainVerificationState;
import android.content.pm.verify.domain.DomainVerificationUserState;
import android.content.pm.verify.domain.IDomainVerificationManager;
@@ -243,17 +243,17 @@ public class DomainVerificationService extends SystemService
throws NameNotFoundException {
mEnforcer.assertApprovedQuerent(mConnection.getCallingUid(), mProxy);
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
Map<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArrayMap<String, Integer> hostToStateMap = new ArrayMap<>(pkgState.getStateMap());
// TODO(b/159952358): Should the domain list be cached?
ArraySet<String> domains = mCollector.collectValidAutoVerifyDomains(pkg);
@@ -267,44 +267,56 @@ public class DomainVerificationService extends SystemService
DomainVerificationState.STATE_NO_RESPONSE);
}
final int mapSize = hostToStateMap.size();
for (int index = 0; index < mapSize; index++) {
int internalValue = hostToStateMap.valueAt(index);
int publicValue = DomainVerificationState.convertToInfoState(internalValue);
hostToStateMap.setValueAt(index, publicValue);
}
// TODO(b/159952358): Do not return if no values are editable (all ignored states)?
return new DomainVerificationInfo(pkgState.getId(), packageName, hostToStateMap);
}
}
public void setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
int state) throws InvalidDomainSetException, NameNotFoundException {
@DomainVerificationManager.Error
public int setDomainVerificationStatus(@NonNull UUID domainSetId, @NonNull Set<String> domains,
int state) throws NameNotFoundException {
if (state < DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED) {
if (state != DomainVerificationState.STATE_SUCCESS) {
throw new IllegalArgumentException(
"Verifier can only set STATE_SUCCESS or codes greater than or equal to "
+ "STATE_FIRST_VERIFIER_DEFINED");
return DomainVerificationManager.ERROR_INVALID_STATE_CODE;
}
}
setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId, domains,
state);
return setDomainVerificationStatusInternal(mConnection.getCallingUid(), domainSetId,
domains, state);
}
@DomainVerificationManager.Error
@Override
public void setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
public int setDomainVerificationStatusInternal(int callingUid, @NonNull UUID domainSetId,
@NonNull Set<String> domains, int state)
throws InvalidDomainSetException, NameNotFoundException {
throws NameNotFoundException {
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
synchronized (mLock) {
List<String> verifiedDomains = new ArrayList<>();
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
true /* forAutoVerify */, callingUid, null /* userId */);
if (result.isError()) {
return result.getErrorCode();
}
DomainVerificationPkgState pkgState = result.getPkgState();
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
for (String domain : domains) {
Integer previousState = stateMap.get(domain);
if (previousState != null
&& !DomainVerificationManager.isStateModifiable(previousState)) {
&& !DomainVerificationState.isModifiable(previousState)) {
continue;
}
if (DomainVerificationManager.isStateVerified(state)) {
if (DomainVerificationState.isVerified(state)) {
verifiedDomains.add(domain);
}
@@ -318,6 +330,7 @@ public class DomainVerificationService extends SystemService
}
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
}
@Override
@@ -460,17 +473,24 @@ public class DomainVerificationService extends SystemService
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
pkgState.getOrCreateUserState(userId)
.setLinkHandlingAllowed(allowed);
if (userId == UserHandle.USER_ALL) {
for (int aUserId : mConnection.getAllUserIds()) {
pkgState.getOrCreateUserState(aUserId)
.setLinkHandlingAllowed(allowed);
}
} else {
pkgState.getOrCreateUserState(userId)
.setLinkHandlingAllowed(allowed);
}
}
}
mConnection.scheduleWriteSettings();
}
public void setDomainVerificationUserSelection(@NonNull UUID domainSetId,
public int setDomainVerificationUserSelection(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean enabled, @UserIdInt int userId)
throws InvalidDomainSetException, NameNotFoundException {
throws NameNotFoundException {
synchronized (mLock) {
final int callingUid = mConnection.getCallingUid();
// Pass null for package name here and do the app visibility enforcement inside
@@ -478,14 +498,17 @@ public class DomainVerificationService extends SystemService
// ID reason if the target app is invisible
if (!mEnforcer.assertApprovedUserSelector(callingUid, mConnection.getCallingUserId(),
null /* packageName */, userId)) {
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_ID_INVALID);
return DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID;
}
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
GetAttachedResult result = getAndValidateAttachedLocked(domainSetId, domains,
false /* forAutoVerify */, callingUid, userId);
DomainVerificationInternalUserState userState =
pkgState.getOrCreateUserState(userId);
if (result.isError()) {
return result.getErrorCode();
}
DomainVerificationPkgState pkgState = result.getPkgState();
DomainVerificationInternalUserState userState = pkgState.getOrCreateUserState(userId);
// Disable other packages if approving this one. Note that this check is only done for
// enabling. This allows an escape hatch in case multiple packages somehow get selected.
@@ -503,8 +526,7 @@ public class DomainVerificationService extends SystemService
userId, APPROVAL_LEVEL_NONE + 1, mConnection::getPackageSettingLocked);
int highestApproval = packagesToLevel.second;
if (highestApproval > APPROVAL_LEVEL_SELECTION) {
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_UNABLE_TO_APPROVE);
return DomainVerificationManager.ERROR_UNABLE_TO_APPROVE;
}
domainToApprovedPackages.put(domain, packagesToLevel.first);
@@ -544,6 +566,7 @@ public class DomainVerificationService extends SystemService
}
mConnection.scheduleWriteSettings();
return DomainVerificationManager.STATUS_OK;
}
@Override
@@ -636,16 +659,16 @@ public class DomainVerificationService extends SystemService
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
synchronized (mLock) {
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
AndroidPackage pkg = mConnection.getPackageLocked(packageName);
if (pkg == null) {
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(packageName);
if (pkgState == null) {
return null;
}
ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
int webDomainsSize = webDomains.size();
@@ -659,7 +682,7 @@ public class DomainVerificationService extends SystemService
Integer state = stateMap.get(host);
int domainState;
if (state != null && DomainVerificationManager.isStateVerified(state)) {
if (state != null && DomainVerificationState.isVerified(state)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_VERIFIED;
} else if (enabledHosts.contains(host)) {
domainState = DomainVerificationUserState.DOMAIN_STATE_SELECTED;
@@ -793,19 +816,12 @@ public class DomainVerificationService extends SystemService
Integer oldStateInteger = oldStateMap.get(domain);
if (oldStateInteger != null) {
int oldState = oldStateInteger;
switch (oldState) {
case DomainVerificationState.STATE_SUCCESS:
case DomainVerificationState.STATE_RESTORED:
case DomainVerificationState.STATE_MIGRATED:
newStateMap.put(domain, oldState);
break;
default:
// In all other cases, the state code is left unset
// (STATE_NO_RESPONSE) to signal to the verification agent that any
// existing error has been cleared and the domain should be
// re-attempted. This makes update of a package a signal to
// re-verify.
break;
// If the following case fails, the state code is left unset
// (STATE_NO_RESPONSE) to signal to the verification agent that any existing
// error has been cleared and the domain should be re-attempted. This makes
// update of a package a signal to re-verify.
if (DomainVerificationState.shouldMigrate(oldState)) {
newStateMap.put(domain, oldState);
}
}
}
@@ -858,13 +874,13 @@ public class DomainVerificationService extends SystemService
boolean sendBroadcast = true;
DomainVerificationPkgState pkgState;
pkgState = mSettings.getPendingState(pkgName);
pkgState = mSettings.removePendingState(pkgName);
if (pkgState != null) {
// Don't send when attaching from pending read, which is usually boot scan. Re-send on
// boot is handled in a separate method once all packages are added.
sendBroadcast = false;
} else {
pkgState = mSettings.getRestoredState(pkgName);
pkgState = mSettings.removeRestoredState(pkgName);
}
AndroidPackage pkg = newPkgSetting.getPkg();
@@ -872,7 +888,7 @@ public class DomainVerificationService extends SystemService
boolean hasAutoVerifyDomains = !domains.isEmpty();
boolean isPendingOrRestored = pkgState != null;
if (isPendingOrRestored) {
pkgState.setId(domainSetId);
pkgState = new DomainVerificationPkgState(pkgState, domainSetId, hasAutoVerifyDomains);
} else {
pkgState = new DomainVerificationPkgState(pkgName, domainSetId, hasAutoVerifyDomains);
}
@@ -1097,28 +1113,25 @@ public class DomainVerificationService extends SystemService
* @param userIdForFilter which user to filter app access to, or null if the caller has already
* validated package visibility
*/
@CheckResult
@GuardedBy("mLock")
private DomainVerificationPkgState getAndValidateAttachedLocked(@NonNull UUID domainSetId,
private GetAttachedResult getAndValidateAttachedLocked(@NonNull UUID domainSetId,
@NonNull Set<String> domains, boolean forAutoVerify, int callingUid,
@Nullable Integer userIdForFilter)
throws InvalidDomainSetException, NameNotFoundException {
@Nullable Integer userIdForFilter) throws NameNotFoundException {
if (domainSetId == null) {
throw new InvalidDomainSetException(null, null,
InvalidDomainSetException.REASON_ID_NULL);
return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL);
}
DomainVerificationPkgState pkgState = mAttachedPkgStates.get(domainSetId);
if (pkgState == null) {
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_ID_INVALID);
return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
}
String pkgName = pkgState.getPackageName();
if (userIdForFilter != null
&& mConnection.filterAppAccess(pkgName, callingUid, userIdForFilter)) {
throw new InvalidDomainSetException(domainSetId, null,
InvalidDomainSetException.REASON_ID_INVALID);
return GetAttachedResult.error(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID);
}
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(pkgName);
@@ -1127,8 +1140,8 @@ public class DomainVerificationService extends SystemService
}
if (CollectionUtils.isEmpty(domains)) {
throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
InvalidDomainSetException.REASON_SET_NULL_OR_EMPTY);
return GetAttachedResult.error(
DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY);
}
AndroidPackage pkg = pkgSetting.getPkg();
ArraySet<String> declaredDomains = forAutoVerify
@@ -1136,11 +1149,10 @@ public class DomainVerificationService extends SystemService
: mCollector.collectAllWebDomains(pkg);
if (domains.retainAll(declaredDomains)) {
throw new InvalidDomainSetException(domainSetId, pkgState.getPackageName(),
InvalidDomainSetException.REASON_UNKNOWN_DOMAIN);
return GetAttachedResult.error(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN);
}
return pkgState;
return GetAttachedResult.success(pkgState);
}
@Override
@@ -1185,7 +1197,7 @@ public class DomainVerificationService extends SystemService
/**
* Determine whether or not a broadcast should be sent at boot for the given {@param pkgState}.
* Sends only if the only states recorded are default as decided by {@link
* DomainVerificationManager#isStateDefault(int)}.
* DomainVerificationState#isDefault(int)}.
*
* If any other state is set, it's assumed that the domain verification agent is aware of the
* package and has already scheduled future verification requests.
@@ -1199,7 +1211,7 @@ public class DomainVerificationService extends SystemService
int statesSize = stateMap.size();
for (int stateIndex = 0; stateIndex < statesSize; stateIndex++) {
Integer state = stateMap.valueAt(stateIndex);
if (!DomainVerificationManager.isStateDefault(state)) {
if (!DomainVerificationState.isDefault(state)) {
return false;
}
}
@@ -1318,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);
@@ -1403,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;
}
@NonNull
private List<ResolveInfo> filterToLastDeclared(
@NonNull ArrayMap<String, List<ResolveInfo>> inputMap,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
List<ResolveInfo> finalList = new ArrayList<>(inputMap.size());
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);
}
}
}
int inputSize = inputMap.size();
for (int inputIndex = 0; inputIndex < inputSize; inputIndex++) {
String packageName = inputMap.keyAt(inputIndex);
List<ResolveInfo> infos = inputMap.valueAt(inputIndex);
@NonNull
private void filterToLastFirstInstalled(@NonNull ArrayMap<ResolveInfo, Integer> inputMap,
@NonNull Function<String, PackageSetting> pkgSettingFunction) {
// 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
@@ -1472,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");
}
@@ -1542,7 +1579,7 @@ public class DomainVerificationService extends SystemService
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
// Check if the exact host matches
Integer state = stateMap.get(host);
if (state != null && DomainVerificationManager.isStateVerified(state)) {
if (state != null && DomainVerificationState.isVerified(state)) {
if (DEBUG_APPROVAL) {
debugApproval(packageName, debugObject, userId, true,
"host verified exactly");
@@ -1553,7 +1590,7 @@ public class DomainVerificationService extends SystemService
// 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))) {
if (!DomainVerificationState.isVerified(stateMap.valueAt(index))) {
continue;
}
@@ -1672,4 +1709,40 @@ public class DomainVerificationService extends SystemService
Slog.d(TAG + "Approval", packageName + " was " + approvalString + " for "
+ debugObject + " for user " + userId + ": " + reason);
}
private static class GetAttachedResult {
@Nullable
private DomainVerificationPkgState mPkgState;
private int mErrorCode;
GetAttachedResult(@Nullable DomainVerificationPkgState pkgState, int errorCode) {
mPkgState = pkgState;
mErrorCode = errorCode;
}
@NonNull
static GetAttachedResult error(@DomainVerificationManager.Error int errorCode) {
return new GetAttachedResult(null, errorCode);
}
@NonNull
static GetAttachedResult success(@NonNull DomainVerificationPkgState pkgState) {
return new GetAttachedResult(pkgState, DomainVerificationManager.STATUS_OK);
}
@NonNull
DomainVerificationPkgState getPkgState() {
return mPkgState;
}
boolean isError() {
return mErrorCode != DomainVerificationManager.STATUS_OK;
}
public int getErrorCode() {
return mErrorCode;
}
}
}

View File

@@ -256,16 +256,16 @@ class DomainVerificationSettings {
}
@Nullable
public DomainVerificationPkgState getPendingState(@NonNull String pkgName) {
public DomainVerificationPkgState removePendingState(@NonNull String pkgName) {
synchronized (mLock) {
return mPendingPkgStates.get(pkgName);
return mPendingPkgStates.remove(pkgName);
}
}
@Nullable
public DomainVerificationPkgState getRestoredState(@NonNull String pkgName) {
public DomainVerificationPkgState removeRestoredState(@NonNull String pkgName) {
synchronized (mLock) {
return mRestoredPkgStates.get(pkgName);
return mRestoredPkgStates.remove(pkgName);
}
}
}

View File

@@ -38,6 +38,8 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.function.Function;
public class DomainVerificationShell {
@@ -226,22 +228,19 @@ public class DomainVerificationShell {
userId = translateUserId(userId, "runSetAppLinksUserState");
String enabledString = commandHandler.getNextArgRequired();
String enabledArg = commandHandler.getNextArg();
if (TextUtils.isEmpty(enabledArg)) {
commandHandler.getErrPrintWriter().println("Error: enabled param not specified");
return false;
}
// Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
// accidentally parsed as a boolean
boolean enabled;
switch (enabledString) {
case "true":
enabled = true;
break;
case "false":
enabled = false;
break;
default:
commandHandler.getErrPrintWriter().println(
"Invalid enabled param: " + enabledString);
return false;
try {
enabled = parseEnabled(enabledArg);
} catch (IllegalArgumentException e) {
commandHandler.getErrPrintWriter()
.println("Error: invalid enabled param: " + e.getMessage());
return false;
}
ArraySet<String> domains = new ArraySet<>(getRemainingArgs(commandHandler));
@@ -255,8 +254,8 @@ public class DomainVerificationShell {
}
try {
mCallback.setDomainVerificationUserSelectionInternal(userId,
packageName, enabled, domains);
mCallback.setDomainVerificationUserSelectionInternal(userId, packageName, enabled,
domains);
} catch (NameNotFoundException e) {
commandHandler.getErrPrintWriter().println("Package not found: " + packageName);
return false;
@@ -362,15 +361,12 @@ public class DomainVerificationShell {
private boolean runSetAppLinksAllowed(@NonNull BasicShellCommandHandler commandHandler) {
String packageName = null;
Integer userId = null;
Boolean allowed = null;
String option;
while ((option = commandHandler.getNextOption()) != null) {
if (option.equals("--package")) {
packageName = commandHandler.getNextArgRequired();
} if (option.equals("--user")) {
packageName = commandHandler.getNextArg();
} else if (option.equals("--user")) {
userId = UserHandle.parseUserArg(commandHandler.getNextArgRequired());
} else if (allowed == null) {
allowed = Boolean.valueOf(option);
} else {
commandHandler.getErrPrintWriter().println("Error: unexpected option: " + option);
return false;
@@ -389,11 +385,21 @@ public class DomainVerificationShell {
return false;
}
if (allowed == null) {
String allowedArg = commandHandler.getNextArg();
if (TextUtils.isEmpty(allowedArg)) {
commandHandler.getErrPrintWriter().println("Error: allowed setting not specified");
return false;
}
boolean allowed;
try {
allowed = parseEnabled(allowedArg);
} catch (IllegalArgumentException e) {
commandHandler.getErrPrintWriter()
.println("Error: invalid allowed setting: " + e.getMessage());
return false;
}
userId = translateUserId(userId, "runSetAppLinksAllowed");
try {
@@ -421,6 +427,22 @@ public class DomainVerificationShell {
userId, true, true, logContext, "pm command");
}
/**
* Manually ensure that "true" and "false" are the only options, to ensure a domain isn't
* accidentally parsed as a boolean.
*/
@NonNull
private boolean parseEnabled(@NonNull String arg) throws IllegalArgumentException {
switch (arg.toLowerCase(Locale.US)) {
case "true":
return true;
case "false":
return false;
default:
throw new IllegalArgumentException(arg + " is not a valid boolean");
}
}
/**
* Separated interface from {@link DomainVerificationManagerInternal} to hide methods that are
* even more internal, and so that testing is easier.
@@ -498,7 +520,8 @@ public class DomainVerificationShell {
void verifyPackages(@Nullable List<String> packageNames, boolean reVerify);
/**
* @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer)
* @see DomainVerificationManagerInternal#printState(IndentingPrintWriter, String, Integer,
* Function)
*/
void printState(@NonNull IndentingPrintWriter writer, @Nullable String packageName,
@Nullable @UserIdInt Integer userId) throws NameNotFoundException;

View File

@@ -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);
}
}

View File

@@ -68,6 +68,12 @@ public class DomainVerificationPkgState {
this(packageName, id, hasAutoVerifyDomains, new ArrayMap<>(0), new SparseArray<>(0));
}
public DomainVerificationPkgState(@NonNull DomainVerificationPkgState pkgState,
@NonNull UUID id, boolean hasAutoVerifyDomains) {
this(pkgState.getPackageName(), id, hasAutoVerifyDomains, pkgState.getStateMap(),
pkgState.getUserStates());
}
@Nullable
public DomainVerificationInternalUserState getUserState(@UserIdInt int userId) {
return mUserStates.get(userId);
@@ -84,10 +90,6 @@ public class DomainVerificationPkgState {
return userState;
}
public void setId(@NonNull UUID id) {
mId = id;
}
public void removeUser(@UserIdInt int userId) {
mUserStates.remove(userId);
}

View File

@@ -207,20 +207,24 @@ public class DomainVerificationProxyV1 implements DomainVerificationProxy {
int callingUid = response.callingUid;
if (!successfulDomains.isEmpty()) {
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
successfulDomains, DomainVerificationState.STATE_SUCCESS);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException e) {
if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
successfulDomains, DomainVerificationState.STATE_SUCCESS)
!= DomainVerificationManager.STATUS_OK) {
Slog.e(TAG, "Failure reporting successful domains for " + packageName);
}
} catch (Exception e) {
Slog.e(TAG, "Failure reporting successful domains for " + packageName, e);
}
}
if (!failedDomains.isEmpty()) {
try {
mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE);
} catch (DomainVerificationManager.InvalidDomainSetException
| PackageManager.NameNotFoundException e) {
if (mManager.setDomainVerificationStatusInternal(callingUid, domainSetId,
failedDomains, DomainVerificationState.STATE_LEGACY_FAILURE)
!= DomainVerificationManager.STATUS_OK) {
Slog.e(TAG, "Failure reporting failed domains for " + packageName);
}
} catch (Exception e) {
Slog.e(TAG, "Failure reporting failed domains for " + packageName, e);
}
}

View File

@@ -23,14 +23,17 @@ package {
android_test {
name: "PackageManagerServiceUnitTests",
srcs: ["src/**/*.kt"],
srcs: [
"src/**/*.java",
"src/**/*.kt",
],
static_libs: [
"androidx.test.rules",
"androidx.test.runner",
"junit",
"kotlin-test",
"services.core",
"servicestests-utils",
"testng",
"truth-prebuilt",
],
platform_apis: true,

View File

@@ -25,8 +25,8 @@ import android.os.PatternMatcher
import android.util.ArraySet
import com.android.server.SystemConfig
import com.android.server.compat.PlatformCompat
import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat

View File

@@ -19,6 +19,7 @@ package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainSet
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Parcel
import android.os.Parcelable
@@ -74,7 +75,9 @@ class DomainVerificationCoreApiTest {
DomainVerificationInfo(
UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"),
"com.test.pkg",
massiveSet.withIndex().associate { it.value to it.index }
massiveSet.withIndex().associate {
it.value to DomainVerificationState.convertToInfoState(it.index)
}
)
},
unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) },

View File

@@ -20,20 +20,21 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
import android.util.SparseArray
import androidx.test.platform.app.InstrumentationRegistry
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationEnforcer
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.spyThrowOnUnmocked
import com.android.server.testutils.whenever
@@ -50,6 +51,8 @@ import java.io.File
import java.util.UUID
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger
import kotlin.test.assertFailsWith
import kotlin.test.fail
@RunWith(Parameterized::class)
class DomainVerificationEnforcerTest {
@@ -81,47 +84,49 @@ class DomainVerificationEnforcerTest {
whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
true
}
whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 }
})
}
}
val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger, DomainVerificationService> =
{
val callingUidInt = AtomicInteger(-1)
val callingUserIdInt = AtomicInteger(-1)
val makeService: (Context) -> Triple<AtomicInteger, AtomicInteger,
DomainVerificationService> = {
val callingUidInt = AtomicInteger(-1)
val callingUserIdInt = AtomicInteger(-1)
val connection: DomainVerificationManagerInternal.Connection =
mockThrowOnUnmocked {
whenever(callingUid) { callingUidInt.get() }
whenever(callingUserId) { callingUserIdInt.get() }
whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())
whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
true
}
val connection: DomainVerificationManagerInternal.Connection =
mockThrowOnUnmocked {
whenever(callingUid) { callingUidInt.get() }
whenever(callingUserId) { callingUserIdInt.get() }
whenever(getPackageSettingLocked(VISIBLE_PKG)) { visiblePkgSetting }
whenever(getPackageLocked(VISIBLE_PKG)) { visiblePkg }
whenever(getPackageSettingLocked(INVISIBLE_PKG)) { invisiblePkgSetting }
whenever(getPackageLocked(INVISIBLE_PKG)) { invisiblePkg }
whenever(schedule(anyInt(), any()))
whenever(scheduleWriteSettings())
whenever(filterAppAccess(eq(VISIBLE_PKG), anyInt(), anyInt())) { false }
whenever(filterAppAccess(eq(INVISIBLE_PKG), anyInt(), anyInt())) {
true
}
val service = DomainVerificationService(
it,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
whenever(
isChangeEnabled(
anyLong(),
any()
)
) { true }
}).apply {
setConnection(connection)
whenever(doesUserExist(anyInt())) { (arguments[0] as Int) <= 1 }
}
Triple(callingUidInt, callingUserIdInt, service)
val service = DomainVerificationService(
it,
mockThrowOnUnmocked { whenever(linkedApps) { ArraySet<String>() } },
mockThrowOnUnmocked {
whenever(
isChangeEnabled(
anyLong(),
any()
)
) { true }
}).apply {
setConnection(connection)
}
Triple(callingUidInt, callingUserIdInt, service)
}
fun enforcer(
type: Type,
name: String,
@@ -175,7 +180,7 @@ class DomainVerificationEnforcerTest {
service(Type.INTERNAL, "setStatusInternalPackageName") {
setDomainVerificationStatusInternal(
it.targetPackageName,
DomainVerificationManager.STATE_SUCCESS,
DomainVerificationState.STATE_SUCCESS,
ArraySet(setOf("example.com"))
)
},
@@ -206,7 +211,7 @@ class DomainVerificationEnforcerTest {
setDomainVerificationStatus(
it.targetDomainSetId,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
DomainVerificationState.STATE_SUCCESS
)
},
service(Type.VERIFIER, "setStatusInternalUid") {
@@ -214,7 +219,7 @@ class DomainVerificationEnforcerTest {
it.callingUid,
it.targetDomainSetId,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
DomainVerificationState.STATE_SUCCESS
)
},
service(Type.SELECTOR_USER, "setLinkHandlingAllowedUserId") {
@@ -475,24 +480,7 @@ class DomainVerificationEnforcerTest {
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// User selector makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
if (throws) {
allUids.forEach {
assertFails {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
// User selector doesn't use QUERY_ALL, so the invisible package should always fail
allUids.forEach {
assertFails {
runMethod(target, it, visible = false, callingUserId, targetUserId)
}
}
runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws)
}
val callingUserId = 0
@@ -529,24 +517,7 @@ class DomainVerificationEnforcerTest {
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// User selector makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
if (throws) {
allUids.forEach {
assertFails {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
// User selector doesn't use QUERY_ALL, so the invisible package should always fail
allUids.forEach {
assertFails {
runMethod(target, it, visible = false, callingUserId, targetUserId)
}
}
runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws)
}
val callingUserId = 0
@@ -590,24 +561,10 @@ class DomainVerificationEnforcerTest {
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Legacy makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
if (throws) {
allUids.forEach {
assertFails {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
// Legacy doesn't use QUERY_ALL, so the invisible package should always fail
allUids.forEach {
assertFails {
runMethod(target, it, visible = false, callingUserId, targetUserId)
}
}
// The legacy selector does a silent failure when the user IDs don't match, so it
// cannot verify the non-existent user ID check, as it will not throw an Exception.
runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
verifyUserIdCheck = false)
}
val callingUserId = 0
@@ -642,27 +599,29 @@ class DomainVerificationEnforcerTest {
}
val target = params.construct(context)
// Legacy code can return PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
// as an error code. This is distinct from the class level assertFails as unfortunately
// the same number, 0, is used in opposite contexts, where it does represent a failure
// for this legacy case, but not for the modern APIs.
fun assertFailsLegacy(block: () -> Any?) {
try {
val value = block()
if ((value as? Int)
!= PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
) {
throw AssertionError("Expected call to return false, was $value")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
// Any of these 2 exceptions are considered failures, which is expected
}
}
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Legacy makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
if (throws) {
allUids.forEach {
assertFails {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
// Legacy doesn't use QUERY_ALL, so the invisible package should always fail
allUids.forEach {
assertFails {
runMethod(target, it, visible = false, callingUserId, targetUserId)
}
}
runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
assertFailsMethod = ::assertFailsLegacy)
}
val callingUserId = 0
@@ -704,17 +663,8 @@ class DomainVerificationEnforcerTest {
fun runTestCases(callingUserId: Int, targetUserId: Int, throws: Boolean) {
// Owner querent makes no distinction by UID
val allUids = INTERNAL_UIDS + VERIFIER_UID + NON_VERIFIER_UID
if (throws) {
allUids.forEach {
assertFails {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
runCrossUserMethod(allUids, target, callingUserId, targetUserId, throws,
verifyInvisiblePkg = false)
}
val callingUserId = 0
@@ -782,22 +732,88 @@ class DomainVerificationEnforcerTest {
return params.runMethod(target, callingUid, callingUserId, userId, packageName, uuid, proxy)
}
private fun runCrossUserMethod(
allUids: Iterable<Int>,
target: Any,
callingUserId: Int,
targetUserId: Int,
throws: Boolean,
verifyUserIdCheck: Boolean = true,
verifyInvisiblePkg: Boolean = true,
assertFailsMethod: (() -> Any?) -> Unit = ::assertFails,
) {
if (throws) {
allUids.forEach {
assertFailsMethod {
// When testing a non-user ID failure, send an invalid user ID.
// This ensures the failure occurs before the user ID check is run.
try {
runMethod(target, it, visible = true, callingUserId, 100)
} catch (e: SecurityException) {
if (verifyUserIdCheck) {
e.message?.let {
if (it.contains("user ID", ignoreCase = true)
|| it.contains("100")) {
fail(
"Method should not check user existence before permissions"
)
}
}
}
// Rethrow to allow normal fail checking logic to run
throw e
}
}
}
} else {
allUids.forEach {
runMethod(target, it, visible = true, callingUserId, targetUserId)
}
}
if (verifyInvisiblePkg) {
allUids.forEach {
assertFailsMethod {
runMethod(target, it, visible = false, callingUserId, targetUserId)
}
}
}
if (verifyUserIdCheck) {
// An invalid target user ID should always fail
allUids.forEach {
assertFailsWith(SecurityException::class) {
runMethod(target, it, visible = true, callingUserId, 100)
}
}
// An invalid calling user ID should always fail, although this cannot happen in prod
allUids.forEach {
assertFailsWith(SecurityException::class) {
runMethod(target, it, visible = true, 100, targetUserId)
}
}
}
}
private fun assertFails(block: () -> Any?) {
try {
val value = block()
// Some methods return false rather than throwing, so check that as well
if ((value as? Boolean) != false) {
// Can also return default value if it's a legacy call
if ((value as? Int)
!= PackageManager.INTENT_FILTER_DOMAIN_VERIFICATION_STATUS_UNDEFINED
) {
throw AssertionError("Expected call to return false, was $value")
}
// Some methods return false or an error rather than throwing, so check that as well
val valueAsBoolean = value as? Boolean
if (valueAsBoolean == false) {
// Expected failure, do not throw
return
}
val valueAsInt = value as? Int
if (valueAsInt != null && valueAsInt == DomainVerificationManager.STATUS_OK) {
throw AssertionError("Expected call to return false, was $value")
}
} catch (e: SecurityException) {
} catch (e: PackageManager.NameNotFoundException) {
} catch (e: DomainVerificationManager.InvalidDomainSetException) {
// Any of these 3 exceptions are considered failures, which is expected
// Any of these 2 exceptions are considered failures, which is expected
}
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.content.pm.PackageManager;
import com.android.server.pm.verify.domain.DomainVerificationService;
import java.util.Set;
import java.util.UUID;
/**
* Proxies Kotlin calls to the Java layer such that null values can be passed for {@link NonNull}
* marked parameters, as Kotlin disallows this at the compiler leveling, preventing the null error
* codes from being tested.
*/
class DomainVerificationJavaUtil {
static int setStatusForceNullable(@NonNull DomainVerificationService service,
@Nullable UUID domainSetId, @Nullable Set<String> domains, int state)
throws PackageManager.NameNotFoundException {
return service.setDomainVerificationStatus(domainSetId, domains, state);
}
static int setUserSelectionForceNullable(@NonNull DomainVerificationService service,
@Nullable UUID domainSetId, @Nullable Set<String> domains, boolean enabled,
@UserIdInt int userId)
throws PackageManager.NameNotFoundException {
return service.setDomainVerificationUserSelection(domainSetId, domains, enabled, userId);
}
}

View File

@@ -19,9 +19,9 @@ package com.android.server.pm.test.verify.domain
import android.content.pm.IntentFilterVerificationInfo
import android.content.pm.PackageManager
import android.util.ArraySet
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.readXml
import com.android.server.pm.test.verify.domain.DomainVerificationPersistenceTest.Companion.writeXml
import com.android.server.pm.verify.domain.DomainVerificationLegacySettings
import com.google.common.truth.Truth.assertWithMessage
import org.junit.Rule
import org.junit.Test

View File

@@ -0,0 +1,372 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Build
import android.os.PatternMatcher
import android.os.Process
import android.util.ArraySet
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
import kotlin.test.assertFailsWith
class DomainVerificationManagerApiTest {
companion object {
private const val PKG_ONE = "com.test.one"
private const val PKG_TWO = "com.test.two"
private const val PKG_THREE = "com.test.three"
private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private val UUID_THREE = UUID.fromString("0b3260ed-07c4-4b45-840b-237f8fb8b433")
private val UUID_INVALID = UUID.fromString("ad33babc-490b-4965-9d78-7e91248b00f")
private val DOMAIN_BASE = DomainVerificationManagerApiTest::class.java.packageName
private val DOMAIN_1 = "one.$DOMAIN_BASE"
private val DOMAIN_2 = "two.$DOMAIN_BASE"
private val DOMAIN_3 = "three.$DOMAIN_BASE"
private val DOMAIN_4 = "four.$DOMAIN_BASE"
}
@Test
fun queryValidVerificationPackageNames() {
val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
addPackages(pkgWithDomains, pkgWithoutDomains)
}
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkgWithDomains.getName())
}
@Test
fun getDomainVerificationInfoId() {
val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
addPackages(pkgWithDomains, pkgWithoutDomains)
}
assertThat(service.getDomainVerificationInfoId(PKG_ONE)).isEqualTo(UUID_ONE)
assertThat(service.getDomainVerificationInfoId(PKG_TWO)).isEqualTo(UUID_TWO)
assertThat(service.getDomainVerificationInfoId("invalid.pkg.name")).isEqualTo(null)
}
@Test
fun getDomainVerificationInfo() {
val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
addPackages(pkgWithDomains, pkgWithoutDomains)
}
val infoOne = service.getDomainVerificationInfo(pkgWithDomains.getName())
assertThat(infoOne).isNotNull()
assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName())
assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DomainVerificationInfo.STATE_NO_RESPONSE,
DOMAIN_2 to DomainVerificationInfo.STATE_NO_RESPONSE,
))
assertThat(service.getDomainVerificationInfo(pkgWithoutDomains.getName())).isNull()
assertFailsWith(PackageManager.NameNotFoundException::class) {
service.getDomainVerificationInfo("invalid.pkg.name")
}
}
@Test
fun setStatus() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2)
val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_2), 1100))
.isEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setStatus(UUID_INVALID, setOf(DOMAIN_1), 1100))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, null,
setOf(DOMAIN_1), 1100))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
assertThat(DomainVerificationJavaUtil.setStatusForceNullable(service, UUID_ONE, null,
1100)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
assertThat(service.setStatus(UUID_ONE, emptySet(), 1100))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_3), 1100))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), 1100))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), 15))
.isEqualTo(DomainVerificationManager.ERROR_INVALID_STATE_CODE)
map.clear()
assertFailsWith(PackageManager.NameNotFoundException::class){
service.setStatus(UUID_ONE, setOf(DOMAIN_1), 1100)
}
}
@Test
fun setDomainVerificationLinkHandlingAllowed() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
val map = mutableMapOf(pkg1.getName() to pkg1, pkg2.getName() to pkg2)
val service = makeService(map::get).apply { addPackages(pkg1, pkg2) }
service.setDomainVerificationLinkHandlingAllowed(PKG_ONE, false, 0);
// Should edit same package, same user
assertThat(service.getDomainVerificationUserState(PKG_ONE, 0)
?.isLinkHandlingAllowed).isEqualTo(false)
// Shouldn't edit different user
assertThat(service.getDomainVerificationUserState(PKG_ONE, 1)
?.isLinkHandlingAllowed).isEqualTo(true)
// Shouldn't edit different package
assertThat(service.getDomainVerificationUserState(PKG_TWO, 0)
?.isLinkHandlingAllowed).isEqualTo(true)
assertFailsWith(PackageManager.NameNotFoundException::class){
service.setDomainVerificationLinkHandlingAllowed("invalid.pkg.name", false, 0);
}
}
@Test
fun setUserSelection() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_3, DOMAIN_4))
val pkg3 = mockPkgSetting(PKG_THREE, UUID_THREE, listOf(DOMAIN_1, DOMAIN_2))
val map = mutableMapOf(
pkg1.getName() to pkg1,
pkg2.getName() to pkg2,
pkg3.getName() to pkg3
)
val service = makeService(map::get).apply { addPackages(pkg1, pkg2, pkg3) }
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, 0))
.isEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setUserSelection(UUID_INVALID, setOf(DOMAIN_1), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_INVALID)
assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, null,
setOf(DOMAIN_1), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_ID_NULL)
assertThat(DomainVerificationJavaUtil.setUserSelectionForceNullable(service, UUID_ONE, null,
true, 0)).isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
assertThat(service.setUserSelection(UUID_ONE, emptySet(), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_DOMAIN_SET_NULL_OR_EMPTY)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_3), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_1, DOMAIN_2, DOMAIN_3), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_UNKNOWN_DOMAIN)
service.setStatus(UUID_ONE, setOf(DOMAIN_2), DomainVerificationInfo.STATE_SUCCESS)
assertThat(service.setUserSelection(UUID_THREE, setOf(DOMAIN_2), true, 0))
.isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE)
map.clear()
assertFailsWith(PackageManager.NameNotFoundException::class){
service.setUserSelection(UUID_ONE, setOf(DOMAIN_1), true, 0)
}
}
@Test
fun getDomainVerificationUserState() {
val pkgWithDomains = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgWithoutDomains = mockPkgSetting(PKG_TWO, UUID_TWO, emptyList())
val service = makeService(pkgWithDomains, pkgWithoutDomains).apply {
addPackages(pkgWithDomains, pkgWithoutDomains)
}
val infoOne = service.getDomainVerificationUserState(pkgWithDomains.getName(), 0)
assertThat(infoOne).isNotNull()
assertThat(infoOne!!.identifier).isEqualTo(pkgWithDomains.domainSetId)
assertThat(infoOne.packageName).isEqualTo(pkgWithDomains.getName())
assertThat(infoOne.isLinkHandlingAllowed).isTrue()
assertThat(infoOne.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DomainVerificationUserState.DOMAIN_STATE_NONE,
DOMAIN_2 to DomainVerificationUserState.DOMAIN_STATE_NONE,
))
val infoTwo = service.getDomainVerificationUserState(pkgWithoutDomains.getName(), 0)
assertThat(infoTwo).isNotNull()
assertThat(infoTwo!!.identifier).isEqualTo(pkgWithoutDomains.domainSetId)
assertThat(infoTwo.packageName).isEqualTo(pkgWithoutDomains.getName())
assertThat(infoOne.isLinkHandlingAllowed).isTrue()
assertThat(infoTwo.hostToStateMap).isEmpty()
assertFailsWith(PackageManager.NameNotFoundException::class) {
service.getDomainVerificationUserState("invalid.pkg.name", 0)
}
}
@Test
fun getOwnersForDomain() {
val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
val service = makeService(pkg1, pkg2).apply {
addPackages(pkg1, pkg2)
}
assertThat(service.getOwnersForDomain(DOMAIN_1, 0)).isEmpty()
service.setStatus(pkg1.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
service.setStatus(pkg2.domainSetId, setOf(DOMAIN_1), DomainVerificationInfo.STATE_SUCCESS)
service.setUserSelection(pkg1.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_1, 0).let {
assertThat(it).hasSize(2)
assertThat(it[0].packageName).isEqualTo(pkg1.getName())
assertThat(it[0].isOverrideable).isEqualTo(false)
assertThat(it[1].packageName).isEqualTo(pkg2.getName())
assertThat(it[1].isOverrideable).isEqualTo(false)
}
service.getOwnersForDomain(DOMAIN_2, 0).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg1.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
service.setUserSelection(pkg2.domainSetId, setOf(DOMAIN_2), true, 0)
service.getOwnersForDomain(DOMAIN_2, 0).let {
assertThat(it).hasSize(1)
assertThat(it.single().packageName).isEqualTo(pkg2.getName())
assertThat(it.single().isOverrideable).isEqualTo(true)
}
assertThat(service.getOwnersForDomain(DOMAIN_2, 1)).isEmpty()
}
private fun makeService(vararg pkgSettings: PackageSetting) =
makeService { pkgName -> pkgSettings.find { pkgName == it.getName() } }
private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
whenever(checkPermission(anyString(), anyInt(), anyInt())) {
PackageManager.PERMISSION_GRANTED
}
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
whenever(isChangeEnabled(anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)?.getPkg()
}
})
}
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
DOMAIN_1, DOMAIN_2
)) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
val activityList = listOf(
ParsedActivity().apply {
domains.forEach {
addIntent(
ParsedIntentInfo().apply {
autoVerify = true
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataScheme("http")
addDataScheme("https")
addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
addDataAuthority(it, null)
}
)
}
},
)
whenever(activities) { activityList }
}
whenever(getPkg()) { pkg }
whenever(getName()) { pkgName }
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
}
fun DomainVerificationService.addPackages(vararg pkgSettings: PackageSetting) =
pkgSettings.forEach(::addPackage)
}

View File

@@ -16,8 +16,8 @@
package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationUserState
import com.android.server.pm.verify.domain.DomainVerificationPersistence

View File

@@ -0,0 +1,425 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_NO_RESPONSE
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_SUCCESS
import android.content.pm.verify.domain.DomainVerificationInfo.STATE_UNMODIFIABLE
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_NONE
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_SELECTED
import android.content.pm.verify.domain.DomainVerificationUserState.DOMAIN_STATE_VERIFIED
import android.os.Build
import android.os.PatternMatcher
import android.os.Process
import android.util.ArraySet
import android.util.Xml
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.any
import org.mockito.ArgumentMatchers.anyInt
import org.mockito.ArgumentMatchers.anyString
import java.util.UUID
class DomainVerificationPackageTest {
companion object {
private const val PKG_ONE = "com.test.one"
private const val PKG_TWO = "com.test.two"
private val UUID_ONE = UUID.fromString("1b041c96-8d37-4932-a858-561bfac5947c")
private val UUID_TWO = UUID.fromString("a3389c16-7f9f-4e86-85e3-500d1249c74c")
private val DOMAIN_BASE = DomainVerificationPackageTest::class.java.packageName
private val DOMAIN_1 = "one.$DOMAIN_BASE"
private val DOMAIN_2 = "two.$DOMAIN_BASE"
private val DOMAIN_3 = "three.$DOMAIN_BASE"
private val DOMAIN_4 = "four.$DOMAIN_BASE"
private const val USER_ID = 0
}
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
@Test
fun addPackageFirstTime() {
val service = makeService(pkg1, pkg2)
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(true)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun addPackageActive() {
// language=XML
val xml = """
<?xml?>
<domain-verifications>
<active>
<package-state
packageName="${pkg1.getName()}"
id="${pkg1.domainSetId}"
>
<state>
<domain name="$DOMAIN_1" state="$STATE_SUCCESS"/>
</state>
<user-states>
<user-state userId="$USER_ID" allowLinkHandling="false">
<enabled-hosts>
<host name="$DOMAIN_2"/>
</enabled-hosts>
</user-state>
</user-states>
</package-state>
</active>
</domain-verifications>
""".trimIndent()
val service = makeService(pkg1, pkg2)
xml.byteInputStream().use {
service.readSettings(Xml.resolvePullParser(it))
}
service.addPackage(pkg1)
val info = service.getInfo(pkg1.getName())
assertThat(info.packageName).isEqualTo(pkg1.getName())
assertThat(info.identifier).isEqualTo(pkg1.domainSetId)
assertThat(info.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
))
val userState = service.getUserState(pkg1.getName())
assertThat(userState.packageName).isEqualTo(pkg1.getName())
assertThat(userState.identifier).isEqualTo(pkg1.domainSetId)
assertThat(userState.isLinkHandlingAllowed).isEqualTo(false)
assertThat(userState.user.identifier).isEqualTo(USER_ID)
assertThat(userState.hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
assertThat(service.queryValidVerificationPackageNames())
.containsExactly(pkg1.getName())
}
@Test
fun migratePackageDropDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3, DOMAIN_4))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
listOf(DOMAIN_1, DOMAIN_2))
// Test 4 domains:
// 1 will be approved and preserved, 2 will be selected and preserved,
// 3 will be denied and dropped, 4 will be selected and dropped
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
// To test the approve/denial states, use the internal methods for this variant
service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_APPROVED,
ArraySet(setOf(DOMAIN_1)))
service.setDomainVerificationStatusInternal(pkgName, DomainVerificationState.STATE_DENIED,
ArraySet(setOf(DOMAIN_3)))
service.setUserSelection(
UUID_ONE, setOf(DOMAIN_2, DOMAIN_4), true, USER_ID)
// Check the verifier cannot change the shell approve/deny states
service.setStatus(UUID_ONE, setOf(DOMAIN_1, DOMAIN_3), STATE_SUCCESS)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_NO_RESPONSE,
DOMAIN_3 to STATE_UNMODIFIABLE,
DOMAIN_4 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE,
DOMAIN_4 to DOMAIN_STATE_SELECTED,
))
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
map[pkgName] = pkgAfter
service.migrateState(pkgBefore, pkgAfter)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_UNMODIFIABLE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun migratePackageDropAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, emptyList())
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
assertThat(service.queryValidVerificationPackageNames()).isEmpty()
}
@Test
fun migratePackageAddDomain() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE,
listOf(DOMAIN_1, DOMAIN_2))
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO,
listOf(DOMAIN_1, DOMAIN_2, DOMAIN_3))
// Test 3 domains:
// 1 will be verified and preserved, 2 will be selected and preserved,
// 3 will be new and default
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS)
service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID)
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
))
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_SUCCESS,
DOMAIN_2 to STATE_NO_RESPONSE,
DOMAIN_3 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_VERIFIED,
DOMAIN_2 to DOMAIN_STATE_SELECTED,
DOMAIN_3 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
@Test
fun migratePackageAddAll() {
val pkgName = PKG_ONE
val pkgBefore = mockPkgSetting(pkgName, UUID_ONE, emptyList())
val pkgAfter = mockPkgSetting(pkgName, UUID_TWO, listOf(DOMAIN_1, DOMAIN_2))
val map = mutableMapOf<String, PackageSetting>()
val service = makeService { map[it] }
service.addPackage(pkgBefore)
// Only insert the package after addPackage call to ensure the service doesn't access
// a live package inside the addPackage logic. It should only use the provided input.
map[pkgName] = pkgBefore
assertThat(service.setStatus(UUID_ONE, setOf(DOMAIN_1), STATE_SUCCESS))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.setUserSelection(UUID_ONE, setOf(DOMAIN_2), true, USER_ID))
.isNotEqualTo(DomainVerificationManager.STATUS_OK)
assertThat(service.getDomainVerificationInfo(pkgName)).isNull()
assertThat(service.getUserState(pkgName).hostToStateMap).isEmpty()
assertThat(service.queryValidVerificationPackageNames()).isEmpty()
// Now remove the package because migrateState shouldn't use it either
map.remove(pkgName)
service.migrateState(pkgBefore, pkgAfter)
map[pkgName] = pkgAfter
assertThat(service.getInfo(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to STATE_NO_RESPONSE,
DOMAIN_2 to STATE_NO_RESPONSE,
))
assertThat(service.getUserState(pkgName).hostToStateMap).containsExactlyEntriesIn(mapOf(
DOMAIN_1 to DOMAIN_STATE_NONE,
DOMAIN_2 to DOMAIN_STATE_NONE,
))
assertThat(service.queryValidVerificationPackageNames()).containsExactly(pkgName)
}
private fun DomainVerificationService.getInfo(pkgName: String) =
getDomainVerificationInfo(pkgName)
.also { assertThat(it).isNotNull() }!!
private fun DomainVerificationService.getUserState(pkgName: String) =
getDomainVerificationUserState(pkgName, USER_ID)
.also { assertThat(it).isNotNull() }!!
private fun makeService(vararg pkgSettings: PackageSetting) =
makeService { pkgName -> pkgSettings.find { pkgName == it.getName()} }
private fun makeService(pkgSettingFunction: (String) -> PackageSetting? = { null }) =
DomainVerificationService(mockThrowOnUnmocked {
// Assume the test has every permission necessary
whenever(enforcePermission(anyString(), anyInt(), anyInt(), anyString()))
whenever(checkPermission(anyString(), anyInt(), anyInt())) {
PackageManager.PERMISSION_GRANTED
}
}, mockThrowOnUnmocked {
whenever(linkedApps) { ArraySet<String>() }
}, mockThrowOnUnmocked {
whenever(isChangeEnabled(ArgumentMatchers.anyLong(), any())) { true }
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
whenever(callingUid) { Process.ROOT_UID }
whenever(callingUserId) { 0 }
whenever(getPackageSettingLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!
}
whenever(getPackageLocked(anyString())) {
pkgSettingFunction(arguments[0] as String)!!.getPkg()
}
})
}
private fun mockPkgSetting(pkgName: String, domainSetId: UUID, domains: List<String> = listOf(
DOMAIN_1, DOMAIN_2
)) = mockThrowOnUnmocked<PackageSetting> {
val pkg = mockThrowOnUnmocked<AndroidPackage> {
whenever(packageName) { pkgName }
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
val activityList = listOf(
ParsedActivity().apply {
domains.forEach {
addIntent(
ParsedIntentInfo().apply {
autoVerify = true
addAction(Intent.ACTION_VIEW)
addCategory(Intent.CATEGORY_BROWSABLE)
addCategory(Intent.CATEGORY_DEFAULT)
addDataScheme("http")
addDataScheme("https")
addDataPath("/sub", PatternMatcher.PATTERN_LITERAL)
addDataAuthority(it, null)
}
)
}
},
)
whenever(activities) { activityList }
}
whenever(getPkg()) { pkg }
whenever(getName()) { pkgName }
whenever(this.domainSetId) { domainSetId }
whenever(getInstantApp(anyInt())) { false }
whenever(firstInstallTime) { 0L }
}
}

View File

@@ -16,7 +16,7 @@
package com.android.server.pm.test.verify.domain
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
import android.util.ArrayMap
import android.util.TypedXmlPullParser
import android.util.TypedXmlSerializer
@@ -117,11 +117,11 @@ class DomainVerificationPersistenceTest {
@Test
fun readMalformed() {
val stateZero = mockEmptyPkgState(0).apply {
stateMap["example.com"] = DomainVerificationManager.STATE_SUCCESS
stateMap["example.org"] = DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED
stateMap["example.com"] = DomainVerificationState.STATE_SUCCESS
stateMap["example.org"] = DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED
// A domain without a written state falls back to default
stateMap["missing-state.com"] = DomainVerificationManager.STATE_NO_RESPONSE
stateMap["missing-state.com"] = DomainVerificationState.STATE_NO_RESPONSE
userStates[1] = DomainVerificationInternalUserState(1).apply {
addHosts(setOf("example-user1.com", "example-user1.org"))
@@ -159,9 +159,9 @@ class DomainVerificationPersistenceTest {
>
<state>
<domain name="example.com" state="${
DomainVerificationManager.STATE_SUCCESS}"/>
DomainVerificationState.STATE_SUCCESS}"/>
<domain name="example.org" state="${
DomainVerificationManager.STATE_FIRST_VERIFIER_DEFINED}"/>
DomainVerificationState.STATE_FIRST_VERIFIER_DEFINED}"/>
<not-domain name="not-domain.com" state="1"/>
<domain name="missing-state.com"/>
</state>

View File

@@ -21,20 +21,20 @@ import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.content.pm.PackageManager
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationRequest
import android.content.pm.verify.domain.DomainVerificationInfo
import android.content.pm.verify.domain.DomainVerificationState
import android.os.Bundle
import android.os.UserHandle
import android.util.ArraySet
import com.android.server.DeviceIdleInternal
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationCollector
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV1
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxyV2
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.whenever
import com.google.common.truth.Truth.assertThat
@@ -106,20 +106,22 @@ class DomainVerificationProxyTest {
when (val pkgName = arguments[0] as String) {
TEST_PKG_NAME_TARGET_ONE -> DomainVerificationInfo(
TEST_UUID_ONE, pkgName, mapOf(
"example1.com" to DomainVerificationManager.STATE_NO_RESPONSE,
"example2.com" to DomainVerificationManager.STATE_NO_RESPONSE
"example1.com" to DomainVerificationInfo.STATE_NO_RESPONSE,
"example2.com" to DomainVerificationInfo.STATE_NO_RESPONSE
)
)
TEST_PKG_NAME_TARGET_TWO -> DomainVerificationInfo(
TEST_UUID_TWO, pkgName, mapOf(
"example3.com" to DomainVerificationManager.STATE_NO_RESPONSE,
"example4.com" to DomainVerificationManager.STATE_NO_RESPONSE
"example3.com" to DomainVerificationInfo.STATE_NO_RESPONSE,
"example4.com" to DomainVerificationInfo.STATE_NO_RESPONSE
)
)
else -> throw IllegalArgumentException("Unexpected package name $pkgName")
}
}
whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt()))
whenever(setDomainVerificationStatusInternal(anyInt(), any(), any(), anyInt())) {
DomainVerificationManager.STATUS_OK
}
}
collector = mockThrowOnUnmocked {
whenever(collectValidAutoVerifyDomains(any())) {
@@ -316,7 +318,7 @@ class DomainVerificationProxyTest {
eq(TEST_CALLING_UID_ACCEPT),
idCaptor.capture(),
hostCaptor.capture(),
eq(DomainVerificationManager.STATE_SUCCESS)
eq(DomainVerificationState.STATE_SUCCESS)
)
assertThat(idCaptor.allValues).containsExactly(TEST_UUID_ONE, TEST_UUID_TWO)

View File

@@ -0,0 +1,31 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm.test.verify.domain
import android.annotation.UserIdInt
import com.android.server.pm.verify.domain.DomainVerificationService
import java.util.UUID
fun DomainVerificationService.setStatus(domainSetId: UUID, domains: Set<String>, state: Int) =
setDomainVerificationStatus(domainSetId, domains.toMutableSet(), state)
fun DomainVerificationService.setUserSelection(
domainSetId: UUID,
domains: Set<String>,
enabled: Boolean,
@UserIdInt userId: Int
) = setDomainVerificationUserSelection(domainSetId, domains.toMutableSet(), enabled, userId)

View File

@@ -20,28 +20,27 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.pm.PackageUserState
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationState
import android.os.Build
import android.os.Process
import android.util.ArraySet
import android.util.SparseArray
import com.android.server.pm.PackageSetting
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
import com.android.server.pm.verify.domain.DomainVerificationService
import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.testutils.mockThrowOnUnmocked
import com.android.server.testutils.spyThrowOnUnmocked
import com.android.server.testutils.whenever
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import org.mockito.Mockito
import org.mockito.Mockito.any
import org.mockito.Mockito.anyInt
import org.mockito.Mockito.anyLong
import org.mockito.Mockito.any
import org.mockito.Mockito.anyString
import org.mockito.Mockito.eq
import org.mockito.Mockito.verify
@@ -129,13 +128,13 @@ class DomainVerificationSettingsMutationTest {
setDomainVerificationStatus(
TEST_UUID,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
DomainVerificationState.STATE_SUCCESS
)
},
service("setStatusInternalPackageName") {
setDomainVerificationStatusInternal(
TEST_PKG,
DomainVerificationManager.STATE_SUCCESS,
DomainVerificationState.STATE_SUCCESS,
ArraySet(setOf("example.com"))
)
},
@@ -144,7 +143,7 @@ class DomainVerificationSettingsMutationTest {
TEST_UID,
TEST_UUID,
setOf("example.com"),
DomainVerificationManager.STATE_SUCCESS
DomainVerificationState.STATE_SUCCESS
)
},
service("setLinkHandlingAllowedUserId") {
@@ -266,5 +265,7 @@ class DomainVerificationSettingsMutationTest {
// This doesn't check for visibility; that's done in the enforcer test
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(10)) { true }
}
}

View File

@@ -21,6 +21,7 @@ import android.content.pm.PackageManager
import android.content.pm.parsing.component.ParsedActivity
import android.content.pm.parsing.component.ParsedIntentInfo
import android.content.pm.verify.domain.DomainVerificationManager
import android.content.pm.verify.domain.DomainVerificationState
import android.content.pm.verify.domain.DomainVerificationUserState
import android.os.Build
import android.os.PatternMatcher
@@ -74,6 +75,8 @@ class DomainVerificationUserStateOverrideTest {
}).apply {
setConnection(mockThrowOnUnmocked {
whenever(filterAppAccess(anyString(), anyInt(), anyInt())) { false }
whenever(doesUserExist(0)) { true }
whenever(doesUserExist(1)) { true }
whenever(scheduleWriteSettings())
// Need to provide an internal UID so some permission checks are ignored
@@ -154,19 +157,20 @@ class DomainVerificationUserStateOverrideTest {
.containsExactly(PKG_TWO)
}
@Test(expected = IllegalArgumentException::class)
@Test
fun anotherPackageTakeoverFailure() {
val service = makeService()
// Verify 1 to give it a higher approval level
service.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
DomainVerificationManager.STATE_SUCCESS)
DomainVerificationState.STATE_SUCCESS)
assertThat(service.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
assertThat(service.getOwnersForDomain(DOMAIN_ONE, USER_ID).map { it.packageName })
.containsExactly(PKG_ONE)
// Attempt override by package 2
service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true, USER_ID)
assertThat(service.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true,
USER_ID)).isEqualTo(DomainVerificationManager.ERROR_UNABLE_TO_APPROVE)
}
private fun DomainVerificationService.stateFor(pkgName: String, host: String) =