diff --git a/core/api/system-current.txt b/core/api/system-current.txt index 368e24987d14b..1efef75de7621 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -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 CREATOR; + } + public final class DomainVerificationInfo implements android.os.Parcelable { method public int describeContents(); method @NonNull public java.util.Map 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 getOwnersForDomain(@NonNull String); method @NonNull @RequiresPermission(android.Manifest.permission.DOMAIN_VERIFICATION_AGENT) public java.util.List 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 getHostToUserSelectionMap(); + method @NonNull public java.util.Map 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 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 } } diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.aidl b/core/java/android/content/pm/verify/domain/DomainOwner.aidl new file mode 100644 index 0000000000000..41366d1a29b2a --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.aidl @@ -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; diff --git a/core/java/android/content/pm/verify/domain/DomainOwner.java b/core/java/android/content/pm/verify/domain/DomainOwner.java new file mode 100644 index 0000000000000..b050f5da79286 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainOwner.java @@ -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 CREATOR + = new Parcelable.Creator() { + @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 + +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java index cbb3baaa6700c..11402afac8b68 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManager.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManager.java @@ -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 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 diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java index 459e4197faae4..8b9865c2b436a 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java @@ -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 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); } diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java index 612b64c98720d..d23f5f133841e 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java @@ -42,9 +42,9 @@ import java.util.UUID; * toggle affects all links and is not based on the verification state of the domains. *

* 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. *

* 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 mHostToUserSelectionMap; + private final Map 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 unparcelHostToUserSelectionMap(Parcel in) { + @NonNull + private Map 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 hostToUserSelectionMap) { + @NonNull Map 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 getHostToUserSelectionMap() { - return mHostToUserSelectionMap; + public @NonNull Map 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 hostToUserSelectionMap = unparcelHostToUserSelectionMap(in); + Map 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 mHostToUserSelectionMap\nprivate void parcelHostToUserSelectionMap(android.os.Parcel,int)\nprivate java.util.Map 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 mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate @android.annotation.NonNull java.util.Map 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() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java index 83265c71da24b..93005fae17728 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java @@ -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 map) { boolean targetSizeExceeded = false; diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index b3b41009177e5..701af320fb019 100644 --- a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl +++ b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl @@ -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 getOwnersForDomain(String domain, int userId); + void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); diff --git a/services/core/java/com/android/server/pm/PackageSettingBase.java b/services/core/java/com/android/server/pm/PackageSettingBase.java index a83a3f81bc002..38e100e80cd31 100644 --- a/services/core/java/com/android/server/pm/PackageSettingBase.java +++ b/services/core/java/com/android/server/pm/PackageSettingBase.java @@ -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()); diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java index 275dd053fdde6..ed37fa0da01f9 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationEnforcer.java @@ -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 diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java index e84062f23f296..6f2810785c604 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationManagerStub.java @@ -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 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; diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java index 8dcb45ff5ab62..b58c1ff374d52 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationService.java @@ -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 verifiedDomains = new ArrayList<>(); + DomainVerificationPkgState pkgState = getAndValidateAttachedLocked(domainSetId, domains, true /* forAutoVerify */, callingUid, null /* userId */); ArrayMap 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 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> 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, 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 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 hostToUserSelectionMap = new ArrayMap<>(); - - ArraySet domains = mCollector.collectAllWebDomains(pkg); - int domainsSize = domains.size(); - for (int index = 0; index < domainsSize; index++) { - hostToUserSelectionMap.put(domains.valueAt(index), false); - } + ArraySet webDomains = mCollector.collectAllWebDomains(pkg); + int webDomainsSize = webDomains.size(); + Map domains = new ArrayMap<>(webDomainsSize); + ArrayMap stateMap = pkgState.getStateMap(); DomainVerificationUserState userState = pkgState.getUserSelectionState(userId); - boolean linkHandlingAllowed = true; - if (userState != null) { - linkHandlingAllowed = userState.isLinkHandlingAllowed(); - ArraySet enabledHosts = userState.getEnabledHosts(); - int hostsSize = enabledHosts.size(); - for (int index = 0; index < hostsSize; index++) { - hostToUserSelectionMap.put(enabledHosts.valueAt(index), true); + Set 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 getOwnersForDomain(@NonNull String domain) { + return getOwnersForDomain(domain, mConnection.getCallingUserId()); + } + + public List getOwnersForDomain(@NonNull String domain, @UserIdInt int userId) { + mEnforcer.assertOwnerQuerent(mConnection.getCallingUid(), mConnection.getCallingUserId(), + userId); + + SparseArray> 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 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 owners = new ArrayList<>(); + for (int index = 0; index < size; index++) { + int level = levelToPackages.keyAt(index); + boolean overrideable = level <= APPROVAL_LEVEL_SELECTION; + List 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 diff --git a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java index 22468640800e9..8fbb33afb6ca5 100644 --- a/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java +++ b/services/core/java/com/android/server/pm/verify/domain/models/DomainVerificationUserState.java @@ -58,6 +58,11 @@ public class DomainVerificationUserState { return this; } + public DomainVerificationUserState removeHost(String host) { + mEnabledHosts.remove(host); + return this; + } + public DomainVerificationUserState removeHosts(@NonNull ArraySet newHosts) { mEnabledHosts.removeAll(newHosts); return this; diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt index d18af07f96734..9447f390ada06 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCoreApiTest.kt @@ -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>( - first, second, { it.hostToUserSelectionMap }, + assertAll>( + first, second, { it.hostToStateMap }, { it.component5() }, IS_MAP_EQUAL_TO ) } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt index 2d23fb4990bf6..89394837655a8 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt @@ -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, } } diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt new file mode 100644 index 0000000000000..48056a2b54d14 --- /dev/null +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerUserSelectionOverrideTest.kt @@ -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() } + }, 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 { + val pkg = mockThrowOnUnmocked { + 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] +} diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt index a76d8cee582cf..439048ce51bb6 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationModelExtensions.kt @@ -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 diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt index 48518f4693dd7..010eacf3f51f8 100644 --- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt +++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt @@ -237,6 +237,7 @@ class DomainVerificationSettingsMutationTest { TEST_UUID ) ) { + whenever(getName()) { TEST_PKG } whenever(getPkg()) { mockPkg() } whenever(domainSetId) { TEST_UUID } whenever(userState) {