summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Tyler Dewey <deweytyl@google.com> 2025-02-21 10:55:53 +0000
committer Tyler Dewey <deweytyl@google.com> 2025-02-21 15:55:18 +0000
commit5098fc7aff57f4819be964c02920a1f815befb55 (patch)
tree985514378fdf9e791897a018018686af877d8ca2
parentf4c9bdb9118400a98c95f119a5bc8f896e489c60 (diff)
Use BannerMessagePrefs on SC homepage when expressive design enabled.
This affects SC homepage and quick settings. Will migrate subpages separately when the expressive collapsible groups are ready. Preferences are added directly to the page and are not collapsed for now. Test: atest SafetyCenterActivityFunctionalTestCases CtsSafetyCenterTestCases SafetyCenterFunctionalTestCases Fixes: 379849463 Flag: com.android.settingslib.widget.theme.flags.is_expressive_design_enabled Relnote: Flag protected Safety Center UI updates Change-Id: I2c931ccebfe1db869ff63d5e81bb10668623f04e
-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" }
+ }
}
}
}