summaryrefslogtreecommitdiff
path: root/PermissionController/src
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 /PermissionController/src
parent020ab32e977480109f5784d48008edff1946d091 (diff)
parent5098fc7aff57f4819be964c02920a1f815befb55 (diff)
Merge "Use BannerMessagePrefs on SC homepage when expressive design enabled." into main
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)