Update domain verification Settings API
Adjusts the logic for selecting a domain, removing the selection for other applications once a domain is verified or overridden by another application being selected. To this end, exposes a new Domain class with verified/selected booleans to encapsulate the state, returned in the initial response for the Settings screen API. This avoids the usage of the getDomainVerificationSet API, so that can be restricted to only the domain verification agent in a future change. Also introduces the concept of a DomainOwner, which represents a single package who has been granted any positive level of approval to open a domain. Bug: 177923646 CTS-Coverage-Bug: 179382047 Test: atest com.android.server.pm.verify.domain Test: TODO with Settings changes Change-Id: Ib14049e397154616f84a2264167ac30659cd81c9
This commit is contained in:
@@ -2757,6 +2757,15 @@ package android.content.pm.permission {
|
||||
|
||||
package android.content.pm.verify.domain {
|
||||
|
||||
public final class DomainOwner implements android.os.Parcelable {
|
||||
ctor public DomainOwner(@NonNull String, boolean);
|
||||
method public int describeContents();
|
||||
method @NonNull public String getPackageName();
|
||||
method public boolean isOverrideable();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainOwner> CREATOR;
|
||||
}
|
||||
|
||||
public final class DomainVerificationInfo implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
|
||||
@@ -2769,6 +2778,7 @@ package android.content.pm.verify.domain {
|
||||
public interface 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 @Nullable @RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION) public android.content.pm.verify.domain.DomainVerificationUserSelection getDomainVerificationUserSelection(@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 @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List<java.lang.String> getValidVerificationPackageNames();
|
||||
method public static boolean isStateModifiable(int);
|
||||
method public static boolean isStateVerified(int);
|
||||
@@ -2790,13 +2800,16 @@ package android.content.pm.verify.domain {
|
||||
|
||||
public final class DomainVerificationUserSelection implements android.os.Parcelable {
|
||||
method public int describeContents();
|
||||
method @NonNull public java.util.Map<java.lang.String,java.lang.Boolean> getHostToUserSelectionMap();
|
||||
method @NonNull public java.util.Map<java.lang.String,java.lang.Integer> getHostToStateMap();
|
||||
method @NonNull public java.util.UUID getIdentifier();
|
||||
method @NonNull public String getPackageName();
|
||||
method @NonNull public android.os.UserHandle getUser();
|
||||
method @NonNull public boolean isLinkHandlingAllowed();
|
||||
method public void writeToParcel(@NonNull android.os.Parcel, int);
|
||||
field @NonNull public static final android.os.Parcelable.Creator<android.content.pm.verify.domain.DomainVerificationUserSelection> CREATOR;
|
||||
field public static final int DOMAIN_STATE_NONE = 0; // 0x0
|
||||
field public static final int DOMAIN_STATE_SELECTED = 1; // 0x1
|
||||
field public static final int DOMAIN_STATE_VERIFIED = 2; // 0x2
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
19
core/java/android/content/pm/verify/domain/DomainOwner.aidl
Normal file
19
core/java/android/content/pm/verify/domain/DomainOwner.aidl
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* 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 android.content.pm.verify.domain;
|
||||
|
||||
parcelable DomainOwner;
|
||||
219
core/java/android/content/pm/verify/domain/DomainOwner.java
Normal file
219
core/java/android/content/pm/verify/domain/DomainOwner.java
Normal file
@@ -0,0 +1,219 @@
|
||||
/*
|
||||
* 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 android.content.pm.verify.domain;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.SystemApi;
|
||||
import android.os.Parcelable;
|
||||
|
||||
import com.android.internal.util.DataClass;
|
||||
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
@SystemApi
|
||||
@DataClass(genParcelable = true, genEqualsHashCode = true, genAidl = true, genToString = true)
|
||||
public final class DomainOwner implements Parcelable {
|
||||
|
||||
/**
|
||||
* Package name of that owns the domain.
|
||||
*/
|
||||
@NonNull
|
||||
private final String mPackageName;
|
||||
|
||||
/**
|
||||
* Whether or not this owner can be automatically overridden.
|
||||
*
|
||||
* @see DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)
|
||||
*/
|
||||
private final boolean mOverrideable;
|
||||
|
||||
|
||||
|
||||
// Code below generated by codegen v1.0.22.
|
||||
//
|
||||
// DO NOT MODIFY!
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
//@formatter:off
|
||||
|
||||
|
||||
/**
|
||||
* Creates a new DomainOwner.
|
||||
*
|
||||
* @param packageName
|
||||
* Package name of that owns the domain.
|
||||
* @param overrideable
|
||||
* Whether or not this owner can be automatically overridden. If all owners for a domain are
|
||||
* overrideable, then calling
|
||||
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
|
||||
* of the owners are non-overrideable, then
|
||||
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
|
||||
* boolean)} must be called with false to disable all of the other owners before this domain can
|
||||
* be taken by a new owner through
|
||||
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)}.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public DomainOwner(
|
||||
@NonNull String packageName,
|
||||
boolean overrideable) {
|
||||
this.mPackageName = packageName;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mPackageName);
|
||||
this.mOverrideable = overrideable;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
/**
|
||||
* Package name of that owns the domain.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public @NonNull String getPackageName() {
|
||||
return mPackageName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not this owner can be automatically overridden. If all owners for a domain are
|
||||
* overrideable, then calling
|
||||
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)} to enable the domain will disable all other owners. On the other hand, if any
|
||||
* of the owners are non-overrideable, then
|
||||
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
|
||||
* boolean)} must be called with false to disable all of the other owners before this domain can
|
||||
* be taken by a new owner through
|
||||
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)}.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public boolean isOverrideable() {
|
||||
return mOverrideable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public String toString() {
|
||||
// You can override field toString logic by defining methods like:
|
||||
// String fieldNameToString() { ... }
|
||||
|
||||
return "DomainOwner { " +
|
||||
"packageName = " + mPackageName + ", " +
|
||||
"overrideable = " + mOverrideable +
|
||||
" }";
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public boolean equals(@android.annotation.Nullable Object o) {
|
||||
// You can override field equality logic by defining either of the methods like:
|
||||
// boolean fieldNameEquals(DomainOwner other) { ... }
|
||||
// boolean fieldNameEquals(FieldType otherValue) { ... }
|
||||
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
@SuppressWarnings("unchecked")
|
||||
DomainOwner that = (DomainOwner) o;
|
||||
//noinspection PointlessBooleanExpression
|
||||
return true
|
||||
&& java.util.Objects.equals(mPackageName, that.mPackageName)
|
||||
&& mOverrideable == that.mOverrideable;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int hashCode() {
|
||||
// You can override field hashCode logic by defining methods like:
|
||||
// int fieldNameHashCode() { ... }
|
||||
|
||||
int _hash = 1;
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
|
||||
_hash = 31 * _hash + Boolean.hashCode(mOverrideable);
|
||||
return _hash;
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
|
||||
// You can override field parcelling by defining methods like:
|
||||
// void parcelFieldName(Parcel dest, int flags) { ... }
|
||||
|
||||
byte flg = 0;
|
||||
if (mOverrideable) flg |= 0x2;
|
||||
dest.writeByte(flg);
|
||||
dest.writeString(mPackageName);
|
||||
}
|
||||
|
||||
@Override
|
||||
@DataClass.Generated.Member
|
||||
public int describeContents() { return 0; }
|
||||
|
||||
/** @hide */
|
||||
@SuppressWarnings({"unchecked", "RedundantCast"})
|
||||
@DataClass.Generated.Member
|
||||
/* package-private */ DomainOwner(@NonNull android.os.Parcel in) {
|
||||
// You can override field unparcelling by defining methods like:
|
||||
// static FieldType unparcelFieldName(Parcel in) { ... }
|
||||
|
||||
byte flg = in.readByte();
|
||||
boolean overrideable = (flg & 0x2) != 0;
|
||||
String packageName = in.readString();
|
||||
|
||||
this.mPackageName = packageName;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mPackageName);
|
||||
this.mOverrideable = overrideable;
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
|
||||
@DataClass.Generated.Member
|
||||
public static final @NonNull Parcelable.Creator<DomainOwner> CREATOR
|
||||
= new Parcelable.Creator<DomainOwner>() {
|
||||
@Override
|
||||
public DomainOwner[] newArray(int size) {
|
||||
return new DomainOwner[size];
|
||||
}
|
||||
|
||||
@Override
|
||||
public DomainOwner createFromParcel(@NonNull android.os.Parcel in) {
|
||||
return new DomainOwner(in);
|
||||
}
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1614119379978L,
|
||||
codegenVersion = "1.0.22",
|
||||
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainOwner.java",
|
||||
inputSignatures = "private final @android.annotation.NonNull java.lang.String mPackageName\nprivate final boolean mOverrideable\nclass DomainOwner extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genEqualsHashCode=true, genAidl=true, genToString=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
//@formatter:on
|
||||
// End of generated code
|
||||
|
||||
}
|
||||
@@ -239,7 +239,15 @@ public interface DomainVerificationManager {
|
||||
* {@link Context#createPackageContextAsUser(String, int, UserHandle)} should be used.
|
||||
*
|
||||
* Enabling an unverified domain will allow an application to open it, but this can only occur
|
||||
* if no other app on the device is approved for the domain.
|
||||
* if no other app on the device is approved for a higher approval level. This can queried
|
||||
* using {@link #getOwnersForDomain(String)}.
|
||||
*
|
||||
* If all owners for a domain are {@link DomainOwner#isOverrideable()}, then calling this to
|
||||
* enable that domain will disable all other owners.
|
||||
*
|
||||
* On the other hand, if any of the owners are non-overrideable, then this must be called with
|
||||
* false for all of the other owners to disable them before the domain can be taken by a new
|
||||
* owner.
|
||||
*
|
||||
* @param domainSetId See {@link DomainVerificationInfo#getIdentifier()}.
|
||||
* @param domains The domains to toggle the state of.
|
||||
@@ -275,6 +283,19 @@ public interface DomainVerificationManager {
|
||||
DomainVerificationUserSelection getDomainVerificationUserSelection(@NonNull String packageName)
|
||||
throws NameNotFoundException;
|
||||
|
||||
/**
|
||||
* For the given domain, return all apps which are approved to open it in a
|
||||
* greater than 0 priority. This does not mean that all apps can actually open
|
||||
* an Intent with that domain. That will be decided by the set of apps which
|
||||
* are the highest priority level, ignoring all lower priority levels.
|
||||
*
|
||||
* By default the list will be returned ordered from lowest to highest
|
||||
* priority.
|
||||
*/
|
||||
@NonNull
|
||||
@RequiresPermission(android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION)
|
||||
List<DomainOwner> getOwnersForDomain(@NonNull String domain);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
@@ -21,11 +21,9 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.verify.domain.IDomainVerificationManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceSpecificException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
@@ -158,6 +156,16 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
|
||||
try {
|
||||
return mDomainVerificationManager.getOwnersForDomain(domain, mContext.getUserId());
|
||||
} catch (RemoteException e) {
|
||||
throw e.rethrowFromSystemServer();
|
||||
}
|
||||
}
|
||||
|
||||
private Exception rethrow(Exception exception, @Nullable UUID domainSetId) {
|
||||
return rethrow(exception, domainSetId, null);
|
||||
}
|
||||
|
||||
@@ -42,9 +42,9 @@ import java.util.UUID;
|
||||
* toggle affects <b>all</b> links and is not based on the verification state of the domains.
|
||||
* <p>
|
||||
* Assuming the toggle is enabled, the user can also select additional unverified domains to grant
|
||||
* to the application to open, which is reflected in {@link #getHostToUserSelectionMap()}. But only
|
||||
* a single application can be approved for a domain unless the applications are both approved. If
|
||||
* another application is approved, the user will not be allowed to enable the domain.
|
||||
* to the application to open, which is reflected in {@link #getHostToStateMap()}. But only a single
|
||||
* application can be approved for a domain unless the applications are both approved. If another
|
||||
* application is approved, the user will not be allowed to enable the domain.
|
||||
* <p>
|
||||
* These values can be changed through the
|
||||
* {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String,
|
||||
@@ -60,9 +60,27 @@ import java.util.UUID;
|
||||
@SystemApi
|
||||
@SuppressWarnings("DefaultAnnotationParam")
|
||||
@DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true,
|
||||
genEqualsHashCode = true)
|
||||
genEqualsHashCode = true, genHiddenConstDefs = true)
|
||||
public final class DomainVerificationUserSelection implements Parcelable {
|
||||
|
||||
/**
|
||||
* The domain is unverified and unselected, and the application is unable to open web links
|
||||
* that resolve to the domain.
|
||||
*/
|
||||
public static final int DOMAIN_STATE_NONE = 0;
|
||||
|
||||
/**
|
||||
* The domain has been selected through the
|
||||
* {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, boolean)}
|
||||
* API, under the assumption it has not been reset by the system.
|
||||
*/
|
||||
public static final int DOMAIN_STATE_SELECTED = 1;
|
||||
|
||||
/**
|
||||
* The domain has been previously verified by the domain verification agent.
|
||||
*/
|
||||
public static final int DOMAIN_STATE_VERIFIED = 2;
|
||||
|
||||
/**
|
||||
* @see DomainVerificationInfo#getIdentifier
|
||||
*/
|
||||
@@ -89,20 +107,17 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
private final boolean mLinkHandlingAllowed;
|
||||
|
||||
/**
|
||||
* Retrieve the existing user selection state for the matching {@link #getPackageName()}, as was
|
||||
* previously set by {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)}.
|
||||
*
|
||||
* @return Map of hosts to enabled state for the given package and user.
|
||||
* Mapping of domain host to state, as defined by {@link DomainState}.
|
||||
*/
|
||||
@NonNull
|
||||
private final Map<String, Boolean> mHostToUserSelectionMap;
|
||||
private final Map<String, Integer> mHostToStateMap;
|
||||
|
||||
private void parcelHostToUserSelectionMap(Parcel dest, @SuppressWarnings("unused") int flags) {
|
||||
DomainVerificationUtils.writeHostMap(dest, mHostToUserSelectionMap);
|
||||
private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) {
|
||||
DomainVerificationUtils.writeHostMap(dest, mHostToStateMap);
|
||||
}
|
||||
|
||||
private Map<String, Boolean> unparcelHostToUserSelectionMap(Parcel in) {
|
||||
@NonNull
|
||||
private Map<String, Integer> unparcelHostToStateMap(Parcel in) {
|
||||
return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(),
|
||||
DomainVerificationUserSelection.class.getClassLoader());
|
||||
}
|
||||
@@ -115,14 +130,37 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
// CHECKSTYLE:OFF Generated code
|
||||
//
|
||||
// To regenerate run:
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain
|
||||
// /DomainVerificationUserSelection.java
|
||||
// $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java
|
||||
//
|
||||
// To exclude the generated code from IntelliJ auto-formatting enable (one-time):
|
||||
// Settings > Editor > Code Style > Formatter Control
|
||||
//@formatter:off
|
||||
|
||||
|
||||
/** @hide */
|
||||
@android.annotation.IntDef(prefix = "DOMAIN_STATE_", value = {
|
||||
DOMAIN_STATE_NONE,
|
||||
DOMAIN_STATE_SELECTED,
|
||||
DOMAIN_STATE_VERIFIED
|
||||
})
|
||||
@java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
|
||||
@DataClass.Generated.Member
|
||||
public @interface DomainState {}
|
||||
|
||||
/** @hide */
|
||||
@DataClass.Generated.Member
|
||||
public static String domainStateToString(@DomainState int value) {
|
||||
switch (value) {
|
||||
case DOMAIN_STATE_NONE:
|
||||
return "DOMAIN_STATE_NONE";
|
||||
case DOMAIN_STATE_SELECTED:
|
||||
return "DOMAIN_STATE_SELECTED";
|
||||
case DOMAIN_STATE_VERIFIED:
|
||||
return "DOMAIN_STATE_VERIFIED";
|
||||
default: return Integer.toHexString(value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new DomainVerificationUserSelection.
|
||||
*
|
||||
@@ -132,10 +170,8 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
* The user that this data corresponds to.
|
||||
* @param linkHandlingAllowed
|
||||
* Whether or not this package is allowed to open links.
|
||||
* @param hostToUserSelectionMap
|
||||
* Retrieve the existing user selection state for the matching {@link #getPackageName()}, as was
|
||||
* previously set by {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)}.
|
||||
* @param hostToStateMap
|
||||
* Mapping of domain host to state, as defined by {@link DomainState}.
|
||||
* @hide
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
@@ -144,7 +180,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
@NonNull String packageName,
|
||||
@NonNull UserHandle user,
|
||||
@NonNull boolean linkHandlingAllowed,
|
||||
@NonNull Map<String,Boolean> hostToUserSelectionMap) {
|
||||
@NonNull Map<String,Integer> hostToStateMap) {
|
||||
this.mIdentifier = identifier;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mIdentifier);
|
||||
@@ -157,9 +193,9 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
this.mLinkHandlingAllowed = linkHandlingAllowed;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mLinkHandlingAllowed);
|
||||
this.mHostToUserSelectionMap = hostToUserSelectionMap;
|
||||
this.mHostToStateMap = hostToStateMap;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mHostToUserSelectionMap);
|
||||
NonNull.class, null, mHostToStateMap);
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
@@ -197,15 +233,11 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the existing user selection state for the matching {@link #getPackageName()}, as was
|
||||
* previously set by {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID,
|
||||
* Set, boolean)}.
|
||||
*
|
||||
* @return Map of hosts to enabled state for the given package and user.
|
||||
* Mapping of domain host to state, as defined by {@link DomainState}.
|
||||
*/
|
||||
@DataClass.Generated.Member
|
||||
public @NonNull Map<String,Boolean> getHostToUserSelectionMap() {
|
||||
return mHostToUserSelectionMap;
|
||||
public @NonNull Map<String,Integer> getHostToStateMap() {
|
||||
return mHostToStateMap;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -219,7 +251,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
"packageName = " + mPackageName + ", " +
|
||||
"user = " + mUser + ", " +
|
||||
"linkHandlingAllowed = " + mLinkHandlingAllowed + ", " +
|
||||
"hostToUserSelectionMap = " + mHostToUserSelectionMap +
|
||||
"hostToStateMap = " + mHostToStateMap +
|
||||
" }";
|
||||
}
|
||||
|
||||
@@ -240,7 +272,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
&& java.util.Objects.equals(mPackageName, that.mPackageName)
|
||||
&& java.util.Objects.equals(mUser, that.mUser)
|
||||
&& mLinkHandlingAllowed == that.mLinkHandlingAllowed
|
||||
&& java.util.Objects.equals(mHostToUserSelectionMap, that.mHostToUserSelectionMap);
|
||||
&& java.util.Objects.equals(mHostToStateMap, that.mHostToStateMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -254,7 +286,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mPackageName);
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mUser);
|
||||
_hash = 31 * _hash + Boolean.hashCode(mLinkHandlingAllowed);
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mHostToUserSelectionMap);
|
||||
_hash = 31 * _hash + java.util.Objects.hashCode(mHostToStateMap);
|
||||
return _hash;
|
||||
}
|
||||
|
||||
@@ -281,7 +313,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
sParcellingForIdentifier.parcel(mIdentifier, dest, flags);
|
||||
dest.writeString(mPackageName);
|
||||
dest.writeTypedObject(mUser, flags);
|
||||
parcelHostToUserSelectionMap(dest, flags);
|
||||
parcelHostToStateMap(dest, flags);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -300,7 +332,7 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
UUID identifier = sParcellingForIdentifier.unparcel(in);
|
||||
String packageName = in.readString();
|
||||
UserHandle user = (UserHandle) in.readTypedObject(UserHandle.CREATOR);
|
||||
Map<String,Boolean> hostToUserSelectionMap = unparcelHostToUserSelectionMap(in);
|
||||
Map<String,Integer> hostToStateMap = unparcelHostToStateMap(in);
|
||||
|
||||
this.mIdentifier = identifier;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
@@ -314,9 +346,9 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
this.mLinkHandlingAllowed = linkHandlingAllowed;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mLinkHandlingAllowed);
|
||||
this.mHostToUserSelectionMap = hostToUserSelectionMap;
|
||||
this.mHostToStateMap = hostToStateMap;
|
||||
com.android.internal.util.AnnotationValidations.validate(
|
||||
NonNull.class, null, mHostToUserSelectionMap);
|
||||
NonNull.class, null, mHostToStateMap);
|
||||
|
||||
// onConstructed(); // You can define this method to get a callback
|
||||
}
|
||||
@@ -336,10 +368,10 @@ public final class DomainVerificationUserSelection implements Parcelable {
|
||||
};
|
||||
|
||||
@DataClass.Generated(
|
||||
time = 1613002353615L,
|
||||
time = 1613683603297L,
|
||||
codegenVersion = "1.0.22",
|
||||
sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java",
|
||||
inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Boolean> mHostToUserSelectionMap\nprivate void parcelHostToUserSelectionMap(android.os.Parcel,int)\nprivate java.util.Map<java.lang.String,java.lang.Boolean> unparcelHostToUserSelectionMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)")
|
||||
inputSignatures = "public static final int DOMAIN_STATE_NONE\npublic static final int DOMAIN_STATE_SELECTED\npublic static final int DOMAIN_STATE_VERIFIED\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 android.os.UserHandle mUser\nprivate final @android.annotation.NonNull boolean mLinkHandlingAllowed\nprivate final @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,java.lang.Integer> unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationUserSelection extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true, genHiddenConstDefs=true)")
|
||||
@Deprecated
|
||||
private void __metadata() {}
|
||||
|
||||
|
||||
@@ -18,27 +18,27 @@ package android.content.pm.verify.domain;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.os.Binder;
|
||||
import android.os.IBinder;
|
||||
import android.os.Parcel;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/** @hide */
|
||||
/**
|
||||
* @hide
|
||||
*/
|
||||
public class DomainVerificationUtils {
|
||||
|
||||
private static final int STRINGS_TARGET_BYTE_SIZE = IBinder.getSuggestedMaxIpcSizeBytes() / 2;
|
||||
|
||||
/**
|
||||
* Write a map containing web hosts to the given parcel, using {@link Parcel#writeBlob(byte[])}
|
||||
* if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that
|
||||
* the written map is the only data structure in the caller that varies based on the host data
|
||||
* set. Other data that will be written to the parcel after this method will not be considered
|
||||
* in the calculation.
|
||||
* if the limit exceeds {@link IBinder#getSuggestedMaxIpcSizeBytes()} / 2. This assumes that the
|
||||
* written map is the only data structure in the caller that varies based on the host data set.
|
||||
* Other data that will be written to the parcel after this method will not be considered in the
|
||||
* calculation.
|
||||
*/
|
||||
public static void writeHostMap(@NonNull Parcel dest, @NonNull Map<String, ?> map) {
|
||||
boolean targetSizeExceeded = false;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package android.content.pm.verify.domain;
|
||||
|
||||
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.DomainVerificationUserSelection;
|
||||
@@ -36,6 +37,9 @@ interface IDomainVerificationManager {
|
||||
DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName,
|
||||
int userId);
|
||||
|
||||
@nullable
|
||||
List<DomainOwner> getOwnersForDomain(String domain, int userId);
|
||||
|
||||
void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state);
|
||||
|
||||
void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId);
|
||||
|
||||
@@ -787,6 +787,10 @@ public abstract class PackageSettingBase extends SettingBase {
|
||||
return firstInstallTime;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
protected PackageSettingBase updateFrom(PackageSettingBase other) {
|
||||
super.copyFrom(other);
|
||||
setPath(other.getPath());
|
||||
|
||||
@@ -185,6 +185,29 @@ public class DomainVerificationEnforcer {
|
||||
return !mCallback.filterAppAccess(packageName, callingUid, targetUserId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Querying for the owners of a domain. Because this API cannot filter the returned list of
|
||||
* packages, enforces {@link android.Manifest.permission.QUERY_ALL_PACKAGES}, but also enforces
|
||||
* {@link android.Manifest.permission.INTERACT_ACROSS_USERS} because each user has a different
|
||||
* state.
|
||||
*/
|
||||
public void assertOwnerQuerent(int callingUid, @UserIdInt int callingUserId,
|
||||
@UserIdInt int targetUserId) {
|
||||
final int callingPid = Binder.getCallingPid();
|
||||
if (callingUserId != targetUserId) {
|
||||
mContext.enforcePermission(android.Manifest.permission.INTERACT_ACROSS_USERS,
|
||||
callingPid, callingUid, "Caller is not allowed to query other users");
|
||||
}
|
||||
|
||||
mContext.enforcePermission(android.Manifest.permission.QUERY_ALL_PACKAGES,
|
||||
callingPid, callingUid, "Caller " + callingUid + " does not hold "
|
||||
+ android.Manifest.permission.QUERY_ALL_PACKAGES);
|
||||
|
||||
mContext.enforcePermission(
|
||||
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION,
|
||||
callingPid, callingUid, "Caller is not allowed to query user selections");
|
||||
}
|
||||
|
||||
public interface Callback {
|
||||
/**
|
||||
* @return true if access to the given package should be filtered and the method failed as
|
||||
|
||||
@@ -20,14 +20,14 @@ import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
import android.annotation.UserIdInt;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
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.InvalidDomainSetException;
|
||||
import android.content.pm.verify.domain.DomainVerificationManagerImpl;
|
||||
import android.content.pm.verify.domain.DomainVerificationInfo;
|
||||
import android.content.pm.verify.domain.DomainVerificationUserSelection;
|
||||
import android.content.pm.verify.domain.IDomainVerificationManager;
|
||||
import android.os.ServiceSpecificException;
|
||||
import android.util.ArraySet;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
@@ -104,6 +104,17 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<DomainOwner> getOwnersForDomain(@NonNull String domain,
|
||||
@UserIdInt int userId) {
|
||||
try {
|
||||
return mService.getOwnersForDomain(domain, userId);
|
||||
} catch (Exception e) {
|
||||
throw rethrow(e);
|
||||
}
|
||||
}
|
||||
|
||||
private RuntimeException rethrow(Exception exception) throws RuntimeException {
|
||||
if (exception instanceof InvalidDomainSetException) {
|
||||
int packedErrorCode = DomainVerificationManagerImpl.ERROR_INVALID_DOMAIN_SET;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package com.android.server.pm.verify.domain;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
import static java.util.Collections.emptySet;
|
||||
|
||||
import android.annotation.NonNull;
|
||||
import android.annotation.Nullable;
|
||||
@@ -30,6 +31,7 @@ import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManager.NameNotFoundException;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.parsing.component.ParsedActivity;
|
||||
import android.content.pm.verify.domain.DomainOwner;
|
||||
import android.content.pm.verify.domain.DomainVerificationInfo;
|
||||
import android.content.pm.verify.domain.DomainVerificationManager;
|
||||
import android.content.pm.verify.domain.DomainVerificationState;
|
||||
@@ -291,6 +293,8 @@ public class DomainVerificationService extends SystemService
|
||||
throws InvalidDomainSetException, NameNotFoundException {
|
||||
mEnforcer.assertApprovedVerifier(callingUid, mProxy);
|
||||
synchronized (mLock) {
|
||||
List<String> verifiedDomains = new ArrayList<>();
|
||||
|
||||
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
|
||||
true /* forAutoVerify */, callingUid, null /* userId */);
|
||||
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
|
||||
@@ -301,8 +305,17 @@ public class DomainVerificationService extends SystemService
|
||||
continue;
|
||||
}
|
||||
|
||||
if (DomainVerificationManager.isStateVerified(state)) {
|
||||
verifiedDomains.add(domain);
|
||||
}
|
||||
|
||||
stateMap.put(domain, state);
|
||||
}
|
||||
|
||||
int size = verifiedDomains.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
removeUserSelectionsForDomain(verifiedDomains.get(index));
|
||||
}
|
||||
}
|
||||
|
||||
mConnection.scheduleWriteSettings();
|
||||
@@ -387,6 +400,20 @@ public class DomainVerificationService extends SystemService
|
||||
}
|
||||
}
|
||||
|
||||
private void removeUserSelectionsForDomain(@NonNull String domain) {
|
||||
synchronized (mLock) {
|
||||
final int size = mAttachedPkgStates.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
|
||||
SparseArray<DomainVerificationUserState> array = pkgState.getUserSelectionStates();
|
||||
int arraySize = array.size();
|
||||
for (int arrayIndex = 0; arrayIndex < arraySize; arrayIndex++) {
|
||||
array.valueAt(arrayIndex).removeHost(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDomainVerificationLinkHandlingAllowed(@NonNull String packageName,
|
||||
boolean allowed) throws NameNotFoundException {
|
||||
@@ -470,19 +497,59 @@ public class DomainVerificationService extends SystemService
|
||||
InvalidDomainSetException.REASON_ID_INVALID);
|
||||
}
|
||||
|
||||
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
|
||||
false /* forAutoVerify */, callingUid, userId);
|
||||
DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(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.
|
||||
// They can be disabled without blocking in a circular dependency.
|
||||
if (enabled) {
|
||||
// Cache the approved packages from the 1st pass because the search is expensive
|
||||
ArrayMap<String, List<String>> domainToApprovedPackages = new ArrayMap<>();
|
||||
|
||||
for (String domain : domains) {
|
||||
if (!getApprovedPackages(domain, userId, APPROVAL_LEVEL_LEGACY_ALWAYS + 1,
|
||||
mConnection::getPackageSettingLocked).first.isEmpty()) {
|
||||
if (userState.getEnabledHosts().contains(domain)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Pair<List<String>, Integer> packagesToLevel = getApprovedPackages(domain,
|
||||
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);
|
||||
}
|
||||
|
||||
domainToApprovedPackages.put(domain, packagesToLevel.first);
|
||||
}
|
||||
|
||||
// The removal for other packages must be done in a 2nd pass after it's determined
|
||||
// that no higher priority owners exist for all of the domains in the set.
|
||||
int mapSize = domainToApprovedPackages.size();
|
||||
for (int mapIndex = 0; mapIndex < mapSize; mapIndex++) {
|
||||
String domain = domainToApprovedPackages.keyAt(mapIndex);
|
||||
List<String> approvedPackages = domainToApprovedPackages.valueAt(mapIndex);
|
||||
int approvedSize = approvedPackages.size();
|
||||
for (int approvedIndex = 0; approvedIndex < approvedSize; approvedIndex++) {
|
||||
String approvedPackage = approvedPackages.get(approvedIndex);
|
||||
DomainVerificationPkgState approvedPkgState =
|
||||
mAttachedPkgStates.get(approvedPackage);
|
||||
if (approvedPkgState == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
DomainVerificationUserState approvedUserState =
|
||||
approvedPkgState.getUserSelectionState(userId);
|
||||
if (approvedUserState == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
approvedUserState.removeHost(domain);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains,
|
||||
false /* forAutoVerify */, callingUid, userId);
|
||||
DomainVerificationUserState userState = pkgState.getOrCreateUserSelectionState(userId);
|
||||
if (enabled) {
|
||||
userState.addHosts(domains);
|
||||
} else {
|
||||
@@ -600,30 +667,111 @@ public class DomainVerificationService extends SystemService
|
||||
throw DomainVerificationUtils.throwPackageUnavailable(packageName);
|
||||
}
|
||||
|
||||
ArrayMap<String, Boolean> hostToUserSelectionMap = new ArrayMap<>();
|
||||
|
||||
ArraySet<String> domains = mCollector.collectAllWebDomains(pkg);
|
||||
int domainsSize = domains.size();
|
||||
for (int index = 0; index < domainsSize; index++) {
|
||||
hostToUserSelectionMap.put(domains.valueAt(index), false);
|
||||
}
|
||||
ArraySet<String> webDomains = mCollector.collectAllWebDomains(pkg);
|
||||
int webDomainsSize = webDomains.size();
|
||||
|
||||
Map<String, Integer> domains = new ArrayMap<>(webDomainsSize);
|
||||
ArrayMap<String, Integer> stateMap = pkgState.getStateMap();
|
||||
DomainVerificationUserState userState = pkgState.getUserSelectionState(userId);
|
||||
boolean linkHandlingAllowed = true;
|
||||
if (userState != null) {
|
||||
linkHandlingAllowed = userState.isLinkHandlingAllowed();
|
||||
ArraySet<String> enabledHosts = userState.getEnabledHosts();
|
||||
int hostsSize = enabledHosts.size();
|
||||
for (int index = 0; index < hostsSize; index++) {
|
||||
hostToUserSelectionMap.put(enabledHosts.valueAt(index), true);
|
||||
Set<String> enabledHosts = userState == null ? emptySet() : userState.getEnabledHosts();
|
||||
|
||||
for (int index = 0; index < webDomainsSize; index++) {
|
||||
String host = webDomains.valueAt(index);
|
||||
Integer state = stateMap.get(host);
|
||||
|
||||
int domainState;
|
||||
if (state != null && DomainVerificationManager.isStateVerified(state)) {
|
||||
domainState = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED;
|
||||
} else if (enabledHosts.contains(host)) {
|
||||
domainState = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED;
|
||||
} else {
|
||||
domainState = DomainVerificationUserSelection.DOMAIN_STATE_NONE;
|
||||
}
|
||||
|
||||
domains.put(host, domainState);
|
||||
}
|
||||
|
||||
boolean linkHandlingAllowed = userState == null || userState.isLinkHandlingAllowed();
|
||||
|
||||
return new DomainVerificationUserSelection(pkgState.getId(), packageName,
|
||||
UserHandle.of(userId), linkHandlingAllowed, hostToUserSelectionMap);
|
||||
UserHandle.of(userId), linkHandlingAllowed, domains);
|
||||
}
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public List<DomainOwner> getOwnersForDomain(@NonNull String domain) {
|
||||
return getOwnersForDomain(domain, mConnection.getCallingUserId());
|
||||
}
|
||||
|
||||
public List<DomainOwner> getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) {
|
||||
mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(),
|
||||
userId);
|
||||
|
||||
SparseArray<List<String>> levelToPackages = new SparseArray<>();
|
||||
|
||||
// First, collect the raw approval level values
|
||||
synchronized (mLock) {
|
||||
final int size = mAttachedPkgStates.size();
|
||||
for (int index = 0; index < size; index++) {
|
||||
DomainVerificationPkgState pkgState = mAttachedPkgStates.valueAt(index);
|
||||
String packageName = pkgState.getPackageName();
|
||||
PackageSetting pkgSetting = mConnection.getPackageSettingLocked(packageName);
|
||||
if (pkgSetting == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
int level = approvalLevelForDomain(pkgSetting, domain, userId, domain);
|
||||
if (level <= APPROVAL_LEVEL_NONE) {
|
||||
continue;
|
||||
}
|
||||
List<String> list = levelToPackages.get(level);
|
||||
if (list == null) {
|
||||
list = new ArrayList<>();
|
||||
levelToPackages.put(level, list);
|
||||
}
|
||||
list.add(packageName);
|
||||
}
|
||||
}
|
||||
|
||||
final int size = levelToPackages.size();
|
||||
if (size == 0) {
|
||||
return emptyList();
|
||||
}
|
||||
|
||||
// Then sort them ascending by first installed time, with package name as the tie breaker
|
||||
for (int index = 0; index < size; index++) {
|
||||
levelToPackages.valueAt(index).sort((first, second) -> {
|
||||
PackageSetting firstPkgSetting = mConnection.getPackageSettingLocked(first);
|
||||
PackageSetting secondPkgSetting = mConnection.getPackageSettingLocked(second);
|
||||
|
||||
long firstInstallTime =
|
||||
firstPkgSetting == null ? -1L : firstPkgSetting.getFirstInstallTime();
|
||||
long secondInstallTime =
|
||||
secondPkgSetting == null ? -1L : secondPkgSetting.getFirstInstallTime();
|
||||
|
||||
if (firstInstallTime != secondInstallTime) {
|
||||
return (int) (firstInstallTime - secondInstallTime);
|
||||
}
|
||||
|
||||
return first.compareToIgnoreCase(second);
|
||||
});
|
||||
}
|
||||
|
||||
List<DomainOwner> owners = new ArrayList<>();
|
||||
for (int index = 0; index < size; index++) {
|
||||
int level = levelToPackages.keyAt(index);
|
||||
boolean overrideable = level <= APPROVAL_LEVEL_SELECTION;
|
||||
List<String> packages = levelToPackages.valueAt(index);
|
||||
int packagesSize = packages.size();
|
||||
for (int packageIndex = 0; packageIndex < packagesSize; packageIndex++) {
|
||||
owners.add(new DomainOwner(packages.get(packageIndex), overrideable));
|
||||
}
|
||||
}
|
||||
|
||||
return owners;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public UUID generateNewId() {
|
||||
@@ -634,7 +782,7 @@ public class DomainVerificationService extends SystemService
|
||||
@Override
|
||||
public void migrateState(@NonNull PackageSetting oldPkgSetting,
|
||||
@NonNull PackageSetting newPkgSetting) {
|
||||
String pkgName = newPkgSetting.name;
|
||||
String pkgName = newPkgSetting.getName();
|
||||
boolean sendBroadcast;
|
||||
|
||||
synchronized (mLock) {
|
||||
@@ -730,7 +878,7 @@ public class DomainVerificationService extends SystemService
|
||||
// gains or loses all domains.
|
||||
|
||||
UUID domainSetId = newPkgSetting.getDomainSetId();
|
||||
String pkgName = newPkgSetting.name;
|
||||
String pkgName = newPkgSetting.getName();
|
||||
|
||||
boolean sendBroadcast = true;
|
||||
|
||||
@@ -1347,7 +1495,7 @@ public class DomainVerificationService extends SystemService
|
||||
@Override
|
||||
public int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull Intent intent,
|
||||
@PackageManager.ResolveInfoFlags int resolveInfoFlags, @UserIdInt int userId) {
|
||||
String packageName = pkgSetting.name;
|
||||
String packageName = pkgSetting.getName();
|
||||
if (!DomainVerificationUtils.isDomainVerificationIntent(intent, resolveInfoFlags)) {
|
||||
if (DEBUG_APPROVAL) {
|
||||
debugApproval(packageName, intent, userId, false, "not valid intent");
|
||||
@@ -1364,7 +1512,7 @@ public class DomainVerificationService extends SystemService
|
||||
*/
|
||||
private int approvalLevelForDomain(@NonNull PackageSetting pkgSetting, @NonNull String host,
|
||||
@UserIdInt int userId, @NonNull Object debugObject) {
|
||||
String packageName = pkgSetting.name;
|
||||
String packageName = pkgSetting.getName();
|
||||
final AndroidPackage pkg = pkgSetting.getPkg();
|
||||
|
||||
// Should never be null, but if it is, skip this and assume that v2 is enabled
|
||||
|
||||
@@ -58,6 +58,11 @@ public class DomainVerificationUserState {
|
||||
return this;
|
||||
}
|
||||
|
||||
public DomainVerificationUserState removeHost(String host) {
|
||||
mEnabledHosts.remove(host);
|
||||
return this;
|
||||
}
|
||||
|
||||
public DomainVerificationUserState removeHosts(@NonNull ArraySet<String> newHosts) {
|
||||
mEnabledHosts.removeAll(newHosts);
|
||||
return this;
|
||||
|
||||
@@ -100,7 +100,7 @@ class DomainVerificationCoreApiTest {
|
||||
UserHandle.of(10),
|
||||
true,
|
||||
massiveSet.withIndex()
|
||||
.associate { it.value to (it.index % 2 == 0) }
|
||||
.associate { it.value to (it.index % 3) }
|
||||
)
|
||||
},
|
||||
unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) },
|
||||
@@ -118,8 +118,8 @@ class DomainVerificationCoreApiTest {
|
||||
first, second, { it.isLinkHandlingAllowed },
|
||||
{ it.component4() }, IS_EQUAL_TO
|
||||
)
|
||||
assertAll<DomainVerificationUserSelection, Map<String, Boolean>>(
|
||||
first, second, { it.hostToUserSelectionMap },
|
||||
assertAll<DomainVerificationUserSelection, Map<String, Int>>(
|
||||
first, second, { it.hostToStateMap },
|
||||
{ it.component5() }, IS_MAP_EQUAL_TO
|
||||
)
|
||||
}
|
||||
|
||||
@@ -244,6 +244,14 @@ class DomainVerificationEnforcerTest {
|
||||
service(Type.LEGACY_QUERENT, "getLegacyUserState") {
|
||||
getLegacyState(it.targetPackageName, it.userId)
|
||||
},
|
||||
service(Type.OWNER_QUERENT, "getOwnersForDomain") {
|
||||
// Re-use package name, since the result itself isn't relevant
|
||||
getOwnersForDomain(it.targetPackageName)
|
||||
},
|
||||
service(Type.OWNER_QUERENT_USER, "getOwnersForDomainUserId") {
|
||||
// Re-use package name, since the result itself isn't relevant
|
||||
getOwnersForDomain(it.targetPackageName, it.userId)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -327,6 +335,7 @@ class DomainVerificationEnforcerTest {
|
||||
domainSetId
|
||||
)
|
||||
) {
|
||||
whenever(getName()) { packageName }
|
||||
whenever(getPkg()) { mockPkg(packageName) }
|
||||
whenever(this.domainSetId) { domainSetId }
|
||||
whenever(userState) {
|
||||
@@ -357,6 +366,8 @@ class DomainVerificationEnforcerTest {
|
||||
Type.SELECTOR_USER -> approvedUserSelector(verifyCrossUser = true)
|
||||
Type.LEGACY_QUERENT -> legacyQuerent()
|
||||
Type.LEGACY_SELECTOR -> legacyUserSelector()
|
||||
Type.OWNER_QUERENT -> ownerQuerent(verifyCrossUser = false)
|
||||
Type.OWNER_QUERENT_USER -> ownerQuerent(verifyCrossUser = true)
|
||||
}.run { /*exhaust*/ }
|
||||
}
|
||||
|
||||
@@ -628,6 +639,80 @@ class DomainVerificationEnforcerTest {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = false)
|
||||
}
|
||||
|
||||
private fun ownerQuerent(verifyCrossUser: Boolean) {
|
||||
val allowQueryAll = AtomicBoolean(false)
|
||||
val allowUserSelection = AtomicBoolean(false)
|
||||
val allowInteractAcrossUsers = AtomicBoolean(false)
|
||||
val context: Context = mockThrowOnUnmocked {
|
||||
initPermission(
|
||||
allowQueryAll,
|
||||
android.Manifest.permission.QUERY_ALL_PACKAGES
|
||||
)
|
||||
initPermission(
|
||||
allowUserSelection,
|
||||
android.Manifest.permission.UPDATE_DOMAIN_VERIFICATION_USER_SELECTION
|
||||
)
|
||||
initPermission(
|
||||
allowInteractAcrossUsers,
|
||||
android.Manifest.permission.INTERACT_ACROSS_USERS
|
||||
)
|
||||
}
|
||||
val target = params.construct(context)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val callingUserId = 0
|
||||
val notCallingUserId = 1
|
||||
|
||||
runTestCases(callingUserId, callingUserId, throws = true)
|
||||
if (verifyCrossUser) {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = true)
|
||||
}
|
||||
|
||||
allowQueryAll.set(true)
|
||||
|
||||
runTestCases(callingUserId, callingUserId, throws = true)
|
||||
if (verifyCrossUser) {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = true)
|
||||
}
|
||||
|
||||
allowUserSelection.set(true)
|
||||
|
||||
runTestCases(callingUserId, callingUserId, throws = false)
|
||||
if (verifyCrossUser) {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = true)
|
||||
}
|
||||
|
||||
allowQueryAll.set(false)
|
||||
|
||||
runTestCases(callingUserId, callingUserId, throws = true)
|
||||
if (verifyCrossUser) {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = true)
|
||||
}
|
||||
|
||||
allowQueryAll.set(true)
|
||||
allowInteractAcrossUsers.set(true)
|
||||
|
||||
runTestCases(callingUserId, callingUserId, throws = false)
|
||||
if (verifyCrossUser) {
|
||||
runTestCases(callingUserId, notCallingUserId, throws = false)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Context.initPermission(boolean: AtomicBoolean, permission: String) {
|
||||
whenever(enforcePermission(eq(permission), anyInt(), anyInt(), anyString())) {
|
||||
if (!boolean.get()) {
|
||||
@@ -694,6 +779,12 @@ class DomainVerificationEnforcerTest {
|
||||
LEGACY_QUERENT,
|
||||
|
||||
// Holding the legacy preferred apps permission
|
||||
LEGACY_SELECTOR
|
||||
LEGACY_SELECTOR,
|
||||
|
||||
// Holding user setting permission, but not targeting a package
|
||||
OWNER_QUERENT,
|
||||
|
||||
// Holding user setting permission, but not targeting a package, but targeting cross user
|
||||
OWNER_QUERENT_USER,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
/*
|
||||
* 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.Context
|
||||
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.DomainVerificationManager
|
||||
import android.content.pm.verify.domain.DomainVerificationUserSelection
|
||||
import android.os.Build
|
||||
import android.os.PatternMatcher
|
||||
import android.os.Process
|
||||
import android.util.ArraySet
|
||||
import androidx.test.InstrumentationRegistry
|
||||
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
|
||||
|
||||
class DomainVerificationManagerUserSelectionOverrideTest {
|
||||
|
||||
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_ONE =
|
||||
DomainVerificationManagerUserSelectionOverrideTest::class.java.packageName
|
||||
|
||||
private const val STATE_NONE = DomainVerificationUserSelection.DOMAIN_STATE_NONE
|
||||
private const val STATE_SELECTED = DomainVerificationUserSelection.DOMAIN_STATE_SELECTED
|
||||
private const val STATE_VERIFIED = DomainVerificationUserSelection.DOMAIN_STATE_VERIFIED
|
||||
}
|
||||
|
||||
private val pkg1 = mockPkgSetting(PKG_ONE, UUID_ONE)
|
||||
private val pkg2 = mockPkgSetting(PKG_TWO, UUID_TWO)
|
||||
|
||||
fun makeManager(): DomainVerificationManager =
|
||||
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(scheduleWriteSettings())
|
||||
|
||||
// Need to provide an internal UID so some permission checks are ignored
|
||||
whenever(callingUid) { Process.ROOT_UID }
|
||||
whenever(callingUserId) { 0 }
|
||||
whenever(getPackageSettingLocked(PKG_ONE)) { pkg1 }
|
||||
whenever(getPackageSettingLocked(PKG_TWO)) { pkg2 }
|
||||
whenever(getPackageLocked(PKG_ONE)) { pkg1.getPkg() }
|
||||
whenever(getPackageLocked(PKG_TWO)) { pkg2.getPkg() }
|
||||
})
|
||||
addPackage(pkg1)
|
||||
addPackage(pkg2)
|
||||
|
||||
// Starting state for all tests is to have domain 1 enabled for the first package
|
||||
setDomainVerificationUserSelection(UUID_ONE, setOf(DOMAIN_ONE), true)
|
||||
|
||||
assertThat(stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
|
||||
}
|
||||
|
||||
fun mockPkgSetting(pkgName: String, domainSetId: UUID) = mockThrowOnUnmocked<PackageSetting> {
|
||||
val pkg = mockThrowOnUnmocked<AndroidPackage> {
|
||||
whenever(packageName) { pkgName }
|
||||
whenever(targetSdkVersion) { Build.VERSION_CODES.S }
|
||||
|
||||
val activityList = listOf(
|
||||
ParsedActivity().apply {
|
||||
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(DOMAIN_ONE, null)
|
||||
}
|
||||
)
|
||||
addIntent(
|
||||
ParsedIntentInfo().apply {
|
||||
autoVerify = true
|
||||
addAction(Intent.ACTION_VIEW)
|
||||
addCategory(Intent.CATEGORY_BROWSABLE)
|
||||
addCategory(Intent.CATEGORY_DEFAULT)
|
||||
addDataScheme("http")
|
||||
addDataPath("/sub2", PatternMatcher.PATTERN_LITERAL)
|
||||
addDataAuthority("example2.com", null)
|
||||
}
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
whenever(activities) { activityList }
|
||||
}
|
||||
|
||||
whenever(getPkg()) { pkg }
|
||||
whenever(getName()) { pkgName }
|
||||
whenever(this.domainSetId) { domainSetId }
|
||||
whenever(getInstantApp(anyInt())) { false }
|
||||
whenever(firstInstallTime) { 0L }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun anotherPackageTakeoverSuccess() {
|
||||
val manager = makeManager()
|
||||
|
||||
// Attempt override by package 2
|
||||
manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
|
||||
|
||||
// 1 loses approval
|
||||
assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_NONE)
|
||||
|
||||
// 2 gains approval
|
||||
assertThat(manager.stateFor(PKG_TWO, DOMAIN_ONE)).isEqualTo(STATE_SELECTED)
|
||||
|
||||
// 2 is the only owner
|
||||
assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
|
||||
.containsExactly(PKG_TWO)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun anotherPackageTakeoverFailure() {
|
||||
val manager = makeManager()
|
||||
|
||||
// Verify 1 to give it a higher approval level
|
||||
manager.setDomainVerificationStatus(UUID_ONE, setOf(DOMAIN_ONE),
|
||||
DomainVerificationManager.STATE_SUCCESS)
|
||||
assertThat(manager.stateFor(PKG_ONE, DOMAIN_ONE)).isEqualTo(STATE_VERIFIED)
|
||||
assertThat(manager.getOwnersForDomain(DOMAIN_ONE).map { it.packageName })
|
||||
.containsExactly(PKG_ONE)
|
||||
|
||||
// Attempt override by package 2
|
||||
manager.setDomainVerificationUserSelection(UUID_TWO, setOf(DOMAIN_ONE), true)
|
||||
}
|
||||
|
||||
private fun DomainVerificationManager.stateFor(pkgName: String, host: String) =
|
||||
getDomainVerificationUserSelection(pkgName)!!.hostToStateMap[host]
|
||||
}
|
||||
@@ -34,7 +34,7 @@ operator fun DomainVerificationUserSelection.component1() = identifier
|
||||
operator fun DomainVerificationUserSelection.component2() = packageName
|
||||
operator fun DomainVerificationUserSelection.component3() = user
|
||||
operator fun DomainVerificationUserSelection.component4() = isLinkHandlingAllowed
|
||||
operator fun DomainVerificationUserSelection.component5() = hostToUserSelectionMap
|
||||
operator fun DomainVerificationUserSelection.component5() = hostToStateMap
|
||||
|
||||
operator fun DomainVerificationPersistence.ReadResult.component1() = active
|
||||
operator fun DomainVerificationPersistence.ReadResult.component2() = restored
|
||||
|
||||
@@ -237,6 +237,7 @@ class DomainVerificationSettingsMutationTest {
|
||||
TEST_UUID
|
||||
)
|
||||
) {
|
||||
whenever(getName()) { TEST_PKG }
|
||||
whenever(getPkg()) { mockPkg() }
|
||||
whenever(domainSetId) { TEST_UUID }
|
||||
whenever(userState) {
|
||||
|
||||
Reference in New Issue
Block a user