summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Simon Wingrove <simonjw@google.com> 2022-11-14 16:04:02 +0000
committer Simon Wingrove <simonjw@google.com> 2022-11-30 08:06:53 +0000
commit46aa98e75473df7c2c4212b2ab85a8cdc5125224 (patch)
treeaad7f3de041af39c490e7b072f98348ddb41ba3b
parenta84dc9c4d38cc0bb0a8e10722c00f3a372dacc58 (diff)
Add SafetySource.getPackageCertificateHashes
Cert hashes will be used in a signature check on other APIs where currently a package name only check is used. Test: atest SafetySourceTest SafetyCenterConfigTests Bug: 252726218 Change-Id: Iac01b010e8ef39e9217738642d90bc5cf1d141ed
-rw-r--r--SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java17
-rw-r--r--SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt6
-rw-r--r--SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt6
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml32
-rw-r--r--SafetyCenter/Config/tests/res/raw-v34/config_valid.xml9
-rw-r--r--SafetyCenter/Config/tests/res/values/strings.xml1
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java4
-rw-r--r--SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java28
-rw-r--r--framework-s/api/system-current.txt2
-rw-r--r--framework-s/java/android/safetycenter/config/BuilderUtils.java40
-rw-r--r--framework-s/java/android/safetycenter/config/SafetySource.java66
-rw-r--r--framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd4
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt90
-rw-r--r--tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt12
14 files changed, 307 insertions, 10 deletions
diff --git a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
index e71bf9e74..f7faf9e27 100644
--- a/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
+++ b/SafetyCenter/Config/java/com/android/safetycenter/config/SafetyCenterConfigParser.java
@@ -76,6 +76,7 @@ public final class SafetyCenterConfigParser {
"refreshOnPageOpenAllowed";
private static final String ATTR_SAFETY_SOURCE_NOTIFICATIONS_ALLOWED = "notificationsAllowed";
private static final String ATTR_SAFETY_SOURCE_DEDUPLICATION_GROUP = "deduplicationGroup";
+ private static final String ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES = "packageCertificateHashes";
private static final String ENUM_STATELESS_ICON_TYPE_NONE = "none";
private static final String ENUM_STATELESS_ICON_TYPE_PRIVACY = "privacy";
private static final String ENUM_GROUP_TYPE_STATEFUL = "stateful";
@@ -365,6 +366,22 @@ public final class SafetyCenterConfigParser {
throw attributeUnexpected(name, parser.getAttributeName(i));
}
break;
+ case ATTR_SAFETY_SOURCE_PACKAGE_CERT_HASHES:
+ if (SdkLevel.isAtLeastU()) {
+ String commaSeparatedHashes =
+ parseStringResourceValue(
+ parser.getAttributeValue(i),
+ name,
+ parser.getAttributeName(i),
+ resources);
+ String[] splits = commaSeparatedHashes.split(",");
+ for (int j = 0; j < splits.length; j++) {
+ builder.addPackageCertificateHash(splits[j]);
+ }
+ } else {
+ throw attributeUnexpected(name, parser.getAttributeName(i));
+ }
+ break;
default:
throw attributeUnexpected(name, parser.getAttributeName(i));
}
diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
index c919f20f0..fc169fd5e 100644
--- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
+++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigInvalidTest.kt
@@ -340,6 +340,12 @@ class ParserConfigInvalidTest {
"Prohibited attribute packageName present",
!SdkLevel.isAtLeastU()),
Params(
+ "ConfigStaticSafetySourceWithPackageCertficates",
+ R.raw.config_static_safety_source_with_package_certs,
+ "Element static-safety-source invalid",
+ "Prohibited attribute packageCertificateHashes present",
+ SdkLevel.isAtLeastU()),
+ Params(
"ConfigStaticSafetySourceWithPrimaryAndWork",
R.raw.config_static_safety_source_with_primary_and_work,
"Element static-safety-source invalid",
diff --git a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
index 28a8bd20a..573725582 100644
--- a/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
+++ b/SafetyCenter/Config/tests/java/com/android/safetycenter/config/ParserConfigValidTest.kt
@@ -73,6 +73,8 @@ class ParserConfigValidTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
}
}
.build())
@@ -94,6 +96,8 @@ class ParserConfigValidTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
}
}
.build())
@@ -170,6 +174,8 @@ class ParserConfigValidTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash("feed1")
+ addPackageCertificateHash("feed2")
}
}
.build())
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml
new file mode 100644
index 000000000..2a99d5617
--- /dev/null
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_static_safety_source_with_package_certs.xml
@@ -0,0 +1,32 @@
+<!--
+ ~ Copyright (C) 2022 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.
+ -->
+
+<safety-center-config>
+ <safety-sources-config>
+ <safety-sources-group
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference">
+ <static-safety-source
+ id="id"
+ title="@com.android.safetycenter.config.tests:string/reference"
+ summary="@com.android.safetycenter.config.tests:string/reference"
+ intentAction="intent"
+ profile="primary_profile_only"
+ packageCertificateHashes="feed1,feed2"/>
+ </safety-sources-group>
+ </safety-sources-config>
+</safety-center-config>
diff --git a/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml b/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml
index 3b5813bca..f9e7fbf27 100644
--- a/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml
+++ b/SafetyCenter/Config/tests/res/raw-v34/config_valid.xml
@@ -26,7 +26,8 @@
loggingAllowed="false"
refreshOnPageOpenAllowed="true"
notificationsAllowed="true"
- deduplicationGroup="group"/>
+ deduplicationGroup="group"
+ packageCertificateHashes="feed1,feed2"/>
<dynamic-safety-source
id="@com.android.safetycenter.config.tests:string/dynamic_all_references_id"
packageName="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_name"
@@ -41,7 +42,8 @@
loggingAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_logging_allowed"
refreshOnPageOpenAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_refresh_on_page_open_allowed"
notificationsAllowed="@com.android.safetycenter.config.tests:string/dynamic_all_references_notifications_allowed"
- deduplicationGroup="@com.android.safetycenter.config.tests:string/dynamic_all_references_deduplication_group"/>
+ deduplicationGroup="@com.android.safetycenter.config.tests:string/dynamic_all_references_deduplication_group"
+ packageCertificateHashes="@com.android.safetycenter.config.tests:string/dynamic_all_references_package_cert_hashes"/>
<dynamic-safety-source
id="dynamic_disabled"
packageName="package"
@@ -97,7 +99,8 @@
loggingAllowed="false"
refreshOnPageOpenAllowed="true"
notificationsAllowed="true"
- deduplicationGroup="group"/>
+ deduplicationGroup="group"
+ packageCertificateHashes="feed1,feed2"/>
</safety-sources-group>
<safety-sources-group
id="mixed"
diff --git a/SafetyCenter/Config/tests/res/values/strings.xml b/SafetyCenter/Config/tests/res/values/strings.xml
index d7b8b13c5..a625c5225 100644
--- a/SafetyCenter/Config/tests/res/values/strings.xml
+++ b/SafetyCenter/Config/tests/res/values/strings.xml
@@ -28,4 +28,5 @@
<string name="dynamic_all_references_refresh_on_page_open_allowed" translatable="false">true</string>
<string name="dynamic_all_references_notifications_allowed" translatable="false">true</string>
<string name="dynamic_all_references_deduplication_group" translatable="false">group</string>
+ <string name="dynamic_all_references_package_cert_hashes" translatable="false">feed1,feed2</string>
</resources>
diff --git a/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java b/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
index e1300de32..81bedc860 100644
--- a/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
+++ b/SafetyCenter/ConfigLintChecker/java/android/os/Parcel.java
@@ -29,6 +29,8 @@ public interface Parcel {
int readInt();
/** Method used in the Safety Center config data structures. */
String readString();
+ /** Method used in the Safety Center config data structures. */
+ ArrayList<String> createStringArrayList();
/** Method used in the Safety Center config data structures. */
void writeBoolean(boolean value);
@@ -37,5 +39,7 @@ public interface Parcel {
/** Method used in the Safety Center config data structures. */
void writeString(String value);
/** Method used in the Safety Center config data structures. */
+ void writeStringList(List<String> value);
+ /** Method used in the Safety Center config data structures. */
<T extends Parcelable> void writeTypedList(List<T> value);
}
diff --git a/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java
new file mode 100644
index 000000000..cfc864c65
--- /dev/null
+++ b/SafetyCenter/ConfigLintChecker/java/android/util/ArraySet.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.util;
+
+import java.util.HashSet;
+
+/**
+ * A simple ArraySet implementation for the lint checker.
+ *
+ * <p>It's not array based, but for this simple purpose that doesn't matter.
+ *
+ * @param <E> the type of elements maintained by this set
+ */
+public final class ArraySet<E> extends HashSet<E> {}
diff --git a/framework-s/api/system-current.txt b/framework-s/api/system-current.txt
index ae7008e64..8aca658d3 100644
--- a/framework-s/api/system-current.txt
+++ b/framework-s/api/system-current.txt
@@ -494,6 +494,7 @@ package android.safetycenter.config {
method @Nullable public String getIntentAction();
method public int getMaxSeverityLevel();
method @Nullable public String getOptionalPackageName();
+ method @NonNull public java.util.Set<java.lang.String> getPackageCertificateHashes();
method @NonNull public String getPackageName();
method public int getProfile();
method @StringRes public int getSearchTermsResId();
@@ -518,6 +519,7 @@ package android.safetycenter.config {
public static final class SafetySource.Builder {
ctor public SafetySource.Builder(int);
+ method @NonNull public android.safetycenter.config.SafetySource.Builder addPackageCertificateHash(@NonNull String);
method @NonNull public android.safetycenter.config.SafetySource build();
method @NonNull public android.safetycenter.config.SafetySource.Builder setDeduplicationGroup(@Nullable String);
method @NonNull public android.safetycenter.config.SafetySource.Builder setId(@Nullable String);
diff --git a/framework-s/java/android/safetycenter/config/BuilderUtils.java b/framework-s/java/android/safetycenter/config/BuilderUtils.java
index e0e08183d..35ec39cc5 100644
--- a/framework-s/java/android/safetycenter/config/BuilderUtils.java
+++ b/framework-s/java/android/safetycenter/config/BuilderUtils.java
@@ -25,6 +25,7 @@ import android.content.res.Resources;
import androidx.annotation.RequiresApi;
+import java.util.Collection;
import java.util.Objects;
@RequiresApi(TIRAMISU)
@@ -39,12 +40,12 @@ final class BuilderUtils {
boolean prohibited,
@Nullable Object defaultValue) {
if (attribute == null && required) {
- throw new IllegalStateException("Required attribute " + name + " missing");
+ throwRequiredAttributeMissing(name);
}
boolean nonDefaultValueProvided = !Objects.equals(attribute, defaultValue);
boolean checkProhibited = prohibited && nonDefaultValueProvided;
if (attribute != null && checkProhibited) {
- throw new IllegalStateException("Prohibited attribute " + name + " present");
+ throwProhibitedAttributePresent(name);
}
}
@@ -67,7 +68,7 @@ final class BuilderUtils {
return Resources.ID_NULL;
}
if (required && value == Resources.ID_NULL) {
- throw new IllegalStateException("Required attribute " + name + " invalid");
+ throwRequiredAttributeInvalid(name);
}
return value;
}
@@ -119,4 +120,37 @@ final class BuilderUtils {
}
return value;
}
+
+ /**
+ * Validates a collection argument from a builder.
+ *
+ * <ul>
+ * <li>If {@code required}, a non-empty collection must be supplied.
+ * <li>If {@code prohibited}, an empty collection must be supplied.
+ * </ul>
+ */
+ static <T> void validateCollection(
+ @NonNull Collection<T> value,
+ @NonNull String name,
+ boolean required,
+ boolean prohibited) {
+ if (value.isEmpty() && required) {
+ throwRequiredAttributeMissing(name);
+ }
+ if (!value.isEmpty() && prohibited) {
+ throwProhibitedAttributePresent(name);
+ }
+ }
+
+ static void throwRequiredAttributeMissing(@NonNull String attribute) {
+ throw new IllegalStateException("Required attribute " + attribute + " missing");
+ }
+
+ static void throwProhibitedAttributePresent(@NonNull String attribute) {
+ throw new IllegalStateException("Prohibited attribute " + attribute + " present");
+ }
+
+ static void throwRequiredAttributeInvalid(@NonNull String attribute) {
+ throw new IllegalStateException("Required attribute " + attribute + " invalid");
+ }
}
diff --git a/framework-s/java/android/safetycenter/config/SafetySource.java b/framework-s/java/android/safetycenter/config/SafetySource.java
index e3db49300..6c4c5b766 100644
--- a/framework-s/java/android/safetycenter/config/SafetySource.java
+++ b/framework-s/java/android/safetycenter/config/SafetySource.java
@@ -27,6 +27,7 @@ import android.annotation.SystemApi;
import android.content.res.Resources;
import android.os.Parcel;
import android.os.Parcelable;
+import android.util.ArraySet;
import androidx.annotation.RequiresApi;
@@ -34,7 +35,9 @@ import com.android.modules.utils.build.SdkLevel;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.util.List;
import java.util.Objects;
+import java.util.Set;
/**
* Data class used to represent the initial configuration of a safety source.
@@ -172,6 +175,10 @@ public final class SafetySource implements Parcelable {
if (SdkLevel.isAtLeastU()) {
builder.setNotificationsAllowed(in.readBoolean());
builder.setDeduplicationGroup(in.readString());
+ List<String> certs = in.createStringArrayList();
+ for (int i = 0; i < certs.size(); i++) {
+ builder.addPackageCertificateHash(certs.get(i));
+ }
}
return builder.build();
}
@@ -197,6 +204,7 @@ public final class SafetySource implements Parcelable {
private final boolean mRefreshOnPageOpenAllowed;
private final boolean mNotificationsAllowed;
@Nullable final String mDeduplicationGroup;
+ @NonNull private final Set<String> mPackageCertificateHashes;
private SafetySource(
@SafetySourceType int type,
@@ -213,7 +221,8 @@ public final class SafetySource implements Parcelable {
boolean loggingAllowed,
boolean refreshOnPageOpenAllowed,
boolean notificationsAllowed,
- @Nullable String deduplicationGroup) {
+ @Nullable String deduplicationGroup,
+ @NonNull Set<String> packageCertificateHashes) {
mType = type;
mId = id;
mPackageName = packageName;
@@ -229,6 +238,7 @@ public final class SafetySource implements Parcelable {
mRefreshOnPageOpenAllowed = refreshOnPageOpenAllowed;
mNotificationsAllowed = notificationsAllowed;
mDeduplicationGroup = deduplicationGroup;
+ mPackageCertificateHashes = Set.copyOf(packageCertificateHashes);
}
/** Returns the type of this safety source. */
@@ -496,6 +506,28 @@ public final class SafetySource implements Parcelable {
return mDeduplicationGroup;
}
+ /**
+ * Returns a set of package certificate hashes representing valid signed packages that represent
+ * this {@link SafetySource}.
+ *
+ * <p>If one or more certificate hashes are set, Safety Center will validate that a package
+ * calling {@link android.safetycenter.SafetyCenterManager#setSafetySourceData} is signed with
+ * one of the certificates provided.
+ *
+ * <p>The default value is an empty {@code Set}, in which case only the package name is
+ * validated.
+ *
+ * @see Builder#addPackageCertificateHash(String)
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Set<String> getPackageCertificateHashes() {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ return mPackageCertificateHashes;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -515,7 +547,8 @@ public final class SafetySource implements Parcelable {
&& mLoggingAllowed == that.mLoggingAllowed
&& mRefreshOnPageOpenAllowed == that.mRefreshOnPageOpenAllowed
&& mNotificationsAllowed == that.mNotificationsAllowed
- && Objects.equals(mDeduplicationGroup, that.mDeduplicationGroup);
+ && Objects.equals(mDeduplicationGroup, that.mDeduplicationGroup)
+ && Objects.equals(mPackageCertificateHashes, that.mPackageCertificateHashes);
}
@Override
@@ -535,7 +568,8 @@ public final class SafetySource implements Parcelable {
mLoggingAllowed,
mRefreshOnPageOpenAllowed,
mNotificationsAllowed,
- mDeduplicationGroup);
+ mDeduplicationGroup,
+ mPackageCertificateHashes);
}
@Override
@@ -571,6 +605,8 @@ public final class SafetySource implements Parcelable {
+ mNotificationsAllowed
+ ", mDeduplicationGroup="
+ mDeduplicationGroup
+ + ", mPackageCertificateHashes="
+ + mPackageCertificateHashes
+ '}';
}
@@ -597,6 +633,7 @@ public final class SafetySource implements Parcelable {
if (SdkLevel.isAtLeastU()) {
dest.writeBoolean(mNotificationsAllowed);
dest.writeString(mDeduplicationGroup);
+ dest.writeStringList(List.copyOf(mPackageCertificateHashes));
}
}
@@ -618,6 +655,7 @@ public final class SafetySource implements Parcelable {
@Nullable private Boolean mRefreshOnPageOpenAllowed;
@Nullable private Boolean mNotificationsAllowed;
@Nullable private String mDeduplicationGroup;
+ @NonNull private final ArraySet<String> mPackageCertificateHashes = new ArraySet<>();
/** Creates a {@link Builder} for a {@link SafetySource}. */
public Builder(@SafetySourceType int type) {
@@ -845,6 +883,22 @@ public final class SafetySource implements Parcelable {
}
/**
+ * Adds a package certificate hash to the {@link #getPackageCertificateHashes()} property of
+ * this {@link SafetySource}.
+ *
+ * @see #getPackageCertificateHashes()
+ */
+ @NonNull
+ @RequiresApi(UPSIDE_DOWN_CAKE)
+ public Builder addPackageCertificateHash(@NonNull String packageCertificateHash) {
+ if (!SdkLevel.isAtLeastU()) {
+ throw new UnsupportedOperationException();
+ }
+ mPackageCertificateHashes.add(packageCertificateHash);
+ return this;
+ }
+
+ /**
* Creates the {@link SafetySource} defined by this {@link Builder}.
*
* <p>Throws an {@link IllegalStateException} if any constraint on the safety source is
@@ -947,6 +1001,7 @@ public final class SafetySource implements Parcelable {
String deduplicationGroup = mDeduplicationGroup;
boolean notificationsAllowed = false;
+ Set<String> packageCertificateHashes = Set.copyOf(mPackageCertificateHashes);
if (SdkLevel.isAtLeastU()) {
notificationsAllowed =
BuilderUtils.validateBoolean(
@@ -958,6 +1013,8 @@ public final class SafetySource implements Parcelable {
BuilderUtils.validateAttribute(
deduplicationGroup, "deduplicationGroup", false, isStatic);
+ BuilderUtils.validateCollection(
+ packageCertificateHashes, "packageCertificateHashes", false, isStatic);
}
return new SafetySource(
@@ -975,7 +1032,8 @@ public final class SafetySource implements Parcelable {
loggingAllowed,
refreshOnPageOpenAllowed,
notificationsAllowed,
- deduplicationGroup);
+ deduplicationGroup,
+ packageCertificateHashes);
}
}
}
diff --git a/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd b/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd
index fbf62393b..3a70fa065 100644
--- a/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd
+++ b/framework-s/java/android/safetycenter/config/safety_center_config-v34.xsd
@@ -55,6 +55,8 @@
<!-- id must be unique among safety sources -->
<xsd:attribute name="id" type="stringOrStringResourceName" use="required"/>
<xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/>
+ <!-- optional comma-separated set of certficate hashes, if provided will be used for validation. -->
+ <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/>
<!-- title is required if initialDisplayState is not set to hidden or if searchTerms are provided -->
<xsd:attribute name="title" type="runtimeStringResourceName"/>
<!-- titleForWork is required if profile is set to all_profiles, and initialDisplayState is not set to hidden or if searchTerms are provided -->
@@ -81,6 +83,8 @@
<!-- id must be unique among safety sources -->
<xsd:attribute name="id" type="stringOrStringResourceName" use="required"/>
<xsd:attribute name="packageName" type="stringOrStringResourceName" use="required"/>
+ <!-- optional comma-separated set of certficate hashes, if provided will be used for validation. -->
+ <xsd:attribute name="packageCertificateHashes" type="stringOrStringResourceName"/>
<xsd:attribute name="profile" type="profileOrStringResourceName" use="required"/>
<xsd:attribute name="maxSeverityLevel" type="intOrStringResourceName" default="2147483647"/>
<xsd:attribute name="loggingAllowed" type="booleanOrStringResourceName" default="true"/>
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
index d116a8468..66b976b31 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/config/SafetySourceTest.kt
@@ -287,6 +287,20 @@ class SafetySourceTest {
assertThat(issueOnlyAllOptional().deduplicationGroup).isEqualTo(DEDUPLICATION_GROUP)
}
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE, codeName = "UpsideDownCake")
+ @Test
+ fun getPackageCertificateHashes_returnsPackageCerts() {
+ assertThat(DYNAMIC_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(dynamicAllOptional().packageCertificateHashes).containsExactly(HASH1)
+ assertThat(DYNAMIC_DISABLED.packageCertificateHashes).isEmpty()
+ assertThat(DYNAMIC_HIDDEN.packageCertificateHashes).isEmpty()
+ assertThat(DYNAMIC_HIDDEN_WITH_SEARCH.packageCertificateHashes).isEmpty()
+ assertThat(STATIC_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(STATIC_ALL_OPTIONAL.packageCertificateHashes).isEmpty()
+ assertThat(ISSUE_ONLY_BAREBONE.packageCertificateHashes).isEmpty()
+ assertThat(issueOnlyAllOptional().packageCertificateHashes).containsExactly(HASH1, HASH2)
+ }
+
@Test
fun describeContents_returns0() {
assertThat(DYNAMIC_BAREBONE.describeContents()).isEqualTo(0)
@@ -336,6 +350,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -364,6 +379,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -385,6 +401,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -406,6 +423,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -427,6 +445,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -448,6 +467,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -469,6 +489,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -497,6 +518,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -518,6 +540,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -539,6 +562,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -560,6 +584,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -581,6 +606,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build())
@@ -602,6 +628,7 @@ class SafetySourceTest {
.setRefreshOnPageOpenAllowed(true)
.setNotificationsAllowed(false)
.setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH1)
.build())
addEqualityGroup(
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -619,6 +646,64 @@ class SafetySourceTest {
.setRefreshOnPageOpenAllowed(true)
.setNotificationsAllowed(true)
.setDeduplicationGroup("other_deduplication_group")
+ .addPackageCertificateHash(HASH1)
+ .build())
+ // With no package cert hashes provided
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .build())
+ // With longer package cert hash list
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH1)
+ .addPackageCertificateHash(HASH2)
+ .build())
+ // With package cert hash list with different value
+ addEqualityGroup(
+ SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
+ .setId(DYNAMIC_ALL_OPTIONAL_ID)
+ .setPackageName(PACKAGE_NAME)
+ .setTitleResId(REFERENCE_RES_ID)
+ .setTitleForWorkResId(REFERENCE_RES_ID)
+ .setSummaryResId(REFERENCE_RES_ID)
+ .setIntentAction(INTENT_ACTION)
+ .setProfile(SafetySource.PROFILE_ALL)
+ .setInitialDisplayState(SafetySource.INITIAL_DISPLAY_STATE_DISABLED)
+ .setMaxSeverityLevel(MAX_SEVERITY_LEVEL)
+ .setSearchTermsResId(REFERENCE_RES_ID)
+ .setLoggingAllowed(false)
+ .setRefreshOnPageOpenAllowed(true)
+ .setNotificationsAllowed(true)
+ .setDeduplicationGroup(DEDUPLICATION_GROUP)
+ .addPackageCertificateHash(HASH2)
.build())
}
}
@@ -641,6 +726,8 @@ class SafetySourceTest {
private const val ISSUE_ONLY_BAREBONE_ID = "issue_only_barebone"
private const val ISSUE_ONLY_ALL_OPTIONAL_ID = "issue_only_all_optional"
private const val DEDUPLICATION_GROUP = "deduplication_group"
+ private const val HASH1 = "feed1"
+ private const val HASH2 = "feed2"
internal val DYNAMIC_BAREBONE =
SafetySource.Builder(SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC)
@@ -670,6 +757,7 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
}
}
.build()
@@ -744,6 +832,8 @@ class SafetySourceTest {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup(DEDUPLICATION_GROUP)
+ addPackageCertificateHash(HASH1)
+ addPackageCertificateHash(HASH2)
}
}
.build()
diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
index ea6b07851..7f02a3717 100644
--- a/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
+++ b/tests/cts/safetycenter/src/android/safetycenter/cts/testing/SafetyCenterCtsConfigs.kt
@@ -264,6 +264,12 @@ object SafetyCenterCtsConfigs {
/** Package name for the [DYNAMIC_OTHER_PACKAGE_ID] source. */
const val OTHER_PACKAGE_NAME = "other_package_name"
+ /** Hash of a package certificate for all "ALL_OPTIONAL" sources. */
+ private const val PACKAGE_CERT_HASH_1 = "feed1"
+
+ /** Second hash of a package certificate for all "ALL_OPTIONAL" sources. */
+ private const val PACKAGE_CERT_HASH_2 = "feed2"
+
/** A Simple [SafetyCenterConfig] with an issue only source. */
val ISSUE_ONLY_SOURCE_CONFIG =
singleSourceConfig(issueOnlySafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID).build())
@@ -528,6 +534,8 @@ object SafetyCenterCtsConfigs {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_1)
+ addPackageCertificateHash(PACKAGE_CERT_HASH_2)
}
}
.build())
@@ -595,6 +603,8 @@ object SafetyCenterCtsConfigs {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_1)
+ addPackageCertificateHash(PACKAGE_CERT_HASH_2)
}
}
.build())
@@ -667,6 +677,8 @@ object SafetyCenterCtsConfigs {
if (SdkLevel.isAtLeastU()) {
setNotificationsAllowed(true)
setDeduplicationGroup("group")
+ addPackageCertificateHash(PACKAGE_CERT_HASH_1)
+ addPackageCertificateHash(PACKAGE_CERT_HASH_2)
}
}
.build())