From 87f799c8bf11ba4d54641277fec3861ac071f0cb Mon Sep 17 00:00:00 2001 From: Winson Date: Wed, 10 Feb 2021 17:23:35 -0800 Subject: [PATCH] Send domains through Parcel#writeBlob to avoid size limit If the domains in each data class exceed 32 KB, serializes them using writeBlob so that they get passed through shared memory rather than the Binder transaction. Bug: 177553185 Test: atest DomainVerificationCoreApiTest Change-Id: I0dd4bd140c8b847eb604d8e42272ca5be5690731 --- .../content/pm/verify/domain/DomainSet.aidl | 19 ++ .../content/pm/verify/domain/DomainSet.java | 160 ++++++++++++++++ .../verify/domain/DomainVerificationInfo.java | 96 +++++----- .../domain/DomainVerificationManagerImpl.java | 4 +- .../domain/DomainVerificationRequest.java | 52 +++-- .../DomainVerificationUserSelection.java | 56 +++--- .../domain/DomainVerificationUtils.java | 181 ++++++++++++++++++ .../domain/IDomainVerificationManager.aidl | 5 +- .../domain/DomainVerificationCollector.java | 73 ++++--- .../domain/DomainVerificationManagerStub.java | 9 +- .../domain/DomainVerificationCoreApiTest.kt | 70 ++++--- 11 files changed, 581 insertions(+), 144 deletions(-) create mode 100644 core/java/android/content/pm/verify/domain/DomainSet.aidl create mode 100644 core/java/android/content/pm/verify/domain/DomainSet.java create mode 100644 core/java/android/content/pm/verify/domain/DomainVerificationUtils.java diff --git a/core/java/android/content/pm/verify/domain/DomainSet.aidl b/core/java/android/content/pm/verify/domain/DomainSet.aidl new file mode 100644 index 0000000000000..fab131dfa3174 --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.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 DomainSet; diff --git a/core/java/android/content/pm/verify/domain/DomainSet.java b/core/java/android/content/pm/verify/domain/DomainSet.java new file mode 100644 index 0000000000000..243ff0820e24b --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainSet.java @@ -0,0 +1,160 @@ +/* + * 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.os.Parcel; +import android.os.Parcelable; + +import com.android.internal.util.DataClass; + +import java.util.Set; + +/** + * Wraps an input set of domains from the client process, to be sent to the server. Handles cases + * where the data size is too large by writing data using {@link Parcel#writeBlob(byte[])}. + * + * @hide + */ +@DataClass(genParcelable = true, genAidl = true, genEqualsHashCode = true) +public class DomainSet implements Parcelable { + + @NonNull + private final Set mDomains; + + private void parcelDomains(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mDomains); + } + + private Set unparcelDomains(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + + + + // 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 + // /DomainSet.java + // + // To exclude the generated code from IntelliJ auto-formatting enable (one-time): + // Settings > Editor > Code Style > Formatter Control + //@formatter:off + + + @DataClass.Generated.Member + public DomainSet( + @NonNull Set domains) { + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // onConstructed(); // You can define this method to get a callback + } + + @DataClass.Generated.Member + public @NonNull Set getDomains() { + return mDomains; + } + + @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(DomainSet other) { ... } + // boolean fieldNameEquals(FieldType otherValue) { ... } + + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + @SuppressWarnings("unchecked") + DomainSet that = (DomainSet) o; + //noinspection PointlessBooleanExpression + return true + && java.util.Objects.equals(mDomains, that.mDomains); + } + + @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(mDomains); + return _hash; + } + + @Override + @DataClass.Generated.Member + public void writeToParcel(@NonNull Parcel dest, int flags) { + // You can override field parcelling by defining methods like: + // void parcelFieldName(Parcel dest, int flags) { ... } + + parcelDomains(dest, flags); + } + + @Override + @DataClass.Generated.Member + public int describeContents() { return 0; } + + /** @hide */ + @SuppressWarnings({"unchecked", "RedundantCast"}) + @DataClass.Generated.Member + protected DomainSet(@NonNull Parcel in) { + // You can override field unparcelling by defining methods like: + // static FieldType unparcelFieldName(Parcel in) { ... } + + Set domains = unparcelDomains(in); + + this.mDomains = domains; + com.android.internal.util.AnnotationValidations.validate( + NonNull.class, null, mDomains); + + // 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 DomainSet[] newArray(int size) { + return new DomainSet[size]; + } + + @Override + public DomainSet createFromParcel(@NonNull Parcel in) { + return new DomainSet(in); + } + }; + + @DataClass.Generated( + time = 1613169242020L, + codegenVersion = "1.0.22", + sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainSet.java", + inputSignatures = "private final @android.annotation.NonNull java.util.Set mDomains\nprivate void parcelDomains(android.os.Parcel,int)\nprivate java.util.Set unparcelDomains(android.os.Parcel)\nclass DomainSet extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genAidl=true, genEqualsHashCode=true)") + @Deprecated + private void __metadata() {} + + + //@formatter:on + // End of generated code + +} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java index 7afbe1fcb69fa..809587524f586 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java @@ -19,7 +19,9 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.pm.PackageManager; +import android.os.Parcel; import android.os.Parcelable; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -34,12 +36,12 @@ import java.util.UUID; * against the digital asset links response from the server hosting that domain. *

