summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--flags/flags.aconfig9
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterDataFactory.java112
-rw-r--r--tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt110
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt17
4 files changed, 226 insertions, 22 deletions
diff --git a/flags/flags.aconfig b/flags/flags.aconfig
index 98c64507d..c694e36be 100644
--- a/flags/flags.aconfig
+++ b/flags/flags.aconfig
@@ -81,3 +81,12 @@ flag {
bug: "356910008"
is_fixed_read_only: true
}
+
+flag {
+ name: "safety_center_issue_only_affects_group_status"
+ is_exported: true
+ namespace: "permissions"
+ description: "This flag is used by Safety Center to affect the status light of an entry group when an issue only source pushes a warning"
+ bug: "356910111"
+ is_fixed_read_only: true
+}
diff --git a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
index 7c7ade3f1..7f4e2e166 100644
--- a/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
+++ b/service/java/com/android/safetycenter/SafetyCenterDataFactory.java
@@ -17,7 +17,6 @@
package com.android.safetycenter;
import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
-import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM;
import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED;
import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY;
@@ -388,6 +387,8 @@ public final class SafetyCenterDataFactory {
SafetySourcesGroup safetySourcesGroup,
String defaultPackageName,
UserProfileGroup userProfileGroup) {
+ HighestSeverityIssueOnlyIssue highestSeverityIssueOnlyIssue =
+ new HighestSeverityIssueOnlyIssue();
int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
@@ -415,6 +416,7 @@ public final class SafetyCenterDataFactory {
groupSafetyCenterEntryLevel,
addSafetyCenterEntry(
safetyCenterOverallState,
+ highestSeverityIssueOnlyIssue,
entries,
safetySource,
defaultPackageName,
@@ -436,7 +438,10 @@ public final class SafetyCenterDataFactory {
CharSequence groupSummary =
getSafetyCenterEntryGroupSummary(
- safetySourcesGroup, groupSafetyCenterEntryLevel, entries);
+ safetySourcesGroup,
+ groupSafetyCenterEntryLevel,
+ entries,
+ highestSeverityIssueOnlyIssue);
safetyCenterEntryOrGroups.add(
new SafetyCenterEntryOrGroup(
new SafetyCenterEntryGroup.Builder(
@@ -471,7 +476,8 @@ public final class SafetyCenterDataFactory {
private CharSequence getSafetyCenterEntryGroupSummary(
SafetySourcesGroup safetySourcesGroup,
@SafetyCenterEntry.EntrySeverityLevel int groupSafetyCenterEntryLevel,
- List<SafetyCenterEntry> entries) {
+ List<SafetyCenterEntry> entries,
+ HighestSeverityIssueOnlyIssue highestSeverityIssueOnlyIssue) {
switch (groupSafetyCenterEntryLevel) {
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
@@ -500,6 +506,16 @@ public final class SafetyCenterDataFactory {
}
}
+ SafetySourceIssue highestSeverityIssueOnlySafetySourceIssue =
+ highestSeverityIssueOnlyIssue.mSafetySourceIssue;
+ if (highestSeverityIssueOnlySafetySourceIssue != null
+ && toSafetyCenterEntrySeverityLevel(
+ highestSeverityIssueOnlySafetySourceIssue
+ .getSeverityLevel())
+ == groupSafetyCenterEntryLevel) {
+ return highestSeverityIssueOnlySafetySourceIssue.getTitle();
+ }
+
return getDefaultGroupSummary(safetySourcesGroup, entries);
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
return getDefaultGroupSummary(safetySourcesGroup, entries);
@@ -554,6 +570,7 @@ public final class SafetyCenterDataFactory {
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterEntry(
SafetyCenterOverallState safetyCenterOverallState,
+ HighestSeverityIssueOnlyIssue highestSeverityIssueOnlyIssue,
List<SafetyCenterEntry> entries,
SafetySource safetySource,
String defaultPackageName,
@@ -562,13 +579,10 @@ public final class SafetyCenterDataFactory {
boolean isUserRunning) {
SafetyCenterEntry safetyCenterEntry =
toSafetyCenterEntry(
- safetySource,
- defaultPackageName,
- userId,
- profileType,
- isUserRunning);
+ safetySource, defaultPackageName, userId, profileType, isUserRunning);
if (safetyCenterEntry == null) {
- return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ return getSafetyCenterEntrySeverityLevelFromIssues(
+ highestSeverityIssueOnlyIssue, safetySource, userId, isUserRunning);
}
safetyCenterOverallState.addEntryOverallSeverityLevel(
@@ -579,6 +593,64 @@ public final class SafetyCenterDataFactory {
return safetyCenterEntry.getSeverityLevel();
}
+ @SafetyCenterEntry.EntrySeverityLevel
+ private int getSafetyCenterEntrySeverityLevelFromIssues(
+ HighestSeverityIssueOnlyIssue highestSeverityIssueOnlyIssue,
+ SafetySource safetySource,
+ @UserIdInt int userId,
+ boolean isUserRunning) {
+ if (!Flags.safetyCenterIssueOnlyAffectsGroupStatus()) {
+ return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ }
+ if (safetySource.getType() != SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY) {
+ return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ }
+ if (!isUserRunning) {
+ // Issues don't show for non-running users.
+ return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ }
+ SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
+ SafetySourceData safetySourceData =
+ mSafetyCenterDataManager.getSafetySourceDataInternal(key);
+ if (safetySourceData == null) {
+ return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ }
+ List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
+ int safetyCenterEntrySeverityLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
+ for (int i = 0; i < safetySourceIssues.size(); i++) {
+ SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
+
+ SafetyCenterIssueKey safetyCenterIssueKey =
+ SafetyCenterIssueKey.newBuilder()
+ .setSafetySourceId(safetySource.getId())
+ .setSafetySourceIssueId(safetySourceIssue.getId())
+ .setUserId(userId)
+ .build();
+
+ int safetySourceIssueSeverityLevel = safetySourceIssue.getSeverityLevel();
+
+ if (mSafetyCenterDataManager.isIssueDismissed(
+ safetyCenterIssueKey, safetySourceIssueSeverityLevel)) {
+ continue;
+ }
+
+ SafetySourceIssue highestSeverityIssueOnlySafetySourceIssue =
+ highestSeverityIssueOnlyIssue.mSafetySourceIssue;
+ if (highestSeverityIssueOnlySafetySourceIssue == null
+ || safetySourceIssueSeverityLevel
+ > highestSeverityIssueOnlySafetySourceIssue.getSeverityLevel()) {
+ highestSeverityIssueOnlyIssue.mSafetySourceIssue = safetySourceIssue;
+ }
+
+ safetyCenterEntrySeverityLevel =
+ mergeSafetyCenterEntrySeverityLevels(
+ safetyCenterEntrySeverityLevel,
+ toSafetyCenterEntrySeverityLevel(safetySourceIssueSeverityLevel));
+ }
+
+ return safetyCenterEntrySeverityLevel;
+ }
+
@Nullable
private SafetyCenterEntry toSafetyCenterEntry(
SafetySource safetySource,
@@ -783,11 +855,7 @@ public final class SafetyCenterDataFactory {
boolean isUserRunning) {
SafetyCenterStaticEntry staticEntry =
toSafetyCenterStaticEntry(
- safetySource,
- defaultPackageName,
- userId,
- profileType,
- isUserRunning);
+ safetySource, defaultPackageName, userId, profileType, isUserRunning);
if (staticEntry == null) {
return;
}
@@ -987,7 +1055,7 @@ public final class SafetyCenterDataFactory {
Log.w(
TAG,
- "Unexpected SafetySourceData.SeverityLevel in SafetySourceStatus: "
+ "Unexpected SafetySourceData.SeverityLevel in SafetySourceData: "
+ safetySourceSeverityLevel);
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
@@ -1155,7 +1223,7 @@ public final class SafetyCenterDataFactory {
return getIcuPluralsString(
"overall_severity_level_action_taken_summary", numAutomaticIssues);
}
- // Fall through.
+ // Fall through.
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getIcuPluralsString("overall_severity_n_alerts_summary", numIssues);
@@ -1201,7 +1269,7 @@ public final class SafetyCenterDataFactory {
if (!overallSeverityUnknown) {
return null;
}
- // Fall through.
+ // Fall through.
case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
return mSafetyCenterResourcesApk.getStringByName("scanning_title");
}
@@ -1232,9 +1300,9 @@ public final class SafetyCenterDataFactory {
return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
case PROFILE_TYPE_MANAGED:
return DevicePolicyResources.getSafetySourceWorkString(
- mSafetyCenterResourcesApk,
- safetySource.getId(),
- safetySource.getTitleForWorkResId());
+ mSafetyCenterResourcesApk,
+ safetySource.getId(),
+ safetySource.getTitleForWorkResId());
case PROFILE_TYPE_PRIVATE:
if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
return mSafetyCenterResourcesApk.getString(
@@ -1337,4 +1405,8 @@ public final class SafetyCenterDataFactory {
return Math.max(left, right);
}
}
+
+ private static final class HighestSeverityIssueOnlyIssue {
+ @Nullable private SafetySourceIssue mSafetySourceIssue = null;
+ }
}
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 4f06c0f3f..cb3935ec5 100644
--- a/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
+++ b/tests/functional/safetycenter/singleuser/src/android/safetycenter/functional/SafetyCenterManagerTest.kt
@@ -22,6 +22,9 @@ import android.content.Intent
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.UserHandle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.safetycenter.SafetyCenterData
import android.safetycenter.SafetyCenterEntry
import android.safetycenter.SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING
@@ -64,6 +67,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SdkSuppress
import com.android.compatibility.common.util.SystemUtil
import com.android.modules.utils.build.SdkLevel
+import com.android.permission.flags.Flags
import com.android.safetycenter.internaldata.SafetyCenterBundles
import com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY
import com.android.safetycenter.internaldata.SafetyCenterEntryId
@@ -783,8 +787,9 @@ class SafetyCenterManagerTest {
)
)
- @get:Rule(order = 1) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
- @get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
+ @get:Rule(order = 1) val flagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
+ @get:Rule(order = 2) val supportsSafetyCenterRule = SupportsSafetyCenterRule(context)
+ @get:Rule(order = 3) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@Test
fun getSafetySourceData_differentPackageWithManageSafetyCenterPermission_returnsData() {
@@ -3153,6 +3158,105 @@ class SafetyCenterManagerTest {
}
@Test
+ @RequiresFlagsEnabled(Flags.FLAG_SAFETY_CENTER_ISSUE_ONLY_AFFECTS_GROUP_STATUS)
+ fun getSafetyCenterData_entryGroupWithIssueOnlySourceHighestOfGroup_affectsEntryGroupStatus() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
+ safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.severityLevel)
+ .isEqualTo(ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.summary!!.toString())
+ .isEqualTo("Recommendation issue title")
+ }
+
+ @Test
+ @RequiresFlagsEnabled(Flags.FLAG_SAFETY_CENTER_ISSUE_ONLY_AFFECTS_GROUP_STATUS)
+ fun getSafetyCenterData_entryGroupWithIssueOnlySourceSameAsGroup_affectsTitle() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
+ safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.severityLevel)
+ .isEqualTo(ENTRY_SEVERITY_LEVEL_OK)
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.summary!!.toString())
+ .isEqualTo("Information issue title")
+ }
+
+ @Test
+ fun getSafetyCenterData_entryGroupWithIssueOnlySourceNotHighest_doesntAffectEntryGroupStatus() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
+ safetyCenterTestHelper.setData(
+ SOURCE_ID_1,
+ safetySourceTestData.recommendationWithGeneralIssue
+ )
+ safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.informationIssue)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.severityLevel)
+ .isEqualTo(ENTRY_SEVERITY_LEVEL_RECOMMENDATION)
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.summary!!.toString())
+ .isEqualTo("Recommendation summary")
+ }
+
+ @Test
+ fun getSafetyCenterData_entryGroupWithIssueOnlySourceDismissed_doesntAffectEntryGroupStatus() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
+ safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ )
+ safetyCenterManager.dismissSafetyCenterIssueWithPermission(
+ SafetyCenterTestData.issueId(ISSUE_ONLY_ALL_OPTIONAL_ID, RECOMMENDATION_ISSUE_ID)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.severityLevel)
+ .isEqualTo(ENTRY_SEVERITY_LEVEL_OK)
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.summary!!.toString())
+ .isEqualTo("OK")
+ }
+
+ @Test
+ @RequiresFlagsDisabled(Flags.FLAG_SAFETY_CENTER_ISSUE_ONLY_AFFECTS_GROUP_STATUS)
+ fun getSafetyCenterData_entryGroupWithIssueOnlySourceFlagOff_doesntAffectEntryGroupStatus() {
+ safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.entryGroupWithIssueOnlyConfig)
+ safetyCenterTestHelper.setData(SOURCE_ID_1, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(SOURCE_ID_2, safetySourceTestData.information)
+ safetyCenterTestHelper.setData(
+ ISSUE_ONLY_ALL_OPTIONAL_ID,
+ SafetySourceTestData.issuesOnly(safetySourceTestData.recommendationGeneralIssue)
+ )
+
+ val apiSafetyCenterData = safetyCenterManager.getSafetyCenterDataWithPermission()
+
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.severityLevel)
+ .isEqualTo(ENTRY_SEVERITY_LEVEL_OK)
+ assertThat(apiSafetyCenterData.entriesOrGroups.single().entryGroup!!.summary!!.toString())
+ .isEqualTo("OK")
+ }
+
+ @Test
fun addOnSafetyCenterDataChangedListener_listenerCalledWithSafetyCenterDataFromConfig() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -3836,9 +3940,11 @@ 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
// resurfaced. Use a constant additive error buffer if we increase the delay considerably.
private val RESURFACE_TIMEOUT = RESURFACE_DELAY.multipliedBy(3)
+
// 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)
// if we increase the delay considerably.
diff --git a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
index 0e31b2934..261e179dd 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterTestConfigs.kt
@@ -351,6 +351,23 @@ class SafetyCenterTestConfigs(private val context: Context) {
.build()
/**
+ * A simple [SafetyCenterConfig] for tests with a single stateful group containing 2 dynamic
+ * sources and an issue only source.
+ */
+ val entryGroupWithIssueOnlyConfig =
+ SafetyCenterConfig.Builder()
+ .addSafetySourcesGroup(
+ safetySourcesGroupBuilder("EntryGroupWithIssueOnly")
+ .addSafetySource(dynamicSource1)
+ .addSafetySource(dynamicSource2)
+ .addSafetySource(
+ issueOnlySafetySourceBuilder(ISSUE_ONLY_ALL_OPTIONAL_ID).build()
+ )
+ .build()
+ )
+ .build()
+
+ /**
* A simple [SafetyCenterConfig] for tests with multiple sources with one source having an
* invalid default intent.
*/