diff options
10 files changed, 96 insertions, 92 deletions
diff --git a/SafetyCenter/Resources/shared_res/values/strings.xml b/SafetyCenter/Resources/shared_res/values/strings.xml index f4f1b0201..2b231b55f 100644 --- a/SafetyCenter/Resources/shared_res/values/strings.xml +++ b/SafetyCenter/Resources/shared_res/values/strings.xml @@ -94,9 +94,6 @@ <!-- An error shown to the user when we failed to resolve an alert that they attempted to resolve directly from the Safety Center screen [CHAR LIMIT=50] --> <string name="resolving_action_error">Couldn\’t resolve alert</string> - <!-- An error shown to the user when we failed to refresh the overall Safety Center status. This happens when at least one safety signal did not get back to Safety Center within an arbitrary timeout [CHAR LIMIT=50] --> - <string name="refresh_timeout">Couldn\’t refresh settings</string> - <!-- An error shown to the user when we failed to refresh the status of one or more Safety Center entries. The number of entries is used as a parameter to format the error [CHAR LIMIT=60] --> <string name="refresh_error">{count, plural, =1 {Couldn\’t check setting} diff --git a/service/java/com/android/safetycenter/SafetyCenterFlags.java b/service/java/com/android/safetycenter/SafetyCenterFlags.java index 788575eff..fcb016160 100644 --- a/service/java/com/android/safetycenter/SafetyCenterFlags.java +++ b/service/java/com/android/safetycenter/SafetyCenterFlags.java @@ -60,9 +60,6 @@ public final class SafetyCenterFlags { private static final String PROPERTY_NOTIFICATION_RESURFACE_INTERVAL = "safety_center_notification_resurface_interval"; - private static final String PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT = - "safety_center_show_error_entries_on_timeout"; - private static final String PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION = "safety_center_replace_lock_screen_icon_action"; @@ -173,7 +170,6 @@ public final class SafetyCenterFlags { getImmediateNotificationBehaviorIssues()); printFlag( fout, PROPERTY_NOTIFICATION_RESURFACE_INTERVAL, getNotificationResurfaceInterval()); - printFlag(fout, PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, getShowErrorEntriesOnTimeout()); printFlag(fout, PROPERTY_REPLACE_LOCK_SCREEN_ICON_ACTION, getReplaceLockScreenIconAction()); printFlag(fout, PROPERTY_RESOLVING_ACTION_TIMEOUT_MILLIS, getResolvingActionTimeout()); printFlag(fout, PROPERTY_FGS_ALLOWLIST_DURATION_MILLIS, getFgsAllowlistDuration()); @@ -284,13 +280,6 @@ public final class SafetyCenterFlags { } /** - * Returns whether we should show error entries for sources that timeout when refreshing them. - */ - static boolean getShowErrorEntriesOnTimeout() { - return getBoolean(PROPERTY_SHOW_ERROR_ENTRIES_ON_TIMEOUT, true); - } - - /** * Returns whether we should replace the lock screen source's {@link * android.safetycenter.SafetySourceStatus.IconAction}. */ diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java index cac8bfc61..578997355 100644 --- a/service/java/com/android/safetycenter/SafetyCenterService.java +++ b/service/java/com/android/safetycenter/SafetyCenterService.java @@ -944,22 +944,12 @@ public final class SafetyCenterService extends SystemService { if (stillInFlight == null) { return; } - boolean showErrorEntriesOnTimeout = - SafetyCenterFlags.getShowErrorEntriesOnTimeout(); - boolean setError = - showErrorEntriesOnTimeout - && !RefreshReasons.isBackgroundRefresh(mRefreshReason); + boolean setError = !RefreshReasons.isBackgroundRefresh(mRefreshReason); for (int i = 0; i < stillInFlight.size(); i++) { mSafetyCenterDataManager.markSafetySourceRefreshTimedOut( stillInFlight.valueAt(i), setError); } mSafetyCenterDataChangeNotifier.updateDataConsumers(mUserProfileGroup); - if (!showErrorEntriesOnTimeout) { - mSafetyCenterListeners.deliverErrorForUserProfileGroup( - mUserProfileGroup, - new SafetyCenterErrorDetails( - mSafetyCenterResourcesApk.getStringByName("refresh_timeout"))); - } } } diff --git a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt index 6cd24f800..298d7643c 100644 --- a/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt +++ b/tests/cts/safetycenter/src/android/safetycenter/cts/SafetyCenterManagerTest.kt @@ -1111,11 +1111,11 @@ class SafetyCenterManagerTest { ) // Because wrong ID, refresh hasn't finished. Wait for timeout. - listener.receiveSafetyCenterErrorDetails() + listener.waitForSafetyCenterRefresh(withErrorEntry = true) SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG) SafetySourceReceiver.setResponse( - Request.Rescan(SINGLE_SOURCE_ID), + Request.Refresh(SINGLE_SOURCE_ID), Response.SetData(safetySourceTestData.information) ) safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( @@ -1185,7 +1185,7 @@ class SafetyCenterManagerTest { safetyCenterManager.getSafetySourceDataWithPermission(SINGLE_SOURCE_ID) assertThat(apiSafetySourceData1).isNull() // Wait for the ongoing refresh to timeout. - listener.receiveSafetyCenterErrorDetails() + listener.waitForSafetyCenterRefresh(withErrorEntry = true) SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_LONG) SafetySourceReceiver.setResponse( Request.Refresh(SINGLE_SOURCE_ID), @@ -1237,18 +1237,11 @@ class SafetyCenterManagerTest { REFRESH_REASON_RESCAN_BUTTON_CLICK ) - val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails() - assertThat(safetyCenterErrorDetailsFromListener) - .isEqualTo( - SafetyCenterErrorDetails( - safetyCenterResourcesApk.getStringByName("refresh_timeout") - ) - ) + listener.waitForSafetyCenterRefresh(withErrorEntry = true) } @Test fun refreshSafetySources_withUntrackedSourceThatTimesOut_doesNotTimeOut() { - SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) SafetyCenterFlags.untrackedSources = setOf(SOURCE_ID_1) safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) // SOURCE_ID_1 will timeout @@ -1264,14 +1257,11 @@ class SafetyCenterManagerTest { REFRESH_REASON_RESCAN_BUTTON_CLICK ) - assertFailsWith(TimeoutCancellationException::class) { - listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT) - } + listener.waitForSafetyCenterRefresh(withErrorEntry = false) } @Test fun refreshSafetySources_withMultipleUntrackedSourcesThatTimeOut_doesNotTimeOut() { - SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) SafetyCenterFlags.untrackedSources = setOf(SOURCE_ID_1, SOURCE_ID_2) safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig) // SOURCE_ID_1 and SOURCE_ID_2 will timeout @@ -1285,9 +1275,7 @@ class SafetyCenterManagerTest { REFRESH_REASON_RESCAN_BUTTON_CLICK ) - assertFailsWith(TimeoutCancellationException::class) { - listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT) - } + listener.waitForSafetyCenterRefresh(withErrorEntry = false) } @Test @@ -1301,25 +1289,20 @@ class SafetyCenterManagerTest { REFRESH_REASON_RESCAN_BUTTON_CLICK ) - val safetyCenterErrorDetailsFromListener = listener.receiveSafetyCenterErrorDetails() - assertThat(safetyCenterErrorDetailsFromListener) - .isEqualTo( - SafetyCenterErrorDetails( - safetyCenterResourcesApk.getStringByName("refresh_timeout") - ) - ) + listener.waitForSafetyCenterRefresh(withErrorEntry = true) } @Test fun refreshSafetySources_withTrackedSourceThatHasNoReceiver_doesNotTimeOut() { - SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceOtherPackageConfig) val listener = safetyCenterTestHelper.addListener() safetyCenterManager.refreshSafetySourcesWithPermission(REFRESH_REASON_RESCAN_BUTTON_CLICK) assertFailsWith(TimeoutCancellationException::class) { - listener.receiveSafetyCenterErrorDetails(TIMEOUT_SHORT) + // In this case a refresh isn't even started because there is only a single source + // without a receiver. + listener.receiveSafetyCenterData(TIMEOUT_SHORT) } } diff --git a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt index de7a18c0e..9534b597a 100644 --- a/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt +++ b/tests/functional/safetycenter/multiusers/src/android/safetycenter/functional/multiusers/SafetyCenterMultiUsersTest.kt @@ -35,8 +35,6 @@ import android.safetycenter.SafetyCenterIssue import android.safetycenter.SafetyCenterManager import android.safetycenter.SafetyCenterStaticEntry import android.safetycenter.SafetyCenterStaticEntryGroup -import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS -import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_NONE import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceData import androidx.test.core.app.ApplicationProvider @@ -1279,10 +1277,7 @@ class SafetyCenterMultiUsersTest { private fun disableQuietModeAndWaitForRefreshToComplete() { val listener = safetyCenterTestHelper.addListener() deviceState.workProfile().setQuietMode(false) - listener.receiveSafetyCenterData { - it.status.refreshStatus == REFRESH_STATUS_DATA_FETCH_IN_PROGRESS - } - listener.receiveSafetyCenterData { it.status.refreshStatus == REFRESH_STATUS_NONE } + listener.waitForSafetyCenterRefresh() } private fun safetyCenterEntryOkForWork(sourceId: String, managedUserId: Int) = 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 0c952ede2..e2a7802b3 100644 --- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt +++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt @@ -766,9 +766,8 @@ class SafetyCenterManagerTest { @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper) @Test - fun refreshSafetySources_withShowEntriesOnTimeout_marksSafetySourceAsError() { + fun refreshSafetySources_timeout_marksSafetySourceAsError() { SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) - SafetyCenterFlags.showErrorEntriesOnTimeout = true safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) val listener = safetyCenterTestHelper.addListener() @@ -786,9 +785,8 @@ class SafetyCenterManagerTest { } @Test - fun refreshSafetySources_withShowEntriesOnTimeout_keepsShowingErrorUntilClearedBySource() { + fun refreshSafetySources_timeout_keepsShowingErrorUntilClearedBySource() { SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) - SafetyCenterFlags.showErrorEntriesOnTimeout = true safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) val listener = safetyCenterTestHelper.addListener() safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait( @@ -816,18 +814,15 @@ class SafetyCenterManagerTest { } @Test - fun refreshSafetySources_withShowEntriesOnTimeout_doesntSetErrorForBackgroundRefreshes() { + fun refreshSafetySources_timeout_doesntSetErrorForBackgroundRefreshes() { SafetyCenterFlags.setAllRefreshTimeoutsTo(TIMEOUT_SHORT) - SafetyCenterFlags.showErrorEntriesOnTimeout = true safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig) val listener = safetyCenterTestHelper.addListener() safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(REFRESH_REASON_OTHER) - val safetyCenterBeforeTimeout = listener.receiveSafetyCenterData() - assertThat(safetyCenterBeforeTimeout.status.refreshStatus) - .isEqualTo(REFRESH_STATUS_DATA_FETCH_IN_PROGRESS) - val safetyCenterDataAfterTimeout = listener.receiveSafetyCenterData() + val safetyCenterDataAfterTimeout = + listener.waitForSafetyCenterRefresh(withErrorEntry = false) assertThat(safetyCenterDataAfterTimeout).isEqualTo(safetyCenterDataFromConfig) } diff --git a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt index 9e3a33018..d020aac0a 100644 --- a/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt +++ b/tests/hostside/safetycenter/helper-app/src/android/safetycenter/hostside/device/SafetySourceStateCollectedLoggingHelperTests.kt @@ -18,9 +18,6 @@ package android.safetycenter.hostside.device import android.content.Context import android.safetycenter.SafetyCenterManager -import android.safetycenter.SafetyCenterStatus -import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS -import android.safetycenter.SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS import android.safetycenter.SafetyEvent import android.safetycenter.SafetySourceErrorDetails import androidx.test.core.app.ApplicationProvider @@ -168,12 +165,6 @@ class SafetySourceStateCollectedLoggingHelperTests { // things are logged. val listener = safetyCenterTestHelper.addListener() safetyCenterManager.refreshSafetySourcesWithReceiverPermissionAndWait(refreshReason) - listener.receiveSafetyCenterData { - it.status.refreshStatus == REFRESH_STATUS_DATA_FETCH_IN_PROGRESS || - it.status.refreshStatus == REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS - } - listener.receiveSafetyCenterData { - it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_NONE - } + listener.waitForSafetyCenterRefresh() } } 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 6c6336da3..a7009b19e 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/Coroutines.kt @@ -30,7 +30,11 @@ import kotlinx.coroutines.withTimeoutOrNull /** A class that facilitates interacting with coroutines. */ object Coroutines { - private val TEST_TIMEOUT: Duration + /** + * The timeout of a test case, typically varies depending on whether the test is running + * locally, on pre-submit or post-submit. + */ + val TEST_TIMEOUT: Duration get() = Duration.ofMillis( InstrumentationRegistry.getArguments().getString("timeout_msec", "60000").toLong() diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt index 28b621d5c..5cc2e3c8e 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt @@ -34,6 +34,7 @@ import android.safetycenter.SafetyCenterManager.REFRESH_REASON_RESCAN_BUTTON_CLI import android.safetycenter.SafetyCenterManager.REFRESH_REASON_SAFETY_CENTER_ENABLED import android.safetycenter.SafetySourceData import com.android.modules.utils.build.SdkLevel +import com.android.safetycenter.testing.Coroutines.TEST_TIMEOUT import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG import com.android.safetycenter.testing.ShellPermissions.callWithShellPermissionIdentity import java.time.Duration @@ -100,13 +101,6 @@ object SafetyCenterFlags { DurationParser() ) - /** - * Flag that determines whether we should show error entries for sources that timeout when - * refreshing them. - */ - private val showErrorEntriesOnTimeoutFlag = - Flag("safety_center_show_error_entries_on_timeout", defaultValue = false, BooleanParser()) - /** Flag that determines whether we should replace the IconAction of the lock screen source. */ private val replaceLockScreenIconActionFlag = Flag("safety_center_replace_lock_screen_icon_action", defaultValue = true, BooleanParser()) @@ -115,11 +109,16 @@ object SafetyCenterFlags { * Flag that determines the time for which a Safety Center refresh is allowed to wait for a * source to respond to a refresh request before timing out and marking the refresh as finished, * depending on the refresh reason. + * + * Unlike the production code, this flag is set to [TEST_TIMEOUT] for all refresh reasons by + * default for convenience. UI tests typically will set some data manually rather than going + * through a full refresh, and we don't want to timeout the refresh and potentially end up with + * error entries in this case (as it could lead to flakyness). */ private val refreshSourceTimeoutsFlag = Flag( "safety_center_refresh_sources_timeouts_millis", - defaultValue = getAllRefreshTimeoutsMap(TIMEOUT_LONG), + defaultValue = getAllRefreshTimeoutsMap(TEST_TIMEOUT), MapParser(IntParser(), DurationParser()) ) @@ -302,7 +301,6 @@ object SafetyCenterFlags { notificationsMinDelayFlag, immediateNotificationBehaviorIssuesFlag, notificationResurfaceIntervalFlag, - showErrorEntriesOnTimeoutFlag, replaceLockScreenIconActionFlag, refreshSourceTimeoutsFlag, resolveActionTimeoutFlag, @@ -341,14 +339,11 @@ object SafetyCenterFlags { /** A property that allows getting and setting the [notificationResurfaceIntervalFlag]. */ var notificationResurfaceInterval: Duration by notificationResurfaceIntervalFlag - /** A property that allows getting and setting the [showErrorEntriesOnTimeoutFlag]. */ - var showErrorEntriesOnTimeout: Boolean by showErrorEntriesOnTimeoutFlag - /** A property that allows getting and setting the [replaceLockScreenIconActionFlag]. */ var replaceLockScreenIconAction: Boolean by replaceLockScreenIconActionFlag /** A property that allows getting and setting the [refreshSourceTimeoutsFlag]. */ - var refreshTimeouts: Map<Int, Duration> by refreshSourceTimeoutsFlag + private var refreshTimeouts: Map<Int, Duration> by refreshSourceTimeoutsFlag /** A property that allows getting and setting the [resolveActionTimeoutFlag]. */ var resolveActionTimeout: Duration by resolveActionTimeoutFlag diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt index 624c32d5f..8ce5c25d4 100644 --- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt +++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestListener.kt @@ -18,9 +18,14 @@ package com.android.safetycenter.testing import android.os.Build.VERSION_CODES.TIRAMISU import android.safetycenter.SafetyCenterData +import android.safetycenter.SafetyCenterEntry import android.safetycenter.SafetyCenterErrorDetails import android.safetycenter.SafetyCenterManager.OnSafetyCenterDataChangedListener +import android.safetycenter.SafetyCenterStaticEntry +import android.safetycenter.SafetyCenterStatus +import android.text.TextUtils import androidx.annotation.RequiresApi +import androidx.test.core.app.ApplicationProvider.getApplicationContext import com.android.safetycenter.testing.Coroutines.TIMEOUT_LONG import com.android.safetycenter.testing.Coroutines.runBlockingWithTimeout import java.time.Duration @@ -64,6 +69,47 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener { } /** + * Waits for a full Safety Center refresh to complete, where each change to the underlying + * [SafetyCenterData] must happen within the given [timeout]. + * + * @param withErrorEntry optionally check whether we should expect the [SafetyCenterData] to + * have or not have at least one an error entry after the refresh completes + * @return the [SafetyCenterData] after the refresh completes + */ + fun waitForSafetyCenterRefresh( + timeout: Duration = TIMEOUT_LONG, + withErrorEntry: Boolean? = null + ): SafetyCenterData { + receiveSafetyCenterData(timeout) { + it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS || + it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS + } + val afterRefresh = + receiveSafetyCenterData(timeout) { + it.status.refreshStatus == SafetyCenterStatus.REFRESH_STATUS_NONE + } + if (withErrorEntry == null) { + return afterRefresh + } + val errorMessage = + SafetyCenterTestData(getApplicationContext()) + .getRefreshErrorString(numberOfErrorEntries = 1) + val containsErrorEntry = afterRefresh.containsAnyEntryWithSummary(errorMessage) + if (withErrorEntry && !containsErrorEntry) { + throw AssertionError( + "No error entry with message: \"$errorMessage\" found in SafetyCenterData" + + " after refresh: $afterRefresh" + ) + } else if (!withErrorEntry && containsErrorEntry) { + throw AssertionError( + "Found an error entry with message: \"$errorMessage\" in SafetyCenterData" + + " after refresh: $afterRefresh" + ) + } + return afterRefresh + } + + /** * Waits for a [SafetyCenterErrorDetails] update from SafetyCenter within the given [timeout]. */ fun receiveSafetyCenterErrorDetails(timeout: Duration = TIMEOUT_LONG) = @@ -74,4 +120,23 @@ class SafetyCenterTestListener : OnSafetyCenterDataChangedListener { dataChannel.cancel() errorChannel.cancel() } + + private companion object { + fun SafetyCenterData.containsAnyEntryWithSummary(summary: CharSequence): Boolean = + entries().any { TextUtils.equals(it.summary, summary) } || + staticEntries().any { TextUtils.equals(it.summary, summary) } + + fun SafetyCenterData.entries(): List<SafetyCenterEntry> = + entriesOrGroups.flatMap { + val entry = it.entry + if (entry != null) { + listOf(entry) + } else { + it.entryGroup!!.entries + } + } + + fun SafetyCenterData.staticEntries(): List<SafetyCenterStaticEntry> = + staticEntryGroups.flatMap { it.staticEntries } + } } |