summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt10
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt241
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt81
-rw-r--r--PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java18
-rw-r--r--PermissionController/src/com/android/permissioncontroller/role/Role.md7
-rw-r--r--PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt15
-rw-r--r--tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt2
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt23
-rw-r--r--tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt8
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt30
10 files changed, 65 insertions, 370 deletions
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt
deleted file mode 100644
index 061bcb8a3..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/AppPermissionId.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.os.UserHandle
-
-/** Identifier for an app permission group combination. */
-data class AppPermissionId(
- val packageName: String,
- val userHandle: UserHandle,
- val permissionGroup: String,
-)
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
deleted file mode 100644
index 04cc8a796..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightHistoricalPackageOps.kt
+++ /dev/null
@@ -1,241 +0,0 @@
-/*
- * 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 com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.app.AppOpsManager.AttributedHistoricalOps
-import android.app.AppOpsManager.AttributedOpEntry
-import android.app.AppOpsManager.HistoricalOp
-import android.app.AppOpsManager.HistoricalPackageOps
-import android.app.AppOpsManager.OP_FLAG_SELF
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
-import android.app.AppOpsManager.OpEventProxyInfo
-import android.os.UserHandle
-import com.android.permissioncontroller.permission.utils.PermissionMapping.getPlatformPermissionGroupForOp
-
-/**
- * Light version of [HistoricalPackageOps] class, tracking the last permission access for system
- * permission groups.
- */
-data class LightHistoricalPackageOps(
- /** Name of the package. */
- val packageName: String,
- /** [UserHandle] running the package. */
- val userHandle: UserHandle,
- /**
- * Data about permission accesses, one [AppPermissionDiscreteAccesses] for each permission
- * group.
- */
- // TODO(b/262042582): Consider removing this field and using attributed accesses aggregated over
- // attribution tags instead.
- val appPermissionDiscreteAccesses: List<AppPermissionDiscreteAccesses>,
- /**
- * Attributed data about permission accesses, one [AttributedAppPermissionDiscreteAccesses] for
- * each permission group.
- */
- val attributedAppPermissionDiscreteAccesses: List<AttributedAppPermissionDiscreteAccesses>
-) {
- constructor(
- historicalPackageOps: HistoricalPackageOps,
- userHandle: UserHandle,
- opNames: Set<String>
- ) : this(
- historicalPackageOps.packageName,
- userHandle,
- historicalPackageOps.getAppPermissionDiscreteAccesses(userHandle, opNames),
- historicalPackageOps.getAttributedAppPermissionDiscreteAccesses(userHandle, opNames),
- )
-
- /** Companion object for [LightHistoricalPackageOps]. */
- companion object {
- /** String to represent the absence of an attribution tag. */
- const val NO_ATTRIBUTION_TAG = "no_attribution_tag"
- /** String to represent the absence of a permission group. */
- private const val NO_PERM_GROUP = "no_perm_group"
- private const val DISCRETE_ACCESS_OP_FLAGS =
- OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
-
- /**
- * Creates a list of [AppPermissionDiscreteAccesses] for the provided package, user and ops.
- */
- private fun HistoricalPackageOps.getAppPermissionDiscreteAccesses(
- userHandle: UserHandle,
- opNames: Set<String>
- ): List<AppPermissionDiscreteAccesses> {
- val permissionsToOpNames = partitionOpsByPermission(opNames)
- val appPermissionDiscreteAccesses = mutableListOf<AppPermissionDiscreteAccesses>()
- for (permissionToOpNames in permissionsToOpNames.entries) {
- this.getDiscreteAccesses(permissionToOpNames.value)?.let {
- appPermissionDiscreteAccesses.add(
- AppPermissionDiscreteAccesses(
- AppPermissionId(packageName, userHandle, permissionToOpNames.key),
- it
- )
- )
- }
- }
-
- return appPermissionDiscreteAccesses
- }
-
- /**
- * Creates a list of [AttributedAppPermissionDiscreteAccesses] for the provided package,
- * user and ops.
- */
- private fun HistoricalPackageOps.getAttributedAppPermissionDiscreteAccesses(
- userHandle: UserHandle,
- opNames: Set<String>
- ): List<AttributedAppPermissionDiscreteAccesses> {
- val permissionsToOpNames = partitionOpsByPermission(opNames)
- val attributedAppPermissionDiscreteAccesses =
- mutableMapOf<AppPermissionId, MutableMap<String, List<DiscreteAccess>>>()
-
- val attributedHistoricalOpsList = mutableListOf<AttributedHistoricalOps>()
- for (i in 0 until attributedOpsCount) {
- attributedHistoricalOpsList.add(getAttributedOpsAt(i))
- }
-
- for (permissionToOpNames in permissionsToOpNames.entries) {
- attributedHistoricalOpsList.forEach { attributedHistoricalOps ->
- attributedHistoricalOps.getDiscreteAccesses(permissionToOpNames.value)?.let {
- discAccessData ->
- val appPermissionId =
- AppPermissionId(packageName, userHandle, permissionToOpNames.key)
- if (!attributedAppPermissionDiscreteAccesses.containsKey(appPermissionId)) {
- attributedAppPermissionDiscreteAccesses[appPermissionId] =
- mutableMapOf()
- }
- attributedAppPermissionDiscreteAccesses[appPermissionId]?.put(
- attributedHistoricalOps.tag ?: NO_ATTRIBUTION_TAG,
- discAccessData
- )
- }
- }
- }
-
- return attributedAppPermissionDiscreteAccesses.map {
- AttributedAppPermissionDiscreteAccesses(it.key, it.value)
- }
- }
-
- /**
- * Retrieves all discrete accesses for the provided op names, if any.
- *
- * Returns null if there are no accesses.
- */
- private fun HistoricalPackageOps.getDiscreteAccesses(
- opNames: List<String>
- ): List<DiscreteAccess>? {
- if (opCount == 0) {
- return null
- }
-
- val historicalOps = mutableListOf<HistoricalOp>()
- for (opName in opNames) {
- getOp(opName)?.let { historicalOps.add(it) }
- }
-
- val discreteAccessList = mutableListOf<DiscreteAccess>()
- historicalOps.forEach {
- for (i in 0 until it.discreteAccessCount) {
- val opEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
- discreteAccessList.add(
- DiscreteAccess(
- it.opName,
- opEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
- opEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
- opEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)
- )
- )
- }
- }
-
- if (discreteAccessList.isEmpty()) {
- return null
- }
- return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
- }
-
- /**
- * Retrieves all discrete accesses for the provided op names, if any.
- *
- * Returns null if there are no accesses.
- */
- private fun AttributedHistoricalOps.getDiscreteAccesses(
- opNames: List<String>
- ): List<DiscreteAccess>? {
- if (opCount == 0) {
- return null
- }
-
- val historicalOps = mutableListOf<HistoricalOp>()
- for (opName in opNames) {
- getOp(opName)?.let { historicalOps.add(it) }
- }
-
- val discreteAccessList = mutableListOf<DiscreteAccess>()
- historicalOps.forEach {
- for (i in 0 until it.discreteAccessCount) {
- val attributedOpEntry: AttributedOpEntry = it.getDiscreteAccessAt(i)
- discreteAccessList.add(
- DiscreteAccess(
- it.opName,
- attributedOpEntry.getLastAccessTime(DISCRETE_ACCESS_OP_FLAGS),
- attributedOpEntry.getLastDuration(DISCRETE_ACCESS_OP_FLAGS),
- attributedOpEntry.getLastProxyInfo(DISCRETE_ACCESS_OP_FLAGS)
- )
- )
- }
- }
-
- if (discreteAccessList.isEmpty()) {
- return null
- }
- return discreteAccessList.sortedWith(compareBy { -it.accessTimeMs })
- }
-
- private fun partitionOpsByPermission(ops: Set<String>): Map<String, List<String>> =
- ops.groupBy { getPlatformPermissionGroupForOp(it) ?: NO_PERM_GROUP }
- .filter { it.key != NO_PERM_GROUP }
- }
-
- /**
- * Data class representing permissions accesses for a particular permission group by a
- * particular package and user.
- */
- data class AppPermissionDiscreteAccesses(
- val appPermissionId: AppPermissionId,
- val discreteAccesses: List<DiscreteAccess>
- )
-
- /**
- * Data class representing permissions accesses for a particular permission group by a
- * particular package and user, partitioned by attribution tag.
- */
- data class AttributedAppPermissionDiscreteAccesses(
- val appPermissionId: AppPermissionId,
- val attributedDiscreteAccesses: Map<String, List<DiscreteAccess>>
- )
-
- /** Data class representing a discrete permission access. */
- data class DiscreteAccess(
- val opName: String,
- val accessTimeMs: Long,
- val accessDurationMs: Long,
- val proxy: OpEventProxyInfo?
- )
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
deleted file mode 100644
index b65fda5ea..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/LightPackageOps.kt
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * 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 com.android.permissioncontroller.permission.model.livedatatypes.v31
-
-import android.app.AppOpsManager.OP_FLAG_SELF
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED
-import android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY
-import android.app.AppOpsManager.PackageOps
-import android.os.UserHandle
-import com.android.permissioncontroller.permission.utils.PermissionMapping.getPlatformPermissionGroupForOp
-
-/**
- * Light version of [PackageOps] class, tracking the last permission access for system permission
- * groups.
- */
-data class LightPackageOps(
- /** Name of the package. */
- val packageName: String,
- /** [UserHandle] running the package. */
- val userHandle: UserHandle,
- /**
- * Mapping of permission group name to the last access time of any op backing a permission in
- * the group.
- */
- val lastPermissionGroupAccessTimesMs: Map<String, Long>
-) {
- constructor(
- ops: Set<String>,
- packageOps: PackageOps
- ) : this(
- packageOps.packageName,
- UserHandle.getUserHandleForUid(packageOps.uid),
- createLastPermissionGroupAccessTimesMap(ops, packageOps)
- )
-
- /** Companion object for [LightPackageOps]. */
- companion object {
- /** Flags to use for querying an op's last access time. */
- private const val OPS_LAST_ACCESS_FLAGS =
- OP_FLAG_SELF or OP_FLAG_TRUSTED_PROXIED or OP_FLAG_TRUSTED_PROXY
-
- /** Creates a mapping from permission group to the last time it was accessed. */
- private fun createLastPermissionGroupAccessTimesMap(
- opNames: Set<String>,
- packageOps: PackageOps
- ): Map<String, Long> {
- val lastAccessTimeMs = mutableMapOf<String, Long>()
- // Add keys for all permissions groups covered by the provided ops, regardless of
- // whether they have been observed recently.
- for (permissionGroup in
- opNames.mapNotNull { getPlatformPermissionGroupForOp(it) }.toSet()) {
- lastAccessTimeMs[permissionGroup] = -1
- }
-
- for (opEntry in packageOps.ops) {
- val permissionGroupOfOp = getPlatformPermissionGroupForOp(opEntry.opStr) ?: continue
- lastAccessTimeMs[permissionGroupOfOp] =
- maxOf(
- lastAccessTimeMs[permissionGroupOfOp] ?: -1,
- opEntry.getLastAccessTime(OPS_LAST_ACCESS_FLAGS)
- )
- }
-
- return lastAccessTimeMs
- }
- }
-}
diff --git a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java b/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java
deleted file mode 100644
index b724ba43f..000000000
--- a/PermissionController/src/com/android/permissioncontroller/permission/model/livedatatypes/v31/package-info.java
+++ /dev/null
@@ -1,18 +0,0 @@
-/*
- * Copyright (C) 2023 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.
- */
-
-@androidx.annotation.RequiresApi(android.os.Build.VERSION_CODES.S)
-package com.android.permissioncontroller.permission.model.livedatatypes.v31;
diff --git a/PermissionController/src/com/android/permissioncontroller/role/Role.md b/PermissionController/src/com/android/permissioncontroller/role/Role.md
index 255214495..8d995d557 100644
--- a/PermissionController/src/com/android/permissioncontroller/role/Role.md
+++ b/PermissionController/src/com/android/permissioncontroller/role/Role.md
@@ -56,6 +56,13 @@ receive short text messages, photos, videos, and more". For default apps, this s
the default app detail page as a footer. This attribute is required if the role is `visible`.
- `exclusive`: Whether the role is exclusive. If a role is exclusive, at most one application is
allowed to be its holder.
+- `exclusivity`: Whether the role is exclusive and what type of exclusivity behavior it has. A role
+can have exclusivity of `none`, `user`, or `profileGroup`.
+ - `none`: Role allows multiple holders
+ - `user`: Role allows at most one holder within each user
+ - `profileGroup`: (SDK 36+ only, fallsback to `user` on lower SDK) Role allows at most one holder
+within a profile group (e.g. full user and work
+profile)
- `fallBackToDefaultHolder`: Whether the role should fall back to the default holder. This attribute
is optional and defaults to `false`.
- `featureFlag`: Optional feature flag for the role be available, as the fully qualified name of
diff --git a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
index 2a60e1325..35543b6f1 100644
--- a/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
+++ b/PermissionController/tests/mocking/src/com/android/permissioncontroller/tests/mocking/permission/utils/AdminRestrictedPermissionsUtilsTest.kt
@@ -19,12 +19,14 @@ package com.android.permissioncontroller.tests.mocking.permission.utils
import android.app.admin.DevicePolicyManager
import android.content.Context
import android.health.connect.HealthPermissions
+import android.os.Build
import android.permission.flags.Flags
import android.platform.test.annotations.AsbSecurityTest
import android.platform.test.annotations.RequiresFlagsEnabled
import android.platform.test.flag.junit.DeviceFlagsValueProvider
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
import com.android.modules.utils.build.SdkLevel
import com.android.permissioncontroller.permission.utils.v31.AdminRestrictedPermissionsUtils
import org.junit.Assert.assertEquals
@@ -74,12 +76,12 @@ object AdminRestrictedPermissionsUtilsTest {
companion object {
/**
- * Returns a list of arrays containing the following values:
- * 0. Permission name (String)
- * 1. Permission group name (String)
- * 2. Can admin grant sensors permissions (Boolean)
- * 3. Expected return from mayAdminGrantPermission method (Boolean)
- */
+ * Returns a list of arrays containing the following values:
+ * 0. Permission name (String)
+ * 1. Permission group name (String)
+ * 2. Can admin grant sensors permissions (Boolean)
+ * 3. Expected return from mayAdminGrantPermission method (Boolean)
+ */
@JvmStatic
@Parameterized.Parameters(name = "{index}: validate({0}, {1}, {3}) = {4}")
fun getParameters(): List<Array<out Any?>> {
@@ -109,6 +111,7 @@ object AdminRestrictedPermissionsUtilsTest {
class AdminRestrictedPermissionsUtilsSingleTest {
@Test
+ @SdkSuppress(minSdkVersion = Build.VERSION_CODES.BAKLAVA)
@RequiresFlagsEnabled(Flags.FLAG_REPLACE_BODY_SENSOR_PERMISSION_ENABLED)
fun addAdminRestrictedPermission_addsPermissionToRestrictedList() {
var canGrant =
diff --git a/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
index be4b82932..4781fb895 100644
--- a/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
+++ b/tests/cts/permissionui/src/android/permissionui/cts/LocationAccuracyTest.kt
@@ -72,6 +72,8 @@ class LocationAccuracyTest : BaseUsePermissionTest() {
}
@Test
+ @Ignore("b/396478581")
+ // Ignore this test until the cause of flakiness is identified.
fun testPrecisePermissionIsGranted() {
installPackage(APP_APK_PATH_31)
diff --git a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
index 64db7d47a..f5d230deb 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -75,6 +75,7 @@ import com.android.safetycenter.internaldata.SafetyCenterIds
import com.android.safetycenter.resources.SafetyCenterResourcesApk
import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG
import com.android.safetycenter.testing.Coroutines.TIMEOUT_SHORT
+import com.android.safetycenter.testing.Coroutines.assertWithTimeout
import com.android.safetycenter.testing.Coroutines.waitForWithTimeout
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.dismissSafetyCenterIssueWithPermission
import com.android.safetycenter.testing.SafetyCenterApisWithShellPermissions.getSafetyCenterConfigWithPermission
@@ -2356,18 +2357,14 @@ class SafetyCenterManagerTest {
groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
)
- waitForWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
- val hasResurfaced =
- safetyCenterManager
- .getSafetyCenterDataWithPermission()
- .issues
- .contains(
- safetyCenterTestData.safetyCenterIssueCritical(
- SOURCE_ID_5,
- groupId = MULTIPLE_SOURCES_GROUP_ID_2,
- )
+ assertWithTimeout(timeout = RESURFACE_TIMEOUT, checkPeriod = RESURFACE_CHECK) {
+ assertThat(safetyCenterManager.getSafetyCenterDataWithPermission().issues)
+ .contains(
+ safetyCenterTestData.safetyCenterIssueCritical(
+ SOURCE_ID_5,
+ groupId = MULTIPLE_SOURCES_GROUP_ID_2,
)
- hasResurfaced
+ )
}
}
@@ -3986,9 +3983,9 @@ class SafetyCenterManagerTest {
companion object {
private val RESURFACE_DELAY = Duration.ofMillis(500)
- // Wait 3 times the RESURFACE_DELAY before asserting whether an issue has or has not
+ // Wait 5 times the RESURFACE_DELAY before asserting whether an issue has or has not
// resurfaced. Use a constant additive error buffer if we increase the delay considerably.
- private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(3)
+ private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(5)
// Check more than once during a RESURFACE_DELAY before asserting whether an issue has or
// has not resurfaced. Use a different check logic (focused at the expected resurface time)
diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
index 60e6e41ec..c56c913b6 100644
--- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
+++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetyCenterNotificationLoggingHelperTests.kt
@@ -69,6 +69,12 @@ class SafetyCenterNotificationLoggingHelperTests {
@Test
fun sendNotification() {
safetyCenterTestHelper.setData(SINGLE_SOURCE_ID, newTestDataWithNotifiableIssue())
+ TestNotificationListener.waitForSingleNotificationMatching(
+ NotificationCharacteristics(
+ actions = listOf("See issue"),
+ safetySourceId = SINGLE_SOURCE_ID,
+ )
+ )
}
@Test
@@ -104,7 +110,7 @@ class SafetyCenterNotificationLoggingHelperTests {
statusBarNotificationWithChannel.statusBarNotification.notification.contentIntent
SafetyCenterActivityLauncher.executeBlockAndExit(
launchActivity = { PendingIntentSender.send(contentIntent) },
- block = {} // No action required
+ block = {}, // No action required
)
}
}
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
index cc8c53d5e..47f5165e2 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt
@@ -22,6 +22,7 @@ import java.time.Duration
import kotlinx.coroutines.DEBUG_PROPERTY_NAME
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_AUTO
import kotlinx.coroutines.DEBUG_PROPERTY_VALUE_ON
+import kotlinx.coroutines.TimeoutCancellationException
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withTimeout
@@ -68,6 +69,20 @@ object Coroutines {
runBlockingWithTimeout(timeout) { waitFor(checkPeriod, condition) }
}
+ /** Check an assertion passes, with a timeout if it does not. */
+ fun assertWithTimeout(
+ timeout: Duration = TIMEOUT_LONG,
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ try {
+ runBlockingWithTimeout(timeout) { assertThatWaiting(checkPeriod, assertion) }
+ } catch (ex: TimeoutCancellationException) {
+ // Rerun the assertion to generate a meaningful error message that isn't just "timeout"
+ assertion()
+ }
+ }
+
/** Retries a [fallibleAction] until no errors are thrown or a timeout occurs. */
fun waitForSuccessWithTimeout(
timeout: Duration = TIMEOUT_LONG,
@@ -105,6 +120,21 @@ object Coroutines {
}
}
+ /** Check an assertion passes using coroutines. */
+ private suspend fun assertThatWaiting(
+ checkPeriod: Duration = CHECK_PERIOD,
+ assertion: () -> Unit,
+ ) {
+ while (true) {
+ try {
+ assertion()
+ break
+ } catch (ex: AssertionError) {
+ delay(checkPeriod.toMillis())
+ }
+ }
+ }
+
private const val TAG: String = "Coroutines"
/** A medium period, to be used for conditions that are expected to change. */