* These values for each domain can be modified through - * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, Set, int)}. + * {@link DomainVerificationManager#setDomainVerificationStatus(UUID, + * Set, int)}. * * @hide */ @SystemApi -@SuppressWarnings("DefaultAnnotationParam") @DataClass(genAidl = true, genHiddenConstructor = true, genParcelable = true, genToString = true, genEqualsHashCode = true) public final class DomainVerificationInfo implements Parcelable { @@ -71,22 +73,30 @@ public final class DomainVerificationInfo implements Parcelable { private final String mPackageName; /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. *

- * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @NonNull private final Map mHostToStateMap; + private void parcelHostToStateMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToStateMap); + } + + private Map unparcelHostToStateMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } + // Code below generated by codegen v1.0.22. @@ -95,7 +105,8 @@ public final class DomainVerificationInfo implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationInfo.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -123,18 +134,17 @@ public final class DomainVerificationInfo implements Parcelable { * @param packageName * The package name that this data corresponds to. * @param hostToStateMap - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. *

- * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. * @hide */ @DataClass.Generated.Member @@ -185,18 +195,17 @@ public final class DomainVerificationInfo implements Parcelable { } /** - * Map of host names to their current state. State is an integer, which defaults to - * {@link DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the - * domain verification agent (the intended consumer of this API), which can be equal - * to {@link DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or - * greater than {@link DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for - * any unsuccessful response. + * Map of host names to their current state. State is an integer, which defaults to {@link + * DomainVerificationManager#STATE_NO_RESPONSE}. State can be modified by the domain + * verification agent (the intended consumer of this API), which can be equal to {@link + * DomainVerificationManager#STATE_SUCCESS} when verified, or equal to or greater than {@link + * DomainVerificationManager#STATE_FIRST_VERIFIER_DEFINED} for any unsuccessful response. *

- * Any value non-inclusive between those 2 values are reserved for use by the system. - * The domain verification agent may be able to act on these reserved values, and this - * ability can be queried using {@link DomainVerificationManager#isStateModifiable(int)}. - * It is expected that the agent attempt to verify all domains that it can modify the - * state of, even if it does not understand the meaning of those values. + * Any value non-inclusive between those 2 values are reserved for use by the system. The domain + * verification agent may be able to act on these reserved values, and this ability can be + * queried using {@link DomainVerificationManager#isStateModifiable(int)}. It is expected that + * the agent attempt to verify all domains that it can modify the state of, even if it does not + * understand the meaning of those values. */ @DataClass.Generated.Member public @NonNull Map getHostToStateMap() { @@ -260,13 +269,13 @@ public final class DomainVerificationInfo implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); - dest.writeMap(mHostToStateMap); + parcelHostToStateMap(dest, flags); } @Override @@ -276,14 +285,13 @@ public final class DomainVerificationInfo implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationInfo(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationInfo(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } UUID identifier = sParcellingForIdentifier.unparcel(in); String packageName = in.readString(); - Map hostToStateMap = new java.util.LinkedHashMap<>(); - in.readMap(hostToStateMap, Integer.class.getClassLoader()); + Map hostToStateMap = unparcelHostToStateMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -307,16 +315,16 @@ public final class DomainVerificationInfo implements Parcelable { } @Override - public DomainVerificationInfo createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationInfo createFromParcel(@NonNull Parcel in) { return new DomainVerificationInfo(in); } }; @DataClass.Generated( - time = 1611862790369L, + time = 1613002530369L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationInfo.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map mHostToStateMap\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForUUID.class) java.util.UUID mIdentifier\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nprivate final @android.annotation.NonNull java.util.Map mHostToStateMap\nprivate void parcelHostToStateMap(android.os.Parcel,int)\nprivate java.util.Map unparcelHostToStateMap(android.os.Parcel)\nclass DomainVerificationInfo extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=true, genHiddenConstructor=true, genParcelable=true, genToString=true, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java index 5938def5c83c9..459e4197faae4 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationManagerImpl.java @@ -89,7 +89,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager int state) throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationStatus(domainSetId.toString(), - new ArrayList<>(domains), state); + new DomainSet(domains), state); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { @@ -126,7 +126,7 @@ public class DomainVerificationManagerImpl implements DomainVerificationManager throws IllegalArgumentException, NameNotFoundException { try { mDomainVerificationManager.setDomainVerificationUserSelection(domainSetId.toString(), - new ArrayList<>(domains), enabled, mContext.getUserId()); + new DomainSet(domains), enabled, mContext.getUserId()); } catch (Exception e) { Exception converted = rethrow(e, domainSetId); if (converted instanceof NameNotFoundException) { diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java index 473abce26d81e..65f6d7c181350 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java @@ -19,6 +19,7 @@ package android.content.pm.verify.domain; import android.annotation.NonNull; import android.annotation.SystemApi; import android.content.Intent; +import android.os.Parcel; import android.os.Parcelable; import com.android.internal.util.DataClass; @@ -27,11 +28,11 @@ import com.android.internal.util.Parcelling; import java.util.Set; /** - * Request object sent in the {@link Intent} that's broadcast to the domain verification - * agent, retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. + * Request object sent in the {@link Intent} that's broadcast to the domain verification agent, + * retrieved through {@link DomainVerificationManager#EXTRA_VERIFICATION_REQUEST}. *

- * This contains the set of packages which have been invalidated and will require - * re-verification. The exact domains can be retrieved with + * This contains the set of packages which have been invalidated and will require re-verification. + * The exact domains can be retrieved with * {@link DomainVerificationManager#getDomainVerificationInfo(String)} * * @hide @@ -42,14 +43,22 @@ import java.util.Set; public final class DomainVerificationRequest implements Parcelable { /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @NonNull @DataClass.ParcelWith(Parcelling.BuiltIn.ForStringSet.class) private final Set mPackageNames; + private void parcelPackageNames(@NonNull Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostSet(dest, mPackageNames); + } + + private Set unparcelPackageNames(@NonNull Parcel in) { + return DomainVerificationUtils.readHostSet(in); + } + // Code below generated by codegen v1.0.22. @@ -58,7 +67,8 @@ public final class DomainVerificationRequest implements Parcelable { // CHECKSTYLE:OFF Generated code // // To regenerate run: - // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java + // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/verify/domain + // /DomainVerificationRequest.java // // To exclude the generated code from IntelliJ auto-formatting enable (one-time): // Settings > Editor > Code Style > Formatter Control @@ -69,9 +79,9 @@ public final class DomainVerificationRequest implements Parcelable { * Creates a new DomainVerificationRequest. * * @param packageNames - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. * @hide */ @DataClass.Generated.Member @@ -85,9 +95,9 @@ public final class DomainVerificationRequest implements Parcelable { } /** - * The package names of the apps that need to be verified. The receiver should call - * {@link DomainVerificationManager#getDomainVerificationInfo(String)} with each of - * these values to get the actual set of domains that need to be acted on. + * The package names of the apps that need to be verified. The receiver should call {@link + * DomainVerificationManager#getDomainVerificationInfo(String)} with each of these values to get + * the actual set of domains that need to be acted on. */ @DataClass.Generated.Member public @NonNull Set getPackageNames() { @@ -134,11 +144,11 @@ public final class DomainVerificationRequest implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } - sParcellingForPackageNames.parcel(mPackageNames, dest, flags); + parcelPackageNames(dest, flags); } @Override @@ -148,11 +158,11 @@ public final class DomainVerificationRequest implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationRequest(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationRequest(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } - Set packageNames = sParcellingForPackageNames.unparcel(in); + Set packageNames = unparcelPackageNames(in); this.mPackageNames = packageNames; com.android.internal.util.AnnotationValidations.validate( @@ -170,16 +180,16 @@ public final class DomainVerificationRequest implements Parcelable { } @Override - public DomainVerificationRequest createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationRequest createFromParcel(@NonNull Parcel in) { return new DomainVerificationRequest(in); } }; @DataClass.Generated( - time = 1611862814990L, + time = 1613169505495L, codegenVersion = "1.0.22", sourceFile = "frameworks/base/core/java/android/content/pm/verify/domain/DomainVerificationRequest.java", - inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set mPackageNames\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") + inputSignatures = "private final @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForStringSet.class) java.util.Set mPackageNames\nprivate void parcelPackageNames(android.os.Parcel,int)\nprivate java.util.Set unparcelPackageNames(android.os.Parcel)\nclass DomainVerificationRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genHiddenConstructor=true, genAidl=false, genEqualsHashCode=true)") @Deprecated private void __metadata() {} diff --git a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java index 73346ef0273b2..612b64c98720d 100644 --- a/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUserSelection.java @@ -20,8 +20,10 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SystemApi; import android.content.Context; +import android.os.Parcel; import android.os.Parcelable; import android.os.UserHandle; +import android.util.ArrayMap; import com.android.internal.util.DataClass; import com.android.internal.util.Parcelling; @@ -46,13 +48,12 @@ import java.util.UUID; *

* These values can be changed through the * {@link DomainVerificationManager#setDomainVerificationLinkHandlingAllowed(String, - * boolean)} and - * {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, + * boolean)} and {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, Set, * boolean)} APIs. *

- * Note that because state is per user, if a different user needs to be changed, one will - * need to use {@link Context#createContextAsUser(UserHandle, int)} and hold the - * {@link android.Manifest.permission#INTERACT_ACROSS_USERS} permission. + * Note that because state is per user, if a different user needs to be changed, one will need to + * use {@link Context#createContextAsUser(UserHandle, int)} and hold the {@link + * android.Manifest.permission#INTERACT_ACROSS_USERS} permission. * * @hide */ @@ -88,16 +89,24 @@ 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)}. + * 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. */ @NonNull private final Map mHostToUserSelectionMap; + private void parcelHostToUserSelectionMap(Parcel dest, @SuppressWarnings("unused") int flags) { + DomainVerificationUtils.writeHostMap(dest, mHostToUserSelectionMap); + } + + private Map unparcelHostToUserSelectionMap(Parcel in) { + return DomainVerificationUtils.readHostMap(in, new ArrayMap<>(), + DomainVerificationUserSelection.class.getClassLoader()); + } + // Code below generated by codegen v1.0.22. @@ -124,10 +133,9 @@ public final class DomainVerificationUserSelection implements Parcelable { * @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)}. + * Retrieve the existing user selection state for the matching {@link #getPackageName()}, as was + * previously set by {@link DomainVerificationManager#setDomainVerificationUserSelection(UUID, + * Set, boolean)}. * @hide */ @DataClass.Generated.Member @@ -189,10 +197,9 @@ 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)}. + * 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. */ @@ -264,7 +271,7 @@ public final class DomainVerificationUserSelection implements Parcelable { @Override @DataClass.Generated.Member - public void writeToParcel(@NonNull android.os.Parcel dest, int flags) { + public void writeToParcel(@NonNull Parcel dest, int flags) { // You can override field parcelling by defining methods like: // void parcelFieldName(Parcel dest, int flags) { ... } @@ -274,7 +281,7 @@ public final class DomainVerificationUserSelection implements Parcelable { sParcellingForIdentifier.parcel(mIdentifier, dest, flags); dest.writeString(mPackageName); dest.writeTypedObject(mUser, flags); - dest.writeMap(mHostToUserSelectionMap); + parcelHostToUserSelectionMap(dest, flags); } @Override @@ -284,7 +291,7 @@ public final class DomainVerificationUserSelection implements Parcelable { /** @hide */ @SuppressWarnings({"unchecked", "RedundantCast"}) @DataClass.Generated.Member - /* package-private */ DomainVerificationUserSelection(@NonNull android.os.Parcel in) { + /* package-private */ DomainVerificationUserSelection(@NonNull Parcel in) { // You can override field unparcelling by defining methods like: // static FieldType unparcelFieldName(Parcel in) { ... } @@ -293,8 +300,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 = new java.util.LinkedHashMap<>(); - in.readMap(hostToUserSelectionMap, Boolean.class.getClassLoader()); + Map hostToUserSelectionMap = unparcelHostToUserSelectionMap(in); this.mIdentifier = identifier; com.android.internal.util.AnnotationValidations.validate( @@ -324,16 +330,16 @@ public final class DomainVerificationUserSelection implements Parcelable { } @Override - public DomainVerificationUserSelection createFromParcel(@NonNull android.os.Parcel in) { + public DomainVerificationUserSelection createFromParcel(@NonNull Parcel in) { return new DomainVerificationUserSelection(in); } }; @DataClass.Generated( - time = 1612829797220L, + time = 1613002353615L, 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\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 = "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)") @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 new file mode 100644 index 0000000000000..83265c71da24b --- /dev/null +++ b/core/java/android/content/pm/verify/domain/DomainVerificationUtils.java @@ -0,0 +1,181 @@ +/* + * 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.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 */ +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. + */ + public static void writeHostMap(@NonNull Parcel dest, @NonNull Map map) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : map.keySet()) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + dest.writeMap(map); + return; + } + + Parcel data = Parcel.obtain(); + try { + data.writeMap(map); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * Retrieve a map previously written by {@link #writeHostMap(Parcel, Map)}. + */ + @NonNull + @SuppressWarnings("rawtypes") + public static T readHostMap(@NonNull Parcel in, @NonNull T map, + @NonNull ClassLoader classLoader) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + in.readMap(map, classLoader); + return map; + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + data.readMap(map, classLoader); + } finally { + data.recycle(); + } + + return map; + } + + /** + * {@link ArraySet} variant of {@link #writeHostMap(Parcel, Map)}. + */ + public static void writeHostSet(@NonNull Parcel dest, @NonNull Set set) { + boolean targetSizeExceeded = false; + int totalSize = dest.dataSize(); + for (String host : set) { + totalSize += estimatedByteSizeOf(host); + if (totalSize > STRINGS_TARGET_BYTE_SIZE) { + targetSizeExceeded = true; + break; + } + } + + dest.writeBoolean(targetSizeExceeded); + + if (!targetSizeExceeded) { + writeSet(dest, set); + return; + } + + Parcel data = Parcel.obtain(); + try { + writeSet(data, set); + dest.writeBlob(data.marshall()); + } finally { + data.recycle(); + } + } + + /** + * {@link ArraySet} variant of {@link #readHostMap(Parcel, Map, ClassLoader)}. + */ + @NonNull + public static Set readHostSet(@NonNull Parcel in) { + boolean targetSizeExceeded = in.readBoolean(); + + if (!targetSizeExceeded) { + return readSet(in); + } + + Parcel data = Parcel.obtain(); + try { + byte[] blob = in.readBlob(); + data.unmarshall(blob, 0, blob.length); + data.setDataPosition(0); + return readSet(data); + } finally { + data.recycle(); + } + } + + private static void writeSet(@NonNull Parcel dest, @Nullable Set set) { + if (set == null) { + dest.writeInt(-1); + return; + } + dest.writeInt(set.size()); + for (String string : set) { + dest.writeString(string); + } + } + + @NonNull + private static Set readSet(@NonNull Parcel in) { + int size = in.readInt(); + if (size == -1) { + return Collections.emptySet(); + } + + ArraySet set = new ArraySet<>(size); + for (int count = 0; count < size; count++) { + set.add(in.readString()); + } + return set; + } + + /** + * Ballpark the size of domains to avoid unnecessary allocation of ashmem when sending domains + * across the client-server API. + */ + public static int estimatedByteSizeOf(String string) { + return string.length() * 2 + 12; + } +} diff --git a/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl b/core/java/android/content/pm/verify/domain/IDomainVerificationManager.aidl index 21dd623b46bcb..b3b41009177e5 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.DomainSet; import android.content.pm.verify.domain.DomainVerificationInfo; import android.content.pm.verify.domain.DomainVerificationUserSelection; import java.util.List; @@ -35,10 +36,10 @@ interface IDomainVerificationManager { DomainVerificationUserSelection getDomainVerificationUserSelection(String packageName, int userId); - void setDomainVerificationStatus(String domainSetId, in List domains, int state); + void setDomainVerificationStatus(String domainSetId, in DomainSet domains, int state); void setDomainVerificationLinkHandlingAllowed(String packageName, boolean allowed, int userId); - void setDomainVerificationUserSelection(String domainSetId, in List domains, + void setDomainVerificationUserSelection(String domainSetId, in DomainSet domains, boolean enabled, int userId); } diff --git a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java index 080de73ff9338..e3cf67c34dad0 100644 --- a/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java +++ b/services/core/java/com/android/server/pm/verify/domain/DomainVerificationCollector.java @@ -42,6 +42,8 @@ public class DomainVerificationCollector { private static final Pattern DOMAIN_NAME_WITH_WILDCARD = Pattern.compile("(\\*\\.)?" + Patterns.DOMAIN_NAME.pattern()); + private static final int MAX_DOMAINS_BYTE_SIZE = 1024 * 1024; + @NonNull private final PlatformCompat mPlatformCompat; @@ -71,7 +73,7 @@ public class DomainVerificationCollector { *

  • - Only IntentFilter.SCHEME_HTTP and/or IntentFilter.SCHEME_HTTPS, * with no other schemes
  • * - * + *

    * On prior versions of Android, Intent.CATEGORY_BROWSABLE was not a requirement, other * schemes were allowed, and setting autoVerify to true in any intent filter would implicitly * pretend that all intent filters were set to autoVerify="true". @@ -86,8 +88,8 @@ public class DomainVerificationCollector { } /** - * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires - * {@link IntentFilter#getAutoVerify()} == true. + * Effectively {@link #collectAllWebDomains(AndroidPackage)}, but requires {@link + * IntentFilter#getAutoVerify()} == true. */ @NonNull public ArraySet collectAutoVerifyDomains(@NonNull AndroidPackage pkg) { @@ -100,24 +102,21 @@ public class DomainVerificationCollector { boolean restrictDomains = DomainVerificationUtils.isChangeEnabled(mPlatformCompat, pkg, RESTRICT_DOMAINS); - ArraySet domains = new ArraySet<>(); - if (restrictDomains) { - collectDomains(domains, pkg, checkAutoVerify); + return collectDomainsInternal(pkg, checkAutoVerify); } else { - collectDomainsLegacy(domains, pkg, checkAutoVerify); + return collectDomainsLegacy(pkg, checkAutoVerify); } - - return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomainsLegacy(@NonNull Set domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet collectDomainsLegacy(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { if (!checkAutoVerify) { // Per-domain user selection state doesn't have a V1 equivalent on S, so just use V2 - collectDomains(domains, pkg, false); - return; + return collectDomainsInternal(pkg, false); } List activities = pkg.getActivities(); @@ -140,39 +139,54 @@ public class DomainVerificationCollector { } if (!needsAutoVerify) { - return; + return new ArraySet<>(); } } - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + ArraySet domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (intent.handlesWebUris(false)) { int authorityCount = intent.countDataAuthorities(); for (int index = 0; index < authorityCount; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } } + + return domains; } - /** @see #RESTRICT_DOMAINS */ - private void collectDomains(@NonNull Set domains, - @NonNull AndroidPackage pkg, boolean checkAutoVerify) { + /** + * @see #RESTRICT_DOMAINS + */ + private ArraySet collectDomainsInternal(@NonNull AndroidPackage pkg, + boolean checkAutoVerify) { + ArraySet domains = new ArraySet<>(); + int totalSize = 0; + boolean underMaxSize = true; + List activities = pkg.getActivities(); int activitiesSize = activities.size(); - for (int activityIndex = 0; activityIndex < activitiesSize; activityIndex++) { + for (int activityIndex = 0; activityIndex < activitiesSize && underMaxSize; + activityIndex++) { ParsedActivity activity = activities.get(activityIndex); List intents = activity.getIntents(); int intentsSize = intents.size(); - for (int intentIndex = 0; intentIndex < intentsSize; intentIndex++) { + for (int intentIndex = 0; intentIndex < intentsSize && underMaxSize; intentIndex++) { ParsedIntentInfo intent = intents.get(intentIndex); if (checkAutoVerify && !intent.getAutoVerify()) { continue; @@ -198,14 +212,27 @@ public class DomainVerificationCollector { // app developer by declaring a separate intent-filter. This may not be worth // fixing. int authorityCount = intent.countDataAuthorities(); - for (int index = 0; index < authorityCount; index++) { + for (int index = 0; index < authorityCount && underMaxSize; index++) { String host = intent.getDataAuthority(index).getHost(); if (isValidHost(host)) { + totalSize += byteSizeOf(host); + underMaxSize = totalSize < MAX_DOMAINS_BYTE_SIZE; domains.add(host); } } } } + + return domains; + } + + /** + * Ballpark the size of domains to avoid a ridiculous amount of domains that could slow + * down client-server communication. + */ + private int byteSizeOf(String string) { + // Use the same method from core for the data objects so that restrictions are consistent + return android.content.pm.verify.domain.DomainVerificationUtils.estimatedByteSizeOf(string); } /** 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 8aa63372b826d..e84062f23f296 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,6 +20,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.UserIdInt; import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.verify.domain.DomainSet; import android.content.pm.verify.domain.DomainVerificationManager.InvalidDomainSetException; import android.content.pm.verify.domain.DomainVerificationManagerImpl; import android.content.pm.verify.domain.DomainVerificationInfo; @@ -61,11 +62,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationStatus(String domainSetId, List domains, + public void setDomainVerificationStatus(String domainSetId, @NonNull DomainSet domainSet, int state) { try { mService.setDomainVerificationStatus(UUID.fromString(domainSetId), - new ArraySet<>(domains), state); + domainSet.getDomains(), state); } catch (Exception e) { throw rethrow(e); } @@ -82,11 +83,11 @@ class DomainVerificationManagerStub extends IDomainVerificationManager.Stub { } @Override - public void setDomainVerificationUserSelection(String domainSetId, List domains, + public void setDomainVerificationUserSelection(String domainSetId, @NonNull DomainSet domainSet, boolean enabled, @UserIdInt int userId) { try { mService.setDomainVerificationUserSelection(UUID.fromString(domainSetId), - new ArraySet<>(domains), enabled, userId); + domainSet.getDomains(), enabled, userId); } catch (Exception e) { throw rethrow(e); } 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 deb3147644045..d18af07f96734 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 @@ -16,8 +16,9 @@ package com.android.server.pm.test.verify.domain -import android.content.pm.verify.domain.DomainVerificationRequest +import android.content.pm.verify.domain.DomainSet import android.content.pm.verify.domain.DomainVerificationInfo +import android.content.pm.verify.domain.DomainVerificationRequest import android.content.pm.verify.domain.DomainVerificationUserSelection import android.os.Parcel import android.os.Parcelable @@ -27,6 +28,7 @@ import org.junit.Test import org.junit.runner.RunWith import org.junit.runners.Parameterized import java.util.UUID +import kotlin.random.Random @RunWith(Parameterized::class) class DomainVerificationCoreApiTest { @@ -40,18 +42,25 @@ class DomainVerificationCoreApiTest { assertThat(value).containsExactlyEntriesIn(other) } + private val massiveSet by lazy { + val fragmentOf21 = ".com.example.test.app" + val list = mutableListOf("prefix$fragmentOf21") + var totalSize = 0 + // Slightly overshoot a size of 1MB + while (totalSize < (1024 * 512)) { + val nextValue = "${list.last()}$fragmentOf21" + totalSize += nextValue.length + list += nextValue + } + list.toSet() + } + @JvmStatic - @Parameterized.Parameters + @Parameterized.Parameters(name = "{0}") fun parameters() = arrayOf( Parameter( - initial = { - DomainVerificationRequest( - setOf( - "com.test.pkg.one", - "com.test.pkg.two" - ) - ) - }, + testName = "DomainVerificationRequest", + initial = { DomainVerificationRequest(massiveSet) }, unparcel = { DomainVerificationRequest.CREATOR.createFromParcel(it) }, assertion = { first, second -> assertAll>(first, second, @@ -61,15 +70,12 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationInfo", initial = { DomainVerificationInfo( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", - mapOf( - "example.com" to 0, - "example.org" to 1, - "example.new" to 1000 - ) + massiveSet.withIndex().associate { it.value to it.index } ) }, unparcel = { DomainVerificationInfo.CREATOR.createFromParcel(it) }, @@ -86,17 +92,15 @@ class DomainVerificationCoreApiTest { } ), Parameter( + testName = "DomainVerificationUserSelection", initial = { DomainVerificationUserSelection( UUID.fromString("703f6d34-6241-4cfd-8176-2e1d23355811"), "com.test.pkg", UserHandle.of(10), true, - mapOf( - "example.com" to true, - "example.org" to false, - "example.new" to true - ) + massiveSet.withIndex() + .associate { it.value to (it.index % 2 == 0) } ) }, unparcel = { DomainVerificationUserSelection.CREATOR.createFromParcel(it) }, @@ -119,16 +123,30 @@ class DomainVerificationCoreApiTest { { it.component5() }, IS_MAP_EQUAL_TO ) } + ), + Parameter( + testName = "DomainSet", + initial = { DomainSet(massiveSet) }, + unparcel = { DomainSet.CREATOR.createFromParcel(it) }, + assertion = { first, second -> + assertAll>( + first, second, + { it.domains }, assertion = IS_EQUAL_TO + ) + } ) ) class Parameter( + val testName: String, val initial: () -> T, val unparcel: (Parcel) -> T, private val assertion: (first: T, second: T) -> Unit ) { @Suppress("UNCHECKED_CAST") fun assert(first: Any, second: Any) = assertion(first as T, second as T) + + override fun toString() = testName } private fun assertAll(vararg values: T, block: (value: T, other: T) -> Unit) { @@ -141,11 +159,17 @@ class DomainVerificationCoreApiTest { first: T, second: T, fieldValue: (T) -> V, - componentValue: (T) -> V, + componentValue: ((T) -> V)? = null, assertion: (value: V, other: V) -> Unit ) { - val values = arrayOf(fieldValue(first), fieldValue(second), - componentValue(first), componentValue(second)) + val values = mutableListOf(fieldValue(first), fieldValue(second)) + .apply { + componentValue?.let { + add(it(first)) + add(it(second)) + } + } + .toTypedArray() values.indices.drop(1).forEach { @Suppress("UNCHECKED_CAST") assertion(values[0] as V, values[it] as V)