summaryrefslogtreecommitdiff
path: root/PermissionController/src
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 /PermissionController/src
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
Diffstat (limited to 'PermissionController/src')
-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
4 files changed, 223 insertions, 20 deletions
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)