summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tyler Dewey <deweytyl@google.com> 2025-03-03 06:14:24 -0800
committer Android (Google) Code Review <android-gerrit@google.com> 2025-03-03 06:14:24 -0800
commit47e5e0643a0235d3eea66a71220154364758ce14 (patch)
treea2dc4e6f45de97da0dcc3508a0c1c59315606620
parent020ab32e977480109f5784d48008edff1946d091 (diff)
parent5098fc7aff57f4819be964c02920a1f815befb55 (diff)
Merge "Use BannerMessagePrefs on SC homepage when expressive design enabled." into main
-rw-r--r--PermissionController/Android.bp1
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java3
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java40
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/expressive/SafetyBannerMessagePreference.kt190
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt10
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/Android.bp1
-rw-r--r--tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt34
-rw-r--r--tests/utils/safetycenter/Android.bp1
-rw-r--r--tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt103
9 files changed, 332 insertions, 51 deletions
diff --git a/PermissionController/Android.bp b/PermissionController/Android.bp
index 63fb1a264..c1a54619b 100644
--- a/PermissionController/Android.bp
+++ b/PermissionController/Android.bp
@@ -131,6 +131,7 @@ android_library {
"SettingsLibSearchWidget",
"SettingsLibLayoutPreference",
"SettingsLibBarChartPreference",
+ "SettingsLibBannerMessagePreference",
"SettingsLibActionBarShadow",
"SettingsLibProgressBar",
"SettingsLibCollapsingToolbarBaseActivity",
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
index 88759797e..e47565e3b 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/IssueCardPreference.java
@@ -295,7 +295,8 @@ public class IssueCardPreference extends Preference
public static class ConfirmDismissalDialogFragment extends DialogFragment {
private static final String ISSUE_KEY = "confirm_dialog_sc_issue";
- private static ConfirmDismissalDialogFragment newInstance(SafetyCenterIssue issue) {
+ /** Create new fragment with the data it will need. */
+ public static ConfirmDismissalDialogFragment newInstance(SafetyCenterIssue issue) {
ConfirmDismissalDialogFragment fragment = new ConfirmDismissalDialogFragment();
Bundle args = new Bundle();
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
index ed6bc382c..1297bc4c2 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/SafetyCenterDashboardFragment.java
@@ -35,7 +35,6 @@ import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
import android.safetycenter.SafetyCenterEntryOrGroup;
-import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterStaticEntry;
import android.safetycenter.SafetyCenterStaticEntryGroup;
import android.util.Log;
@@ -52,6 +51,8 @@ import androidx.recyclerview.widget.RecyclerView;
import com.android.modules.utils.build.SdkLevel;
import com.android.permissioncontroller.R;
+import com.android.permissioncontroller.safetycenter.ui.expressive.SafetyBannerMessagePreference;
+import com.android.permissioncontroller.safetycenter.ui.model.IssueUiData;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterUiData;
import com.android.permissioncontroller.safetycenter.ui.model.StatusUiData;
import com.android.safetycenter.internaldata.SafetyCenterBundles;
@@ -61,7 +62,6 @@ import com.android.settingslib.widget.SettingsThemeHelper;
import kotlin.Unit;
import java.util.List;
-import java.util.Map;
import java.util.Objects;
/** Dashboard fragment for the Safety Center. */
@@ -214,7 +214,7 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
// TODO(b/208212820): Only update entries that have changed since last
// update, rather than deleting and re-adding all.
- updateIssues(context, data.getIssues(), uiData.getResolvedIssues());
+ updateIssues(context, uiData);
if (!mIsQuickSettingsFragment) {
updateSafetyEntries(context, data.getEntriesOrGroups());
@@ -222,19 +222,29 @@ public final class SafetyCenterDashboardFragment extends SafetyCenterFragment {
}
}
- private void updateIssues(
- Context context, List<SafetyCenterIssue> issues, Map<String, String> resolvedIssues) {
+ private void updateIssues(Context context, SafetyCenterUiData uiData) {
mIssuesGroup.removeAll();
- getCollapsableIssuesCardHelper()
- .addIssues(
- context,
- getSafetyCenterViewModel(),
- getChildFragmentManager(),
- mIssuesGroup,
- issues,
- emptyList(),
- resolvedIssues,
- getActivity().getTaskId());
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ for (IssueUiData issueUiData : uiData.getIssueUiDatas()) {
+ mIssuesGroup.addPreference(
+ new SafetyBannerMessagePreference(
+ context,
+ issueUiData,
+ getSafetyCenterViewModel(),
+ getChildFragmentManager()));
+ }
+ } else {
+ getCollapsableIssuesCardHelper()
+ .addIssues(
+ context,
+ getSafetyCenterViewModel(),
+ getChildFragmentManager(),
+ mIssuesGroup,
+ uiData.getSafetyCenterData().getIssues(),
+ emptyList(),
+ uiData.getResolvedIssues(),
+ requireActivity().getTaskId());
+ }
}
// TODO(b/208212820): Add groups and move to separate controller
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/expressive/SafetyBannerMessagePreference.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/expressive/SafetyBannerMessagePreference.kt
new file mode 100644
index 000000000..0f2239c60
--- /dev/null
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/expressive/SafetyBannerMessagePreference.kt
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 2025 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.safetycenter.ui.expressive
+
+import android.content.Context
+import android.os.Build
+import android.safetycenter.SafetyCenterIssue
+import android.util.Log
+import android.view.View
+import android.widget.LinearLayout
+import androidx.annotation.RequiresApi
+import androidx.fragment.app.FragmentManager
+import androidx.preference.Preference
+import androidx.preference.PreferenceViewHolder
+import com.android.modules.utils.build.SdkLevel
+import com.android.permissioncontroller.R
+import com.android.permissioncontroller.safetycenter.ui.Action
+import com.android.permissioncontroller.safetycenter.ui.ComparablePreference
+import com.android.permissioncontroller.safetycenter.ui.IssueCardPreference.ConfirmActionDialogFragment
+import com.android.permissioncontroller.safetycenter.ui.IssueCardPreference.ConfirmDismissalDialogFragment
+import com.android.permissioncontroller.safetycenter.ui.model.IssueUiData
+import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel
+import com.android.settingslib.widget.BannerMessagePreference
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+class SafetyBannerMessagePreference(
+ context: Context,
+ private val issueUiData: IssueUiData,
+ private val viewModel: SafetyCenterViewModel,
+ private val dialogFragmentManager: FragmentManager,
+) : BannerMessagePreference(context), ComparablePreference {
+
+ init {
+ setButtonOrientation(LinearLayout.VERTICAL)
+ displayIssue()
+ }
+
+ override fun onBindViewHolder(holder: PreferenceViewHolder) {
+ super.onBindViewHolder(holder)
+
+ viewModel.interactionLogger.recordIssueViewed(issueUiData.issue, issueUiData.isDismissed)
+ }
+
+ private fun displayIssue() {
+ setAttentionLevel(issueUiData.issue.severityLevel.toAttentionLevel())
+
+ title = issueUiData.issue.title
+ summary = issueUiData.issue.summary
+ setHeader(issueUiData.issue.attributionTitle)
+ setSubtitle(issueUiData.issue.subtitle)
+ // Note: BannerMessagePreference i think always shows an icon (even if it's set to null),
+ // which is not in the spec
+
+ configureDismissButton()
+ configureActionButtons()
+ maybeStartResolution()
+ }
+
+ private fun configureDismissButton() {
+ if (issueUiData.issue.isDismissible && !issueUiData.isDismissed) {
+ setDismissButtonVisible(true)
+ setDismissButtonOnClickListener {
+ if (issueUiData.issue.shouldConfirmDismissal()) {
+ ConfirmDismissalDialogFragment.newInstance(issueUiData.issue)
+ .showNow(dialogFragmentManager, /* tag= */ null)
+ } else {
+ viewModel.dismissIssue(issueUiData.issue)
+ viewModel.interactionLogger.recordForIssue(
+ Action.ISSUE_DISMISS_CLICKED,
+ issueUiData.issue,
+ isDismissed = false,
+ )
+ }
+ }
+ } else {
+ setDismissButtonVisible(false)
+ setDismissButtonOnClickListener(null)
+ }
+ }
+
+ private fun configureActionButtons() {
+ val primaryAction = issueUiData.issue.actions.getOrNull(0)
+ if (primaryAction != null) {
+ setPositiveButtonText(primaryAction.label)
+ setPositiveButtonEnabled(issueUiData.resolvedIssueActionId != primaryAction.id)
+ setPositiveButtonVisible(true)
+ setPositiveButtonOnClickListener(
+ ActionButtonOnClickListener(primaryAction, isPrimaryButton = true)
+ )
+ } else {
+ setPositiveButtonVisible(false)
+ setPositiveButtonOnClickListener(null)
+ }
+
+ val secondaryAction = issueUiData.issue.actions.getOrNull(1)
+ if (secondaryAction != null) {
+ setNegativeButtonText(secondaryAction.label)
+ setNegativeButtonEnabled(issueUiData.resolvedIssueActionId != secondaryAction.id)
+ setNegativeButtonVisible(true)
+ setNegativeButtonOnClickListener(
+ ActionButtonOnClickListener(secondaryAction, isPrimaryButton = false)
+ )
+ } else {
+ setNegativeButtonVisible(false)
+ setNegativeButtonOnClickListener(null)
+ }
+ }
+
+ private inner class ActionButtonOnClickListener(
+ private val action: SafetyCenterIssue.Action,
+ private val isPrimaryButton: Boolean,
+ ) : View.OnClickListener {
+ override fun onClick(v: View?) {
+ if (SdkLevel.isAtLeastU() && action.confirmationDialogDetails != null) {
+ ConfirmActionDialogFragment.newInstance(
+ issueUiData.issue,
+ action,
+ issueUiData.launchTaskId,
+ isPrimaryButton,
+ issueUiData.isDismissed,
+ )
+ .showNow(dialogFragmentManager, /* tag= */ null)
+ } else {
+ if (action.willResolve()) {
+ setPositiveButtonEnabled(false)
+ }
+ viewModel.executeIssueAction(issueUiData.issue, action, issueUiData.launchTaskId)
+ viewModel.interactionLogger.recordForIssue(
+ if (isPrimaryButton) {
+ Action.ISSUE_PRIMARY_ACTION_CLICKED
+ } else {
+ Action.ISSUE_SECONDARY_ACTION_CLICKED
+ },
+ issueUiData.issue,
+ issueUiData.isDismissed,
+ )
+ }
+ }
+ }
+
+ private fun maybeStartResolution() {
+ val resolvedActionId = issueUiData.resolvedIssueActionId ?: return
+
+ val action = issueUiData.issue.actions.firstOrNull { it.id == resolvedActionId } ?: return
+ val successMessage =
+ action.successMessage?.ifEmpty { null }
+ ?: context.getString(R.string.safety_center_resolved_issue_fallback)
+
+ showResolutionAnimation(successMessage) {
+ viewModel.markIssueResolvedUiCompleted(issueUiData.issue.id)
+ }
+ }
+
+ private fun Int.toAttentionLevel(): AttentionLevel {
+ return when (this) {
+ SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK -> AttentionLevel.LOW
+ SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION -> AttentionLevel.MEDIUM
+ SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING -> AttentionLevel.HIGH
+ else -> {
+ Log.w(TAG, "Unexpected issue severity level $this")
+ AttentionLevel.LOW
+ }
+ }
+ }
+
+ private companion object {
+ const val TAG = "SafetyBannerMessagePref"
+ }
+
+ override fun isSameItem(preference: Preference): Boolean =
+ preference is SafetyBannerMessagePreference &&
+ preference.issueUiData.issue.id == issueUiData.issue.id
+
+ override fun hasSameContents(preference: Preference): Boolean =
+ preference is SafetyBannerMessagePreference && preference.issueUiData == issueUiData
+}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
index d8aadae2f..1595b5812 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/model/SafetyCenterUiData.kt
@@ -39,9 +39,7 @@ data class SafetyCenterUiData(
val issueUiDatas: List<IssueUiData> by
lazy(LazyThreadSafetyMode.NONE) {
- safetyCenterData.issues.map {
- IssueUiData(it, false, resolvedIssues[it.id], getLaunchTaskIdForIssue(it))
- }
+ safetyCenterData.issues.map { toIssueUiData(it, isDismissed = false) }
}
fun getMatchingIssue(issueKey: SafetyCenterIssueKey): SafetyCenterIssue? {
@@ -92,9 +90,13 @@ data class SafetyCenterUiData(
/** Returns the [SafetyCenterData.getDismissedIssues] that are meant to be visible in the UI. */
@RequiresApi(UPSIDE_DOWN_CAKE)
- fun SafetyCenterData.visibleDismissedIssues() =
+ private fun SafetyCenterData.visibleDismissedIssues() =
dismissedIssues.filter { it.severityLevel > ISSUE_SEVERITY_LEVEL_OK }
+ /** Converts a [SafetyCenterIssue] into [IssueUiData]. */
+ private fun toIssueUiData(issue: SafetyCenterIssue, isDismissed: Boolean) =
+ IssueUiData(issue, isDismissed, resolvedIssues[issue.id], getLaunchTaskIdForIssue(issue))
+
private fun getLaunchTaskIdForIssue(issue: SafetyCenterIssue): Int? {
val sourceId: String =
SafetyCenterIds.issueIdFromString(issue.id)
diff --git a/tests/functional/safetycenter/safetycenteractivity/Android.bp b/tests/functional/safetycenter/safetycenteractivity/Android.bp
index ea5f9f286..2346a0d5f 100644
--- a/tests/functional/safetycenter/safetycenteractivity/Android.bp
+++ b/tests/functional/safetycenter/safetycenteractivity/Android.bp
@@ -29,6 +29,7 @@ android_test {
"src/**/*.kt",
],
static_libs: [
+ "aconfig_settingstheme_exported_flags_java_lib",
"androidx.test.rules",
"androidx.test.ext.junit",
"compatibility-device-preconditions",
diff --git a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
index 09a32f058..fb577e8f6 100644
--- a/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
+++ b/tests/functional/safetycenter/safetycenteractivity/src/android/safetycenter/functional/ui/SafetyCenterActivityTest.kt
@@ -21,6 +21,9 @@ import android.os.Build
import android.os.Build.VERSION_CODES.TIRAMISU
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.os.Bundle
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.flag.junit.CheckFlagsRule
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ID
import android.safetycenter.SafetyCenterManager.EXTRA_SAFETY_SOURCE_ISSUE_ID
import android.safetycenter.SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING
@@ -74,6 +77,7 @@ import com.android.safetycenter.testing.UiTestHelper.waitPageTitleDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceDataDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueDisplayed
import com.android.safetycenter.testing.UiTestHelper.waitSourceIssueNotDisplayed
+import com.android.settingslib.widget.theme.flags.Flags as SettingsThemeFlags
import java.util.regex.Pattern
import org.junit.After
import org.junit.Assume.assumeFalse
@@ -95,6 +99,8 @@ class SafetyCenterActivityTest {
@get:Rule(order = 2) val safetyCenterTestRule = SafetyCenterTestRule(safetyCenterTestHelper)
@get:Rule(order = 3) val disableAnimationRule = DisableAnimationRule()
@get:Rule(order = 4) val freezeRotationRule = FreezeRotationRule()
+ @get:Rule(order = 5)
+ val checkFlagsRule: CheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule()
@After
fun clearDataAfterTest() {
@@ -567,6 +573,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/398188361 - Update this for expressive theme
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun issueCard_noAttribution_hasProperContentDescriptions() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.issueOnlySourceNoGroupTitleConfig)
@@ -581,6 +589,8 @@ class SafetyCenterActivityTest {
@Test
@SdkSuppress(minSdkVersion = UPSIDE_DOWN_CAKE)
+ // TODO: b/398188361 - Update this for expressive theme
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun issueCard_withAttribution_hasProperContentDescriptions() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
@@ -693,7 +703,7 @@ class SafetyCenterActivityTest {
@Test
fun issueCard_resolveIssue_successConfirmationShown() {
- SafetyCenterFlags.hideResolvedIssueUiTransitionDelay = TIMEOUT_LONG
+ SafetyCenterFlags.setHideResolvedIssueUiTransitionDelay(context, TIMEOUT_LONG)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
// Set the initial data for the source
@@ -829,7 +839,7 @@ class SafetyCenterActivityTest {
@Test
fun issueCard_resolveIssue_noSuccessMessage_noResolutionUiShown_issueDismisses() {
- SafetyCenterFlags.hideResolvedIssueUiTransitionDelay = TIMEOUT_LONG
+ SafetyCenterFlags.setHideResolvedIssueUiTransitionDelay(context, TIMEOUT_LONG)
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.singleSourceConfig)
// Set the initial data for the source
@@ -954,6 +964,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromQuickSettings_issuesExpanded() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -978,6 +990,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueAlreadyFirstIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1003,6 +1017,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueSamePriorityAsFirstIssue_reorderedFirstIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1028,6 +1044,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetLowerPriorityAsFirstIssue_reorderedSecondIssue() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1052,6 +1070,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun launchActivity_fromNotification_targetIssueNotFound() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1091,6 +1111,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_moreIssuesCardShown_additionalIssueCardsCollapsed() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1113,6 +1135,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_expandAdditionalIssueCards() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1139,6 +1163,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_rotation_cardsStillExpanded() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1173,6 +1199,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_withThreeIssues_showsTopIssuesAndMoreIssuesCard() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
@@ -1197,6 +1225,8 @@ class SafetyCenterActivityTest {
}
@Test
+ // TODO: b/379849464 - Fix this for expressive design and stop disabling this flag
+ @RequiresFlagsDisabled(SettingsThemeFlags.FLAG_IS_EXPRESSIVE_DESIGN_ENABLED)
fun moreIssuesCard_twoIssuesAlreadyShown_expandAdditionalIssueCards() {
safetyCenterTestHelper.setConfig(safetyCenterTestConfigs.multipleSourcesConfig)
safetyCenterTestHelper.setData(
diff --git a/tests/utils/safetycenter/Android.bp b/tests/utils/safetycenter/Android.bp
index fab8c8dde..11fd3951d 100644
--- a/tests/utils/safetycenter/Android.bp
+++ b/tests/utils/safetycenter/Android.bp
@@ -36,6 +36,7 @@ android_library {
"kotlinx-coroutines-android",
"safety-center-internal-data",
"safety-center-resources-lib",
+ "SettingsLibSettingsTheme",
// TODO(b/326414126): aconfig: support multi-container library
"com.android.permission.flags-aconfig-java",
],
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 7efbba7a0..66c5f46c4 100644
--- a/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
+++ b/tests/utils/safetycenter/java/com/android/safetycenter/testing/SafetyCenterFlags.kt
@@ -21,6 +21,7 @@ import android.Manifest.permission.WRITE_ALLOWLISTED_DEVICE_CONFIG
import android.Manifest.permission.WRITE_DEVICE_CONFIG
import android.annotation.TargetApi
import android.app.job.JobInfo
+import android.content.Context
import android.content.pm.PackageManager
import android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE
import android.provider.DeviceConfig
@@ -38,12 +39,16 @@ 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 com.android.settingslib.widget.SettingsThemeHelper
import java.time.Duration
import kotlin.reflect.KProperty
/** A class that facilitates working with Safety Center flags. */
object SafetyCenterFlags {
+ /** This is a hidden API constant within [DeviceConfig]. */
+ private const val NAMESPACE_SETTINGS_UI = "settings_ui"
+
/** Flag that determines whether Safety Center is enabled. */
private val isEnabledFlag =
Flag("safety_center_is_enabled", defaultValue = SdkLevel.isAtLeastU(), BooleanParser())
@@ -143,8 +148,7 @@ object SafetyCenterFlags {
)
/**
- * Flag that determines the time for which Safety Center will wait before starting dismissal of
- * resolved issue UI
+ * Flag that determines how long Safety Center will wait before hiding the resolved issue UI.
*/
private val hideResolveUiTransitionDelayFlag =
Flag(
@@ -154,6 +158,18 @@ object SafetyCenterFlags {
)
/**
+ * Flag that determines how long an expressive BannerMessagePreference will wait before hiding
+ * the resolved UI.
+ */
+ private val bannerMessagePrefHideResolvedContentTransitionDelayFlag =
+ Flag(
+ "banner_message_pref_hide_resolved_content_delay_millis",
+ defaultValue = Duration.ofMillis(400),
+ DurationParser(),
+ namespace = NAMESPACE_SETTINGS_UI,
+ )
+
+ /**
* Flag containing a comma delimited lists of source IDs that we won't track when deciding if a
* broadcast is completed. We still send broadcasts to (and handle API calls from) these sources
* as normal.
@@ -312,6 +328,7 @@ object SafetyCenterFlags {
resolveActionTimeoutFlag,
tempHiddenIssueResurfaceDelayFlag,
hideResolveUiTransitionDelayFlag,
+ bannerMessagePrefHideResolvedContentTransitionDelayFlag,
untrackedSourcesFlag,
resurfaceIssueMaxCountsFlag,
resurfaceIssueDelaysFlag,
@@ -357,9 +374,28 @@ object SafetyCenterFlags {
/** A property that allows getting and setting the [tempHiddenIssueResurfaceDelayFlag]. */
var tempHiddenIssueResurfaceDelay: Duration by tempHiddenIssueResurfaceDelayFlag
+ // TODO: b/379849464 - replace remaining usages and make this private
/** A property that allows getting and setting the [hideResolveUiTransitionDelayFlag]. */
var hideResolvedIssueUiTransitionDelay: Duration by hideResolveUiTransitionDelayFlag
+ /**
+ * A property that allows getting and setting the
+ * [bannerMessagePrefHideResolvedContentTransitionDelayFlag]
+ */
+ private var bannerMessagePrefHideResolvedContentTransitionDelay: Duration by
+ bannerMessagePrefHideResolvedContentTransitionDelayFlag
+
+ /**
+ * Sets the proper hide_resolved_issue_ui_transition_delay flag based on expressive design
+ * state.
+ */
+ fun setHideResolvedIssueUiTransitionDelay(context: Context, value: Duration) =
+ if (SettingsThemeHelper.isExpressiveTheme(context)) {
+ bannerMessagePrefHideResolvedContentTransitionDelay = value
+ } else {
+ hideResolvedIssueUiTransitionDelay = value
+ }
+
/** A property that allows getting and setting the [untrackedSourcesFlag]. */
var untrackedSources: Set<String> by untrackedSourcesFlag
@@ -396,14 +432,23 @@ object SafetyCenterFlags {
* This snapshot is only taken once and cached afterwards. [setup] must be called at least once
* prior to modifying any flag for the snapshot to be taken with the right values.
*/
- @Volatile lateinit var snapshot: Properties
+ @Volatile lateinit var snapshot: Map<String, Properties>
- private val lazySnapshot: Properties by lazy {
+ private val lazySnapshot: Map<String, Properties> by lazy {
callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
- DeviceConfig.getProperties(NAMESPACE_PRIVACY, *FLAGS.map { it.name }.toTypedArray())
+ mapOf(
+ NAMESPACE_PRIVACY to fetchPropertiesForNamespace(NAMESPACE_PRIVACY),
+ NAMESPACE_SETTINGS_UI to fetchPropertiesForNamespace(NAMESPACE_SETTINGS_UI),
+ )
}
}
+ private fun fetchPropertiesForNamespace(namespace: String) =
+ DeviceConfig.getProperties(
+ namespace,
+ *FLAGS.filter { it.namespace == namespace }.map { it.name }.toTypedArray(),
+ )
+
/**
* Takes a snapshot of all Safety Center flags and sets them up to their default values.
*
@@ -414,7 +459,7 @@ object SafetyCenterFlags {
fun setup() {
snapshot = lazySnapshot
FLAGS.filter { it.name != isEnabledFlag.name }
- .forEach { writeDeviceConfigProperty(it.name, it.defaultStringValue) }
+ .forEach { it.writeToDeviceConfig(it.defaultStringValue) }
}
/**
@@ -431,8 +476,8 @@ object SafetyCenterFlags {
FLAGS.filter { it.name != isEnabledFlag.name }
.forEach {
val key = it.name
- val value = snapshot.getString(key, /* defaultValue */ null)
- writeDeviceConfigProperty(key, value)
+ val value = snapshot[it.namespace]?.getString(key, /* defaultValue */ null)
+ it.writeToDeviceConfig(value)
}
}
@@ -442,8 +487,8 @@ object SafetyCenterFlags {
}
/** Returns the [isEnabledFlag] value of the Safety Center flags snapshot. */
- fun Properties.isSafetyCenterEnabled() =
- getBoolean(isEnabledFlag.name, isEnabledFlag.defaultValue)
+ fun Map<String, Properties>.isSafetyCenterEnabled(): Boolean =
+ this[NAMESPACE_PRIVACY]!!.getBoolean(isEnabledFlag.name, isEnabledFlag.defaultValue)
@TargetApi(UPSIDE_DOWN_CAKE)
private fun getAllRefreshTimeoutsMap(refreshTimeout: Duration): Map<Int, Duration> =
@@ -516,32 +561,32 @@ object SafetyCenterFlags {
.joinToString(entriesDelimiter)
}
- private class Flag<T>(val name: String, val defaultValue: T, private val parser: Parser<T>) {
+ private class Flag<T>(
+ val name: String,
+ val defaultValue: T,
+ private val parser: Parser<T>,
+ val namespace: String = NAMESPACE_PRIVACY,
+ ) {
val defaultStringValue = parser.toString(defaultValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>): T =
- readDeviceConfigProperty(name)?.let(parser::parseFromString) ?: defaultValue
+ readFromDeviceConfig(name)?.let(parser::parseFromString) ?: defaultValue
- operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
- writeDeviceConfigProperty(name, parser.toString(value))
- }
- }
+ private fun readFromDeviceConfig(name: String): String? =
+ callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
+ DeviceConfig.getProperty(namespace, name)
+ }
- private fun readDeviceConfigProperty(name: String): String? =
- callWithShellPermissionIdentity(READ_DEVICE_CONFIG) {
- DeviceConfig.getProperty(NAMESPACE_PRIVACY, name)
+ operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
+ writeToDeviceConfig(parser.toString(value))
}
- private fun writeDeviceConfigProperty(name: String, stringValue: String?) {
- callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
- val valueWasSet =
- DeviceConfig.setProperty(
- NAMESPACE_PRIVACY,
- name,
- stringValue, /* makeDefault */
- false,
- )
- require(valueWasSet) { "Could not set $name to: $stringValue" }
+ fun writeToDeviceConfig(stringValue: String?) {
+ callWithShellPermissionIdentity(WRITE_DEVICE_CONFIG, WRITE_ALLOWLISTED_DEVICE_CONFIG) {
+ val valueWasSet =
+ DeviceConfig.setProperty(namespace, name, stringValue, /* makeDefault */ false)
+ require(valueWasSet) { "Could not set $name to: $stringValue" }
+ }
}
}
}