summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--PermissionController/res/layout-v33/preference_static_entry.xml28
-rw-r--r--PermissionController/res/values-v33/dimens.xml2
-rw-r--r--PermissionController/res/values-v33/styles.xml31
-rw-r--r--PermissionController/res/values/overlayable.xml87
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt2
-rw-r--r--PermissionController/src/com/android/permissioncontroller/safetycenter/ui/StaticSafetyEntryPreference.java2
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterDataTracker.java607
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterRepository.java600
-rw-r--r--service/java/com/android/safetycenter/SafetyCenterService.java54
9 files changed, 665 insertions, 748 deletions
diff --git a/PermissionController/res/layout-v33/preference_static_entry.xml b/PermissionController/res/layout-v33/preference_static_entry.xml
deleted file mode 100644
index a76e880e6..000000000
--- a/PermissionController/res/layout-v33/preference_static_entry.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!--
- ~ Copyright (C) 2022 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
- -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- style="@style/SafetyCenterStaticEntry">
-
- <TextView android:id="@android:id/title"
- style="@style/SafetyCenterStaticEntryTitle" />
-
- <TextView android:id="@android:id/summary"
- style="@style/SafetyCenterStaticEntrySummary" />
-
-</LinearLayout> \ No newline at end of file
diff --git a/PermissionController/res/values-v33/dimens.xml b/PermissionController/res/values-v33/dimens.xml
index 61328d7db..e94eac6d8 100644
--- a/PermissionController/res/values-v33/dimens.xml
+++ b/PermissionController/res/values-v33/dimens.xml
@@ -28,6 +28,7 @@
<dimen name="sc_card_margin">@dimen/sc_spacing_xxxsmall</dimen>
<dimen name="sc_list_margin">@dimen/sc_spacing_large</dimen>
+ <dimen name="sc_list_margin_top">@dimen/sc_spacing_small</dimen>
<dimen name="sc_action_button_list_margin">@dimen/sc_spacing_large</dimen>
<dimen name="sc_top_action_button_margin">@dimen/sc_spacing_xxxlarge</dimen>
<dimen name="sc_entry_padding_end">@dimen/sc_spacing_xxxlarge</dimen>
@@ -38,7 +39,6 @@
<dimen name="sc_card_margin_bottom">@dimen/sc_spacing_xxxlarge</dimen>
<dimen name="sc_icon_button_touch_target_size">48dp</dimen>
- <dimen name="sc_indicator_expand_button_background">24dp</dimen>
<dimen name="sc_button_corner_radius">12dp</dimen>
<dimen name="sc_card_corner_radius_large">28dp</dimen>
<dimen name="sc_card_corner_radius_medium">20dp</dimen>
diff --git a/PermissionController/res/values-v33/styles.xml b/PermissionController/res/values-v33/styles.xml
index e395c9777..afb9f0db5 100644
--- a/PermissionController/res/values-v33/styles.xml
+++ b/PermissionController/res/values-v33/styles.xml
@@ -764,35 +764,4 @@
<item name="android:gravity">center_vertical</item>
<item name="android:orientation">horizontal</item>
</style>
-
- <style name="SafetyCenterStaticEntry"
- parent="android:Widget.DeviceDefault">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:paddingStart">@dimen/sc_spacing_xxxlarge</item>
- <item name="android:paddingEnd">@dimen/sc_spacing_xxxlarge</item>
- <item name="android:paddingTop">@dimen/sc_spacing_large</item>
- <item name="android:paddingBottom">@dimen/sc_spacing_large</item>
- <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
- <item name="android:gravity">center_vertical</item>
- <item name="android:orientation">vertical</item>
- </style>
-
- <style name="SafetyCenterStaticEntryTitle"
- parent="android:Widget.DeviceDefault">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:textAppearance">@style/TextAppearance.SafetyCenter.Headline.Entry</item>
- <item name="android:ellipsize">end</item>
- <item name="android:maxLines">4</item>
- </style>
-
- <style name="SafetyCenterStaticEntrySummary"
- parent="android:Widget.DeviceDefault">
- <item name="android:layout_width">wrap_content</item>
- <item name="android:layout_height">wrap_content</item>
- <item name="android:textAppearance">@style/TextAppearance.SafetyCenter.Body</item>
- <item name="android:ellipsize">end</item>
- <item name="android:maxLines">4</item>
- </style>
</resources>
diff --git a/PermissionController/res/values/overlayable.xml b/PermissionController/res/values/overlayable.xml
index c448d88cf..6e7973fb1 100644
--- a/PermissionController/res/values/overlayable.xml
+++ b/PermissionController/res/values/overlayable.xml
@@ -321,6 +321,7 @@
<item type="dimen" name="sc_spacing_xxxlarge" />
<item type="dimen" name="sc_card_margin" />
<item type="dimen" name="sc_list_margin" />
+ <item type="dimen" name="sc_list_margin_top" />
<item type="dimen" name="sc_action_button_list_margin" />
<item type="dimen" name="sc_top_action_button_margin" />
<item type="dimen" name="sc_entry_padding_end" />
@@ -329,8 +330,10 @@
<item type="dimen" name="sc_entry_group_collapsed_padding_top" />
<item type="dimen" name="sc_entry_group_collapsed_padding_bottom" />
<item type="dimen" name="sc_card_margin_bottom" />
- <item type="dimen" name="sc_icon_button_touch_target_size" />
- <item type="dimen" name="sc_indicator_expand_button_background" />
+ <item type="dimen" name="sc_button_corner_radius" />
+ <item type="dimen" name="sc_card_corner_radius_large" />
+ <item type="dimen" name="sc_card_corner_radius_medium" />
+ <item type="dimen" name="sc_card_corner_radius_xsmall" />
<item type="color" name="safety_center_button_info" />
<item type="color" name="safety_center_button_recommend" />
@@ -423,86 +426,6 @@
<item type="style" name="TextAppearance.SafetyCenter.Medium" />
<item type="style" name="TextAppearance.SafetyCenter.ActionButton" />
<item type="style" name="TextAppearance.SafetyCenter.ActionButton.Secondary" />
-
- <item type="style" name="SafetyCenterCard" />
- <item type="style" name="SafetyCenterActionButton" />
- <item type="style" name="SafetyCenterActionButton.Secondary" />
- <item type="style" name="SafetyCenterCard.Status" />
- <item type="style" name="SafetyCenterStatusImage" />
- <item type="style" name="SafetyCenterStatusTitleAndSummaryContainer" />
- <item type="style" name="SafetyCenterStatusTitle" />
- <item type="style" name="SafetyCenterStatusSummary" />
- <item type="style" name="SafetyCenterStatusButton" />
- <item type="style" name="SafetyCenterStatusButton.ReviewSettings" />
- <item type="style" name="SafetyCenterStatusButton.Rescan" />
- <item type="style" name="SafetyCenterStatusButton.PendingActionsRescan" />
- <item type="style" name="SafetyCenterStatusSafetyProtectionView" />
- <item type="style" name="SafetyCenterCard.Issue" />
- <item type="style" name="SafetyCenterIssueDismiss" />
- <item type="style" name="SafetyCenterIssueTitle" />
- <item type="style" name="SafetyCenterIssueSubtitle" />
- <item type="style" name="SafetyCenterIssueSummary" />
- <item type="style" name="SafetyCenterIssueActionButtonList" />
- <item type="style" name="SafetyCenterIssueSafetyProtectionSection" />
- <item type="style" name="SafetyCenterIssueCardResolvedImage" />
- <item type="style" name="SafetyCenterIssueCardResolvedTitle" />
- <item type="style" name="SafetyCenterMoreIssues" />
- <item type="style" name="SafetyCenterMoreIssuesTitle" />
- <item type="style" name="SafetyCenterMoreIssuesIcon" />
- <item type="style" name="SafetyCenterMoreIssuesWidgetFrame" />
- <item type="style" name="SafetyCenterMoreIssuesWidget" />
- <item type="style" name="SafetyCenterMoreIssuesWidgetTitle" />
- <item type="style" name="SafetyCenterMoreIssuesWidgetIcon" />
- <item type="style" name="SafetyCenterEntry" />
- <item type="style" name="SafetyCenterEntryDivider" />
- <item type="style" name="SafetyCenterEntryWidgetFrame" />
- <item type="style" name="SafetyCenterEntryIconFrame" />
- <item type="style" name="SafetyCenterEntryIcon" />
- <item type="style" name="SafetyCenterEntryEmptySpace" />
- <item type="style" name="SafetyCenterEntryTextContainer" />
- <item type="style" name="SafetyCenterEntryTitle" />
- <item type="style" name="SafetyCenterEntrySummary" />
- <item type="style" name="SafetyCenterEntryIconAction" />
- <item type="style" name="SafetyCenterGroup" />
- <item type="style" name="SafetyCenterGroupHeader" />
- <item type="style" name="SafetyCenterCollapsedGroupHeader" />
- <item type="style" name="SafetyCenterExpandedGroupHeader" />
- <item type="style" name="SafetyCenterExpandedGroupTitle" />
- <item type="style" name="SafetyCenterGroupEntries" />
- <item type="style" name="SafetyCenterGroupWidgetFrame" />
- <item type="style" name="SafetyCenterExpandedGroupIcon" />
- <item type="style" name="SafetyCenterNoLabelPreferenceCategory" />
- <item type="style" name="SafetyCenterGroupEntry" />
- <item type="style" name="SafetyCenterStaticEntry" />
- <item type="style" name="SafetyCenterStaticEntryTitle" />
- <item type="style" name="SafetyCenterStaticEntrySummary" />
-
- <item type="style" name="SafetyCenterQsContainer" />
- <item type="style" name="SafetyCenterLinkText" />
- <item type="style" name="SafetyCenterQsCloseButton" />
- <item type="style" name="SafetyCenterQsSectionTitle" />
- <item type="style" name="SafetyCenterQsPermissionUsage" />
- <item type="style" name="SafetyCenterQsPreferences" />
- <item type="style" name="SafetyCenterQsToggleContainer" />
- <item type="style" name="SafetyCenterQsToggleContainer.Top" />
- <item type="style" name="SafetyCenterQsToggleContainer.Bottom" />
- <item type="style" name="SafetyCenterQsToggleButton" />
- <item type="style" name="SafetyCenterQsToggleButton.Start" />
- <item type="style" name="SafetyCenterQsToggleButton.End" />
- <item type="style" name="SafetyCenterQsToggleTextContainer" />
- <item type="style" name="SafetyCenterQsToggleText" />
- <item type="style" name="SafetyCenterQsToggleText.Title" />
- <item type="style" name="SafetyCenterQsToggleText.Subtitle" />
- <item type="style" name="SafetyCenterQsToggleArrow" />
- <item type="style" name="SafetyCenterQsToggleIcon" />
- <item type="style" name="SafetyCenterIndicatorCardView" />
- <item type="style" name="SafetyCenterIndicatorImageView" />
- <item type="style" name="SafetyCenterIndicatorTitleText" />
- <item type="style" name="SafetyCenterIndicatorLabelText" />
- <item type="style" name="SafetyCenterIndicatorExpandView" />
- <item type="style" name="SafetyCenterIndicatorActionButton" />
- <item type="style" name="SafetyCenterIndicatorSecondaryActionButton" />
- <item type="style" name="SafetyCenterIndicatorForeground" />
</policy>
</overlayable>
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
index 98848aa74..01e8f8d15 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/PositionInCardList.kt
@@ -54,7 +54,7 @@ internal enum class PositionInCardList(val backgroundDrawableResId: Int) {
CARD_START, CARD_START_END, CARD_START_LIST_END ->
context.resources.getDimensionPixelSize(R.dimen.sc_card_margin)
LIST_START, LIST_START_CARD_END, LIST_START_END ->
- context.resources.getDimensionPixelSize(R.dimen.sc_list_margin)
+ context.resources.getDimensionPixelSize(R.dimen.sc_list_margin_top)
else -> 0
}
diff --git a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/StaticSafetyEntryPreference.java b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/StaticSafetyEntryPreference.java
index 6e6583ae0..d51f3714a 100644
--- a/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/StaticSafetyEntryPreference.java
+++ b/PermissionController/src/com/android/permissioncontroller/safetycenter/ui/StaticSafetyEntryPreference.java
@@ -28,7 +28,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.preference.Preference;
-import com.android.permissioncontroller.R;
import com.android.permissioncontroller.safetycenter.ui.model.SafetyCenterViewModel;
/** A preference which displays a visual representation of a {@link SafetyCenterStaticEntry}. */
@@ -48,7 +47,6 @@ public class StaticSafetyEntryPreference extends Preference implements Comparabl
super(context);
mEntry = entry;
mViewModel = viewModel;
- setLayoutResource(R.layout.preference_static_entry);
setTitle(entry.getTitle());
setSummary(entry.getSummary());
if (entry.getPendingIntent() != null) {
diff --git a/service/java/com/android/safetycenter/SafetyCenterDataTracker.java b/service/java/com/android/safetycenter/SafetyCenterDataTracker.java
index 5d72441a9..5c088b5d0 100644
--- a/service/java/com/android/safetycenter/SafetyCenterDataTracker.java
+++ b/service/java/com/android/safetycenter/SafetyCenterDataTracker.java
@@ -18,20 +18,15 @@ package com.android.safetycenter;
import static android.os.Build.VERSION_CODES.TIRAMISU;
-import static com.android.safetycenter.WestworldLogger.toSystemEventResult;
-import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
-
import static java.util.Collections.emptyList;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
-import android.content.Context;
import android.icu.text.ListFormatter;
import android.icu.text.MessageFormat;
import android.icu.util.ULocale;
-import android.os.SystemClock;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
@@ -40,9 +35,7 @@ import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterStaticEntry;
import android.safetycenter.SafetyCenterStaticEntryGroup;
import android.safetycenter.SafetyCenterStatus;
-import android.safetycenter.SafetyEvent;
import android.safetycenter.SafetySourceData;
-import android.safetycenter.SafetySourceErrorDetails;
import android.safetycenter.SafetySourceIssue;
import android.safetycenter.SafetySourceStatus;
import android.safetycenter.config.SafetyCenterConfig;
@@ -50,41 +43,35 @@ import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
import android.text.TextUtils;
import android.util.ArrayMap;
-import android.util.ArraySet;
import android.util.Log;
import android.util.StatsEvent;
import androidx.annotation.RequiresApi;
import com.android.permission.PermissionStatsLog;
-import com.android.permission.util.UserUtils;
-import com.android.safetycenter.SafetyCenterConfigReader.ExternalSafetySource;
-import com.android.safetycenter.WestworldLogger.SystemEventResult;
import com.android.safetycenter.internaldata.SafetyCenterEntryGroupId;
import com.android.safetycenter.internaldata.SafetyCenterEntryId;
import com.android.safetycenter.internaldata.SafetyCenterIds;
import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
import com.android.safetycenter.internaldata.SafetyCenterIssueId;
import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
-import com.android.safetycenter.persistence.PersistedSafetyCenterIssue;
import com.android.safetycenter.resources.SafetyCenterResourcesContext;
-import java.io.PrintWriter;
-import java.time.Duration;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
-import java.util.Objects;
import javax.annotation.concurrent.NotThreadSafe;
/**
- * A class that keeps track of all the {@link SafetySourceData} set by safety sources, and
- * aggregates them into a {@link SafetyCenterData} object to be used by PermissionController.
+ * Aggregates {@link SafetySourceData} into a {@link SafetyCenterData} object to be used by Safety
+ * Center listeners including PermissionController.
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
+// TODO(b/250812300): Change the name of this class to reflect the "aggregating them into
+// SafetyCenterData object" responsibility only.
@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterDataTracker {
@@ -97,195 +84,29 @@ final class SafetyCenterDataTracker {
SAFETY_CENTER_ISSUES_BY_SEVERITY_DESCENDING =
new SafetyCenterIssuesBySeverityDescending();
- private final ArrayMap<SafetySourceKey, SafetySourceData> mSafetySourceDataForKey =
- new ArrayMap<>();
-
- private final ArraySet<SafetySourceKey> mSafetySourceErrors = new ArraySet<>();
-
- private final ArrayMap<SafetyCenterIssueActionId, Long> mSafetyCenterIssueActionsInFlight =
- new ArrayMap<>();
-
- @NonNull private final Context mContext;
@NonNull private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
@NonNull private final WestworldLogger mWestworldLogger;
@NonNull private final PendingIntentFactory mPendingIntentFactory;
@NonNull private final SafetyCenterIssueCache mSafetyCenterIssueCache;
+ @NonNull private final SafetyCenterRepository mSafetyCenterRepository;
SafetyCenterDataTracker(
- @NonNull Context context,
@NonNull SafetyCenterResourcesContext safetyCenterResourcesContext,
@NonNull SafetyCenterConfigReader safetyCenterConfigReader,
@NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker,
@NonNull WestworldLogger westworldLogger,
@NonNull PendingIntentFactory pendingIntentFactory,
- @NonNull SafetyCenterIssueCache safetyCenterIssueCache) {
- mContext = context;
+ @NonNull SafetyCenterIssueCache safetyCenterIssueCache,
+ @NonNull SafetyCenterRepository safetyCenterRepository) {
mSafetyCenterResourcesContext = safetyCenterResourcesContext;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mWestworldLogger = westworldLogger;
mPendingIntentFactory = pendingIntentFactory;
mSafetyCenterIssueCache = safetyCenterIssueCache;
- }
-
- /**
- * Returns whether the Safety Center issue cache has been modified since the last time a
- * snapshot was taken.
- */
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- boolean isSafetyCenterIssueCacheDirty() {
- return mSafetyCenterIssueCache.isDirty();
- }
-
- /**
- * Takes a snapshot of the Safety Center issue cache that should be written to persistent
- * storage.
- *
- * <p>This method will reset the value reported by {@link #isSafetyCenterIssueCacheDirty} to
- * {@code false}.
- */
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- @NonNull
- List<PersistedSafetyCenterIssue> snapshotSafetyCenterIssueCache() {
- return mSafetyCenterIssueCache.snapshot();
- }
-
- /**
- * Replaces the Safety Center issue cache with the given list of issues.
- *
- * <p>This method may modify the Safety Center issue cache and change the value reported by
- * {@link #isSafetyCenterIssueCacheDirty} to {@code true}.
- */
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- void loadSafetyCenterIssueCache(
- @NonNull List<PersistedSafetyCenterIssue> persistedSafetyCenterIssues) {
- mSafetyCenterIssueCache.load(persistedSafetyCenterIssues);
- }
-
- /**
- * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link
- * SafetyEvent}, {@code packageName} and {@code userId}, and returns whether there was a change
- * to the underlying {@link SafetyCenterData}.
- *
- * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
- * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected; or the {@link
- * SafetySourceData} does not respect all constraints defined in the config.
- *
- * <p>Setting a {@code null} {@link SafetySourceData} evicts the current {@link
- * SafetySourceData} entry and clears the Safety Center issue cache for the source.
- *
- * <p>This method may modify the Safety Center issue cache and change the value reported by
- * {@link #isSafetyCenterIssueCacheDirty} to {@code true}.
- */
- boolean setSafetySourceData(
- @Nullable SafetySourceData safetySourceData,
- @NonNull String safetySourceId,
- @NonNull SafetyEvent safetyEvent,
- @NonNull String packageName,
- @UserIdInt int userId) {
- if (!validateRequest(safetySourceData, safetySourceId, packageName, userId)) {
- return false;
- }
- boolean safetyEventChangedSafetyCenterData =
- processSafetyEvent(safetySourceId, safetyEvent, userId, false);
-
- SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
- boolean removingSafetySourceErrorChangedSafetyCenterData = mSafetySourceErrors.remove(key);
- SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key);
- if (Objects.equals(safetySourceData, existingSafetySourceData)) {
- return safetyEventChangedSafetyCenterData
- || removingSafetySourceErrorChangedSafetyCenterData;
- }
-
- ArraySet<String> issueIds = new ArraySet<>();
- if (safetySourceData == null) {
- mSafetySourceDataForKey.remove(key);
- } else {
- mSafetySourceDataForKey.put(key, safetySourceData);
- for (int i = 0; i < safetySourceData.getIssues().size(); i++) {
- issueIds.add(safetySourceData.getIssues().get(i).getId());
- }
- }
- mSafetyCenterIssueCache.updateIssuesForSource(issueIds, safetySourceId, userId);
-
- return true;
- }
-
- /**
- * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
- * the given {@code safetySourceId}, {@code packageName} and {@code userId}.
- *
- * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
- * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
- *
- * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
- * {@link #setSafetySourceData} with a {@code null} value.
- */
- @Nullable
- SafetySourceData getSafetySourceData(
- @NonNull String safetySourceId, @NonNull String packageName, @UserIdInt int userId) {
- if (!validateRequest(null, safetySourceId, packageName, userId)) {
- return null;
- }
- return mSafetySourceDataForKey.get(SafetySourceKey.of(safetySourceId, userId));
- }
-
- /**
- * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and
- * {@code userId}, and returns whether there was a change to the underlying {@link
- * SafetyCenterData}.
- *
- * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
- * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
- */
- boolean reportSafetySourceError(
- @NonNull SafetySourceErrorDetails safetySourceErrorDetails,
- @NonNull String safetySourceId,
- @NonNull String packageName,
- @UserIdInt int userId) {
- if (!validateRequest(null, safetySourceId, packageName, userId)) {
- return false;
- }
- SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
- Log.w(TAG, "Error reported from source: " + safetySourceId + ", for event: " + safetyEvent);
-
- boolean safetyEventChangedSafetyCenterData =
- processSafetyEvent(safetySourceId, safetyEvent, userId, true);
- int safetyEventType = safetyEvent.getType();
- if (safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
- || safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) {
- return safetyEventChangedSafetyCenterData;
- }
-
- SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
- boolean safetySourceErrorChangedSafetyCenterData = setSafetySourceError(key);
- return safetyEventChangedSafetyCenterData || safetySourceErrorChangedSafetyCenterData;
- }
-
- /** Marks the given {@link SafetySourceKey} as having errored-out. */
- boolean setSafetySourceError(@NonNull SafetySourceKey safetySourceKey) {
- boolean removingSafetySourceDataChangedSafetyCenterData =
- mSafetySourceDataForKey.remove(safetySourceKey) != null;
- boolean addingSafetySourceErrorChangedSafetyCenterData =
- mSafetySourceErrors.add(safetySourceKey);
- return removingSafetySourceDataChangedSafetyCenterData
- || addingSafetySourceErrorChangedSafetyCenterData;
- }
-
- /**
- * Clears all safety source errors received so far for the given {@link UserProfileGroup}, this
- * is useful e.g. when starting a new broadcast.
- */
- void clearSafetySourceErrors(@NonNull UserProfileGroup userProfileGroup) {
- // Loop in reverse index order to be able to remove entries while iterating.
- for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) {
- SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i);
- if (userProfileGroup.contains(sourceKey.getUserId())) {
- mSafetySourceErrors.removeAt(i);
- }
- }
+ mSafetyCenterRepository = safetyCenterRepository;
}
/**
@@ -302,136 +123,6 @@ final class SafetyCenterDataTracker {
mSafetyCenterConfigReader.getSafetySourcesGroups(), packageName, userProfileGroup);
}
- /** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */
- void markSafetyCenterIssueActionInFlight(
- @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
- mSafetyCenterIssueActionsInFlight.put(
- safetyCenterIssueActionId, SystemClock.elapsedRealtime());
- }
-
- /**
- * Unmarks the given {@link SafetyCenterIssueActionId} as in-flight, logs that event to
- * Westworld with the given {@code result} value, and returns {@code true} if the underlying
- * {@link SafetyCenterData} changed.
- */
- boolean unmarkSafetyCenterIssueActionInFlight(
- @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId,
- @SystemEventResult int result) {
- Long startElapsedMillis =
- mSafetyCenterIssueActionsInFlight.remove(safetyCenterIssueActionId);
- if (startElapsedMillis == null) {
- Log.w(
- TAG,
- "Attempt to unmark unknown in-flight action: "
- + toUserFriendlyString(safetyCenterIssueActionId));
- return false;
- }
-
- SafetyCenterIssueKey issueKey = safetyCenterIssueActionId.getSafetyCenterIssueKey();
- SafetySourceIssue issue = getSafetySourceIssue(issueKey);
- String issueTypeId = issue == null ? null : issue.getIssueTypeId();
- Duration duration = Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis);
-
- mWestworldLogger.writeInlineActionSystemEvent(
- issueKey.getSafetySourceId(), issueKey.getUserId(), issueTypeId, duration, result);
-
- if (issue == null || getSafetySourceIssueAction(safetyCenterIssueActionId) == null) {
- Log.w(
- TAG,
- "Attempt to unmark in-flight action for a non-existent issue or action: "
- + toUserFriendlyString(safetyCenterIssueActionId));
- return false;
- }
-
- return true;
- }
-
- /**
- * Dismisses the given {@link SafetyCenterIssueKey}.
- *
- * <p>This method may modify the Safety Center issue cache and change the value reported by
- * {@link #isSafetyCenterIssueCacheDirty} to {@code true}.
- */
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- void dismissSafetyCenterIssue(@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
- mSafetyCenterIssueCache.dismissIssue(safetyCenterIssueKey);
- }
-
- /**
- * Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueKey}.
- *
- * <p>Returns {@code null} if there is no such {@link SafetySourceIssue}, or if it's been
- * dismissed.
- */
- @Nullable
- SafetySourceIssue getSafetySourceIssue(@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
- SafetySourceKey key =
- SafetySourceKey.of(
- safetyCenterIssueKey.getSafetySourceId(), safetyCenterIssueKey.getUserId());
- SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
- if (safetySourceData == null) {
- return null;
- }
- List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
-
- SafetySourceIssue targetIssue = null;
- for (int i = 0; i < safetySourceIssues.size(); i++) {
- SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
-
- if (safetyCenterIssueKey.getSafetySourceIssueId().equals(safetySourceIssue.getId())) {
- targetIssue = safetySourceIssue;
- break;
- }
- }
- if (targetIssue == null) {
- return null;
- }
-
- if (mSafetyCenterIssueCache.isIssueDismissed(
- safetyCenterIssueKey, targetIssue.getSeverityLevel())) {
- return null;
- }
-
- return targetIssue;
- }
-
- /**
- * Returns the {@link SafetySourceIssue.Action} associated with the given {@link
- * SafetyCenterIssueActionId}.
- *
- * <p>Returns {@code null} if there is no associated {@link SafetySourceIssue}, or if it's been
- * dismissed.
- *
- * <p>Returns {@code null} if the {@link SafetySourceIssue.Action} is currently in flight.
- */
- @Nullable
- SafetySourceIssue.Action getSafetySourceIssueAction(
- @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
- SafetySourceIssue safetySourceIssue =
- getSafetySourceIssue(safetyCenterIssueActionId.getSafetyCenterIssueKey());
-
- if (safetySourceIssue == null) {
- return null;
- }
-
- if (isInFlight(safetyCenterIssueActionId)) {
- return null;
- }
-
- List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
- for (int i = 0; i < safetySourceIssueActions.size(); i++) {
- SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
-
- if (safetyCenterIssueActionId
- .getSafetySourceIssueActionId()
- .equals(safetySourceIssueAction.getId())) {
- return safetySourceIssueAction;
- }
- }
-
- return null;
- }
-
/**
* Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
*/
@@ -447,93 +138,6 @@ final class SafetyCenterDataTracker {
}
/**
- * Clears all the {@link SafetySourceData} and errors, metadata associated with {@link
- * SafetyCenterIssueKey}s, in flight {@link SafetyCenterIssueActionId} and any refresh in
- * progress so far, for all users.
- *
- * <p>This method will modify the Safety Center issue cache and change the value reported by
- * {@link #isSafetyCenterIssueCacheDirty} to {@code true}.
- */
- void clear() {
- mSafetySourceDataForKey.clear();
- mSafetySourceErrors.clear();
-
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- mSafetyCenterIssueCache.clear();
-
- mSafetyCenterIssueActionsInFlight.clear();
- }
-
- /**
- * Clears all the {@link SafetySourceData}, metadata associated with {@link
- * SafetyCenterIssueKey}s, in flight {@link SafetyCenterIssueActionId} and any refresh in
- * progress so far, for the given user.
- *
- * <p>This method may modify the Safety Center issue cache and change the value reported by
- * {@link #isSafetyCenterIssueCacheDirty} to {@code true}.
- */
- void clearForUser(@UserIdInt int userId) {
- // Loop in reverse index order to be able to remove entries while iterating.
- for (int i = mSafetySourceDataForKey.size() - 1; i >= 0; i--) {
- SafetySourceKey sourceKey = mSafetySourceDataForKey.keyAt(i);
- if (sourceKey.getUserId() == userId) {
- mSafetySourceDataForKey.removeAt(i);
- }
- }
- // Loop in reverse index order to be able to remove entries while iterating.
- for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) {
- SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i);
- if (sourceKey.getUserId() == userId) {
- mSafetySourceErrors.removeAt(i);
- }
- }
-
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- mSafetyCenterIssueCache.clearForUser(userId);
-
- // Loop in reverse index order to be able to remove entries while iterating.
- for (int i = mSafetyCenterIssueActionsInFlight.size() - 1; i >= 0; i--) {
- SafetyCenterIssueActionId issueActionId = mSafetyCenterIssueActionsInFlight.keyAt(i);
- if (issueActionId.getSafetyCenterIssueKey().getUserId() == userId) {
- mSafetyCenterIssueActionsInFlight.removeAt(i);
- }
- }
- }
-
- /** Dumps state for debugging purposes. */
- void dump(@NonNull PrintWriter fout) {
- int dataCount = mSafetySourceDataForKey.size();
- fout.println("SOURCE DATA (" + dataCount + ")");
- for (int i = 0; i < dataCount; i++) {
- SafetySourceKey key = mSafetySourceDataForKey.keyAt(i);
- SafetySourceData data = mSafetySourceDataForKey.valueAt(i);
- fout.println("\t[" + i + "] " + key + " -> " + data);
- }
- fout.println();
-
- int errorCount = mSafetySourceErrors.size();
- fout.println("SOURCE ERRORS (" + errorCount + ")");
- for (int i = 0; i < errorCount; i++) {
- SafetySourceKey key = mSafetySourceErrors.valueAt(i);
- fout.println("\t[" + i + "] " + key);
- }
- fout.println();
-
- // TODO(b/249950069): Consider removing issue cache APIs from SafetyCenterDataTracker
- mSafetyCenterIssueCache.dump(fout);
-
- int actionInFlightCount = mSafetyCenterIssueActionsInFlight.size();
- fout.println("ACTIONS IN FLIGHT (" + actionInFlightCount + ")");
- for (int i = 0; i < actionInFlightCount; i++) {
- SafetyCenterIssueActionId id = mSafetyCenterIssueActionsInFlight.keyAt(i);
- long startElapsedMillis = mSafetyCenterIssueActionsInFlight.valueAt(i);
- long durationMillis = SystemClock.elapsedRealtime() - startElapsedMillis;
- fout.println("\t[" + i + "] " + id + "(duration=" + durationMillis + "ms)");
- }
- fout.println();
- }
-
- /**
* Pulls the {@link PermissionStatsLog#SAFETY_STATE} atom and writes all relevant {@link
* PermissionStatsLog#SAFETY_SOURCE_STATE_COLLECTED} atoms for the given {@link
* UserProfileGroup}.
@@ -594,7 +198,7 @@ final class SafetyCenterDataTracker {
private void writeSafetySourceStateCollectedAtom(
@NonNull String safetySourceId, @UserIdInt int userId, boolean isUserManaged) {
SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
- SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
+ SafetySourceData safetySourceData = mSafetyCenterRepository.getSafetySourceData(key);
SafetySourceStatus safetySourceStatus = getSafetySourceStatus(safetySourceData);
List<SafetySourceIssue> safetySourceIssues =
safetySourceData == null ? emptyList() : safetySourceData.getIssues();
@@ -631,177 +235,6 @@ final class SafetyCenterDataTracker {
dismissedIssuesCount);
}
- private boolean isInFlight(@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
- return mSafetyCenterIssueActionsInFlight.containsKey(safetyCenterIssueActionId);
- }
-
- /**
- * Checks if a request to the SafetyCenter is valid, and returns whether the request should be
- * processed.
- */
- private boolean validateRequest(
- @Nullable SafetySourceData safetySourceData,
- @NonNull String safetySourceId,
- @NonNull String packageName,
- @UserIdInt int userId) {
- ExternalSafetySource externalSafetySource =
- mSafetyCenterConfigReader.getExternalSafetySource(safetySourceId);
- if (externalSafetySource == null) {
- throw new IllegalArgumentException("Unexpected safety source: " + safetySourceId);
- }
-
- SafetySource safetySource = externalSafetySource.getSafetySource();
-
- // TODO(b/222330089): Security: check certs?
- if (!packageName.equals(safetySource.getPackageName())) {
- throw new IllegalArgumentException(
- "Unexpected package name: "
- + packageName
- + ", for safety source: "
- + safetySourceId);
- }
-
- // TODO(b/222327845): Security: check package is installed for user?
-
- if (UserUtils.isManagedProfile(userId, mContext)
- && !SafetySources.supportsManagedProfiles(safetySource)) {
- throw new IllegalArgumentException(
- "Unexpected managed profile request for safety source: " + safetySourceId);
- }
-
- boolean retrievingOrClearingData = safetySourceData == null;
- if (retrievingOrClearingData) {
- return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
- }
-
- SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
-
- if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY
- && safetySourceStatus != null) {
- throw new IllegalArgumentException(
- "Unexpected status for issue only safety source: " + safetySourceId);
- }
-
- if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
- && safetySourceStatus == null) {
- throw new IllegalArgumentException(
- "Missing status for dynamic safety source: " + safetySourceId);
- }
-
- if (safetySourceStatus != null) {
- int sourceSeverityLevel = safetySourceStatus.getSeverityLevel();
-
- if (externalSafetySource.hasEntryInRigidGroup()
- && sourceSeverityLevel != SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED) {
- throw new IllegalArgumentException(
- "Safety source: "
- + safetySourceId
- + " is in a rigid group but specified a severity level: "
- + sourceSeverityLevel);
- }
-
- int maxSourceSeverityLevel =
- Math.max(
- SafetySourceData.SEVERITY_LEVEL_INFORMATION,
- safetySource.getMaxSeverityLevel());
-
- if (sourceSeverityLevel > maxSourceSeverityLevel) {
- throw new IllegalArgumentException(
- "Unexpected severity level: "
- + sourceSeverityLevel
- + ", for safety source: "
- + safetySourceId);
- }
- }
-
- List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
-
- for (int i = 0; i < safetySourceIssues.size(); i++) {
- SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
- int issueSeverityLevel = safetySourceIssue.getSeverityLevel();
- if (issueSeverityLevel > safetySource.getMaxSeverityLevel()) {
- throw new IllegalArgumentException(
- "Unexpected severity level: "
- + issueSeverityLevel
- + ", for issue in safety source: "
- + safetySourceId);
- }
-
- int issueCategory = safetySourceIssue.getIssueCategory();
- if (!SafetyCenterFlags.isIssueCategoryAllowedForSource(issueCategory, safetySourceId)) {
- throw new IllegalArgumentException(
- "Unexpected issue category: "
- + issueCategory
- + ", for issue in safety source: "
- + safetySourceId);
- }
- }
-
- return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
- }
-
- private boolean processSafetyEvent(
- @NonNull String safetySourceId,
- @NonNull SafetyEvent safetyEvent,
- @UserIdInt int userId,
- boolean isError) {
- int type = safetyEvent.getType();
- switch (type) {
- case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
- String refreshBroadcastId = safetyEvent.getRefreshBroadcastId();
- if (refreshBroadcastId == null) {
- Log.w(
- TAG,
- "Received safety event of type "
- + safetyEvent.getType()
- + " without a refresh broadcast id");
- return false;
- }
- return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted(
- refreshBroadcastId, safetySourceId, userId, !isError);
- case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
- case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
- String safetySourceIssueId = safetyEvent.getSafetySourceIssueId();
- if (safetySourceIssueId == null) {
- Log.w(
- TAG,
- "Received safety event of type "
- + safetyEvent.getType()
- + " without a safety source issue id");
- return false;
- }
- String safetySourceIssueActionId = safetyEvent.getSafetySourceIssueActionId();
- if (safetySourceIssueActionId == null) {
- Log.w(
- TAG,
- "Received safety event of type "
- + safetyEvent.getType()
- + " without a safety source issue action id");
- return false;
- }
- SafetyCenterIssueKey safetyCenterIssueKey =
- SafetyCenterIssueKey.newBuilder()
- .setSafetySourceId(safetySourceId)
- .setSafetySourceIssueId(safetySourceIssueId)
- .setUserId(userId)
- .build();
- SafetyCenterIssueActionId safetyCenterIssueActionId =
- SafetyCenterIssueActionId.newBuilder()
- .setSafetyCenterIssueKey(safetyCenterIssueKey)
- .setSafetySourceIssueActionId(safetySourceIssueActionId)
- .build();
- boolean success = type == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED;
- int result = toSystemEventResult(success);
- return unmarkSafetyCenterIssueActionInFlight(safetyCenterIssueActionId, result);
- case SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED:
- case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED:
- case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED:
- return false;
- }
- Log.w(TAG, "Unexpected SafetyEvent.Type: " + type);
- return false;
- }
-
@NonNull
private SafetyCenterData getSafetyCenterData(
@NonNull List<SafetySourcesGroup> safetySourcesGroups,
@@ -918,7 +351,7 @@ final class SafetyCenterDataTracker {
@NonNull SafetySource safetySource,
@UserIdInt int userId) {
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
- SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
+ SafetySourceData safetySourceData = mSafetyCenterRepository.getSafetySourceData(key);
if (safetySourceData == null) {
return;
@@ -1004,7 +437,7 @@ final class SafetyCenterDataTracker {
safetySourceIssueAction.getLabel(),
safetySourceIssueAction.getPendingIntent())
.setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
- .setIsInFlight(isInFlight(safetyCenterIssueActionId))
+ .setIsInFlight(mSafetyCenterRepository.actionIsInFlight(safetyCenterIssueActionId))
.setWillResolve(safetySourceIssueAction.willResolve())
.build();
}
@@ -1127,7 +560,8 @@ final class SafetyCenterDataTracker {
}
SafetySourceKey key = toSafetySourceKey(entry.getId());
- SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
+ SafetySourceData safetySourceData =
+ mSafetyCenterRepository.getSafetySourceData(key);
boolean hasIssues =
safetySourceData != null && !safetySourceData.getIssues().isEmpty();
@@ -1145,7 +579,7 @@ final class SafetyCenterDataTracker {
SafetyCenterEntry entry = entries.get(i);
SafetySourceKey key = toSafetySourceKey(entry.getId());
- if (mSafetySourceErrors.contains(key)) {
+ if (mSafetyCenterRepository.sourceHasError(key)) {
errorEntries++;
}
}
@@ -1230,7 +664,7 @@ final class SafetyCenterDataTracker {
case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceStatus safetySourceStatus =
- getSafetySourceStatus(mSafetySourceDataForKey.get(key));
+ getSafetySourceStatus(mSafetyCenterRepository.getSafetySourceData(key));
boolean defaultEntryDueToQuietMode = isUserManaged && !isManagedUserRunning;
if (safetySourceStatus != null && !defaultEntryDueToQuietMode) {
PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
@@ -1334,7 +768,8 @@ final class SafetyCenterDataTracker {
safetySource.getTitleForWorkResId())
: mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
CharSequence summary =
- mSafetySourceErrors.contains(SafetySourceKey.of(safetySource.getId(), userId))
+ mSafetyCenterRepository.sourceHasError(
+ SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString(1)
: mSafetyCenterResourcesContext.getOptionalString(
safetySource.getSummaryResId());
@@ -1420,7 +855,8 @@ final class SafetyCenterDataTracker {
}
boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning;
boolean hasError =
- mSafetySourceErrors.contains(SafetySourceKey.of(safetySource.getId(), userId));
+ mSafetyCenterRepository.sourceHasError(
+ SafetySourceKey.of(safetySource.getId(), userId));
if (isQuietModeEnabled || hasError) {
safetyCenterOverallState.addEntryOverallSeverityLevel(
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN);
@@ -1441,7 +877,7 @@ final class SafetyCenterDataTracker {
case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceStatus safetySourceStatus =
- getSafetySourceStatus(mSafetySourceDataForKey.get(key));
+ getSafetySourceStatus(mSafetyCenterRepository.getSafetySourceData(key));
boolean defaultEntryDueToQuietMode = isUserManaged && !isManagedUserRunning;
if (safetySourceStatus != null && !defaultEntryDueToQuietMode) {
PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
@@ -1501,7 +937,8 @@ final class SafetyCenterDataTracker {
safetySource.getTitleForWorkResId())
: mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
CharSequence summary =
- mSafetySourceErrors.contains(SafetySourceKey.of(safetySource.getId(), userId))
+ mSafetyCenterRepository.sourceHasError(
+ SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString(1)
: mSafetyCenterResourcesContext.getOptionalString(
safetySource.getSummaryResId());
diff --git a/service/java/com/android/safetycenter/SafetyCenterRepository.java b/service/java/com/android/safetycenter/SafetyCenterRepository.java
new file mode 100644
index 000000000..9821dc218
--- /dev/null
+++ b/service/java/com/android/safetycenter/SafetyCenterRepository.java
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2022 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.safetycenter;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.android.safetycenter.WestworldLogger.toSystemEventResult;
+import static com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.SystemClock;
+import android.safetycenter.SafetyCenterData;
+import android.safetycenter.SafetyEvent;
+import android.safetycenter.SafetySourceData;
+import android.safetycenter.SafetySourceErrorDetails;
+import android.safetycenter.SafetySourceIssue;
+import android.safetycenter.SafetySourceStatus;
+import android.safetycenter.config.SafetyCenterConfig;
+import android.safetycenter.config.SafetySource;
+import android.util.ArrayMap;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.annotation.RequiresApi;
+
+import com.android.permission.util.UserUtils;
+import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
+import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
+
+import java.io.PrintWriter;
+import java.time.Duration;
+import java.util.List;
+import java.util.Objects;
+
+import javax.annotation.concurrent.NotThreadSafe;
+
+/**
+ * Repository for {@link SafetySourceData} and other data managed by Safety Center including {@link
+ * SafetySourceErrorDetails} and metadata about which issue actions are in-flight.
+ *
+ * <p>This class isn't thread safe. Thread safety must be handled by the caller.
+ */
+@RequiresApi(TIRAMISU)
+@NotThreadSafe
+final class SafetyCenterRepository {
+
+ private static final String TAG = "SafetyCenterRepository";
+
+ private final ArrayMap<SafetySourceKey, SafetySourceData> mSafetySourceDataForKey =
+ new ArrayMap<>();
+
+ private final ArraySet<SafetySourceKey> mSafetySourceErrors = new ArraySet<>();
+
+ private final ArrayMap<SafetyCenterIssueActionId, Long> mSafetyCenterIssueActionsInFlight =
+ new ArrayMap<>();
+
+ @NonNull private final Context mContext;
+ @NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
+ @NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
+ @NonNull private final WestworldLogger mWestworldLogger;
+ @NonNull private final SafetyCenterIssueCache mSafetyCenterIssueCache;
+
+ SafetyCenterRepository(
+ @NonNull Context context,
+ @NonNull SafetyCenterConfigReader safetyCenterConfigReader,
+ @NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker,
+ @NonNull WestworldLogger westworldLogger,
+ @NonNull SafetyCenterIssueCache safetyCenterIssueCache) {
+ mContext = context;
+ mSafetyCenterConfigReader = safetyCenterConfigReader;
+ mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
+ mWestworldLogger = westworldLogger;
+ mSafetyCenterIssueCache = safetyCenterIssueCache;
+ }
+
+ /**
+ * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link
+ * SafetyEvent}, {@code packageName} and {@code userId}, and returns whether there was a change
+ * to the underlying {@link SafetyCenterData}.
+ *
+ * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
+ * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected; or the {@link
+ * SafetySourceData} does not respect all constraints defined in the config.
+ *
+ * <p>Setting a {@code null} {@link SafetySourceData} evicts the current {@link
+ * SafetySourceData} entry and clears the Safety Center issue cache for the source.
+ *
+ * <p>This method may modify the {@link SafetyCenterIssueCache}.
+ */
+ boolean setSafetySourceData(
+ @Nullable SafetySourceData safetySourceData,
+ @NonNull String safetySourceId,
+ @NonNull SafetyEvent safetyEvent,
+ @NonNull String packageName,
+ @UserIdInt int userId) {
+ if (!validateRequest(safetySourceData, safetySourceId, packageName, userId)) {
+ return false;
+ }
+ boolean safetyEventChangedSafetyCenterData =
+ processSafetyEvent(safetySourceId, safetyEvent, userId, false);
+
+ SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
+ boolean removingSafetySourceErrorChangedSafetyCenterData = mSafetySourceErrors.remove(key);
+ SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key);
+ if (Objects.equals(safetySourceData, existingSafetySourceData)) {
+ return safetyEventChangedSafetyCenterData
+ || removingSafetySourceErrorChangedSafetyCenterData;
+ }
+
+ ArraySet<String> issueIds = new ArraySet<>();
+ if (safetySourceData == null) {
+ mSafetySourceDataForKey.remove(key);
+ } else {
+ mSafetySourceDataForKey.put(key, safetySourceData);
+ for (int i = 0; i < safetySourceData.getIssues().size(); i++) {
+ issueIds.add(safetySourceData.getIssues().get(i).getId());
+ }
+ }
+ mSafetyCenterIssueCache.updateIssuesForSource(issueIds, safetySourceId, userId);
+
+ return true;
+ }
+
+ /**
+ * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
+ * the given {@code safetySourceId}, {@code packageName} and {@code userId}.
+ *
+ * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
+ * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
+ *
+ * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
+ * {@link #setSafetySourceData} with a {@code null} value.
+ */
+ @Nullable
+ SafetySourceData getSafetySourceData(
+ @NonNull String safetySourceId, @NonNull String packageName, @UserIdInt int userId) {
+ if (!validateRequest(null, safetySourceId, packageName, userId)) {
+ return null;
+ }
+ return getSafetySourceData(SafetySourceKey.of(safetySourceId, userId));
+ }
+
+ /**
+ * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
+ * the given {@link SafetySourceKey}.
+ *
+ * <p>This method does not perform any validation, {@link #getSafetySourceData(String, String,
+ * int)} should be called wherever validation is required.
+ */
+ @Nullable
+ SafetySourceData getSafetySourceData(@NonNull SafetySourceKey safetySourceKey) {
+ return mSafetySourceDataForKey.get(safetySourceKey);
+ }
+
+ /**
+ * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and
+ * {@code userId}, and returns whether there was a change to the underlying {@link
+ * SafetyCenterData}.
+ *
+ * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
+ * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
+ */
+ boolean reportSafetySourceError(
+ @NonNull SafetySourceErrorDetails safetySourceErrorDetails,
+ @NonNull String safetySourceId,
+ @NonNull String packageName,
+ @UserIdInt int userId) {
+ if (!validateRequest(null, safetySourceId, packageName, userId)) {
+ return false;
+ }
+ SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
+ Log.w(TAG, "Error reported from source: " + safetySourceId + ", for event: " + safetyEvent);
+
+ boolean safetyEventChangedSafetyCenterData =
+ processSafetyEvent(safetySourceId, safetyEvent, userId, true);
+ int safetyEventType = safetyEvent.getType();
+ if (safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
+ || safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) {
+ return safetyEventChangedSafetyCenterData;
+ }
+
+ SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
+ boolean safetySourceErrorChangedSafetyCenterData = setSafetySourceError(key);
+ return safetyEventChangedSafetyCenterData || safetySourceErrorChangedSafetyCenterData;
+ }
+
+ /** Marks the given {@link SafetySourceKey} as having errored-out. */
+ boolean setSafetySourceError(@NonNull SafetySourceKey safetySourceKey) {
+ boolean removingSafetySourceDataChangedSafetyCenterData =
+ mSafetySourceDataForKey.remove(safetySourceKey) != null;
+ boolean addingSafetySourceErrorChangedSafetyCenterData =
+ mSafetySourceErrors.add(safetySourceKey);
+ return removingSafetySourceDataChangedSafetyCenterData
+ || addingSafetySourceErrorChangedSafetyCenterData;
+ }
+
+ /**
+ * Clears all safety source errors received so far for the given {@link UserProfileGroup}, this
+ * is useful e.g. when starting a new broadcast.
+ */
+ void clearSafetySourceErrors(@NonNull UserProfileGroup userProfileGroup) {
+ // Loop in reverse index order to be able to remove entries while iterating.
+ for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) {
+ SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i);
+ if (userProfileGroup.contains(sourceKey.getUserId())) {
+ mSafetySourceErrors.removeAt(i);
+ }
+ }
+ }
+
+ /** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */
+ void markSafetyCenterIssueActionInFlight(
+ @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
+ mSafetyCenterIssueActionsInFlight.put(
+ safetyCenterIssueActionId, SystemClock.elapsedRealtime());
+ }
+
+ /**
+ * Unmarks the given {@link SafetyCenterIssueActionId} as in-flight, logs that event to
+ * Westworld with the given {@code result} value, and returns {@code true} if the underlying
+ * {@link SafetyCenterData} changed.
+ */
+ boolean unmarkSafetyCenterIssueActionInFlight(
+ @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId,
+ @WestworldLogger.SystemEventResult int result) {
+ Long startElapsedMillis =
+ mSafetyCenterIssueActionsInFlight.remove(safetyCenterIssueActionId);
+ if (startElapsedMillis == null) {
+ Log.w(
+ TAG,
+ "Attempt to unmark unknown in-flight action: "
+ + toUserFriendlyString(safetyCenterIssueActionId));
+ return false;
+ }
+
+ SafetyCenterIssueKey issueKey = safetyCenterIssueActionId.getSafetyCenterIssueKey();
+ SafetySourceIssue issue = getSafetySourceIssue(issueKey);
+ String issueTypeId = issue == null ? null : issue.getIssueTypeId();
+ Duration duration = Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis);
+
+ mWestworldLogger.writeInlineActionSystemEvent(
+ issueKey.getSafetySourceId(), issueKey.getUserId(), issueTypeId, duration, result);
+
+ if (issue == null || getSafetySourceIssueAction(safetyCenterIssueActionId) == null) {
+ Log.w(
+ TAG,
+ "Attempt to unmark in-flight action for a non-existent issue or action: "
+ + toUserFriendlyString(safetyCenterIssueActionId));
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * Dismisses the given {@link SafetyCenterIssueKey}.
+ *
+ * <p>This method may modify the {@link SafetyCenterIssueCache}.
+ */
+ void dismissSafetyCenterIssue(@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
+ mSafetyCenterIssueCache.dismissIssue(safetyCenterIssueKey);
+ }
+
+ /**
+ * Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueKey}.
+ *
+ * <p>Returns {@code null} if there is no such {@link SafetySourceIssue}, or if it's been
+ * dismissed.
+ */
+ @Nullable
+ SafetySourceIssue getSafetySourceIssue(@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
+ SafetySourceKey key =
+ SafetySourceKey.of(
+ safetyCenterIssueKey.getSafetySourceId(), safetyCenterIssueKey.getUserId());
+ SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
+ if (safetySourceData == null) {
+ return null;
+ }
+ List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
+
+ SafetySourceIssue targetIssue = null;
+ for (int i = 0; i < safetySourceIssues.size(); i++) {
+ SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
+
+ if (safetyCenterIssueKey.getSafetySourceIssueId().equals(safetySourceIssue.getId())) {
+ targetIssue = safetySourceIssue;
+ break;
+ }
+ }
+ if (targetIssue == null) {
+ return null;
+ }
+
+ if (mSafetyCenterIssueCache.isIssueDismissed(
+ safetyCenterIssueKey, targetIssue.getSeverityLevel())) {
+ return null;
+ }
+
+ return targetIssue;
+ }
+
+ /**
+ * Returns the {@link SafetySourceIssue.Action} associated with the given {@link
+ * SafetyCenterIssueActionId}.
+ *
+ * <p>Returns {@code null} if there is no associated {@link SafetySourceIssue}, or if it's been
+ * dismissed.
+ *
+ * <p>Returns {@code null} if the {@link SafetySourceIssue.Action} is currently in flight.
+ */
+ @Nullable
+ SafetySourceIssue.Action getSafetySourceIssueAction(
+ @NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
+ SafetySourceIssue safetySourceIssue =
+ getSafetySourceIssue(safetyCenterIssueActionId.getSafetyCenterIssueKey());
+
+ if (safetySourceIssue == null) {
+ return null;
+ }
+
+ if (actionIsInFlight(safetyCenterIssueActionId)) {
+ return null;
+ }
+
+ List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
+ for (int i = 0; i < safetySourceIssueActions.size(); i++) {
+ SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
+
+ if (safetyCenterIssueActionId
+ .getSafetySourceIssueActionId()
+ .equals(safetySourceIssueAction.getId())) {
+ return safetySourceIssueAction;
+ }
+ }
+
+ return null;
+ }
+
+ /** Clears all {@link SafetySourceData}, errors, issues and in flight actions for all users. */
+ void clear() {
+ mSafetySourceDataForKey.clear();
+ mSafetySourceErrors.clear();
+ mSafetyCenterIssueCache.clear();
+ mSafetyCenterIssueActionsInFlight.clear();
+ }
+
+ /**
+ * Clears all {@link SafetySourceData}, errors, issues and in flight actions, for the given
+ * user.
+ */
+ void clearForUser(@UserIdInt int userId) {
+ // Loop in reverse index order to be able to remove entries while iterating.
+ for (int i = mSafetySourceDataForKey.size() - 1; i >= 0; i--) {
+ SafetySourceKey sourceKey = mSafetySourceDataForKey.keyAt(i);
+ if (sourceKey.getUserId() == userId) {
+ mSafetySourceDataForKey.removeAt(i);
+ }
+ }
+ // Loop in reverse index order to be able to remove entries while iterating.
+ for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) {
+ SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i);
+ if (sourceKey.getUserId() == userId) {
+ mSafetySourceErrors.removeAt(i);
+ }
+ }
+ // Issue cache implements this itself.
+ mSafetyCenterIssueCache.clearForUser(userId);
+ // Loop in reverse index order to be able to remove entries while iterating.
+ for (int i = mSafetyCenterIssueActionsInFlight.size() - 1; i >= 0; i--) {
+ SafetyCenterIssueActionId issueActionId = mSafetyCenterIssueActionsInFlight.keyAt(i);
+ if (issueActionId.getSafetyCenterIssueKey().getUserId() == userId) {
+ mSafetyCenterIssueActionsInFlight.removeAt(i);
+ }
+ }
+ }
+
+ /** Dumps state for debugging purposes. */
+ void dump(@NonNull PrintWriter fout) {
+ int dataCount = mSafetySourceDataForKey.size();
+ fout.println("SOURCE DATA (" + dataCount + ")");
+ for (int i = 0; i < dataCount; i++) {
+ SafetySourceKey key = mSafetySourceDataForKey.keyAt(i);
+ SafetySourceData data = mSafetySourceDataForKey.valueAt(i);
+ fout.println("\t[" + i + "] " + key + " -> " + data);
+ }
+ fout.println();
+
+ int errorCount = mSafetySourceErrors.size();
+ fout.println("SOURCE ERRORS (" + errorCount + ")");
+ for (int i = 0; i < errorCount; i++) {
+ SafetySourceKey key = mSafetySourceErrors.valueAt(i);
+ fout.println("\t[" + i + "] " + key);
+ }
+ fout.println();
+
+ int actionInFlightCount = mSafetyCenterIssueActionsInFlight.size();
+ fout.println("ACTIONS IN FLIGHT (" + actionInFlightCount + ")");
+ for (int i = 0; i < actionInFlightCount; i++) {
+ SafetyCenterIssueActionId id = mSafetyCenterIssueActionsInFlight.keyAt(i);
+ long startElapsedMillis = mSafetyCenterIssueActionsInFlight.valueAt(i);
+ long durationMillis = SystemClock.elapsedRealtime() - startElapsedMillis;
+ fout.println("\t[" + i + "] " + id + "(duration=" + durationMillis + "ms)");
+ }
+ fout.println();
+ }
+
+ /** Returns {@code true} if the given issue action is in flight. */
+ boolean actionIsInFlight(@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
+ return mSafetyCenterIssueActionsInFlight.containsKey(safetyCenterIssueActionId);
+ }
+
+ /** Returns {@code true} if the given source has an error. */
+ boolean sourceHasError(@NonNull SafetySourceKey safetySourceKey) {
+ return mSafetySourceErrors.contains(safetySourceKey);
+ }
+
+ /**
+ * Checks if a request to the SafetyCenter is valid, and returns whether the request should be
+ * processed.
+ */
+ private boolean validateRequest(
+ @Nullable SafetySourceData safetySourceData,
+ @NonNull String safetySourceId,
+ @NonNull String packageName,
+ @UserIdInt int userId) {
+ SafetyCenterConfigReader.ExternalSafetySource externalSafetySource =
+ mSafetyCenterConfigReader.getExternalSafetySource(safetySourceId);
+ if (externalSafetySource == null) {
+ throw new IllegalArgumentException("Unexpected safety source: " + safetySourceId);
+ }
+
+ SafetySource safetySource = externalSafetySource.getSafetySource();
+
+ // TODO(b/222330089): Security: check certs?
+ if (!packageName.equals(safetySource.getPackageName())) {
+ throw new IllegalArgumentException(
+ "Unexpected package name: "
+ + packageName
+ + ", for safety source: "
+ + safetySourceId);
+ }
+
+ // TODO(b/222327845): Security: check package is installed for user?
+
+ if (UserUtils.isManagedProfile(userId, mContext)
+ && !SafetySources.supportsManagedProfiles(safetySource)) {
+ throw new IllegalArgumentException(
+ "Unexpected managed profile request for safety source: " + safetySourceId);
+ }
+
+ boolean retrievingOrClearingData = safetySourceData == null;
+ if (retrievingOrClearingData) {
+ return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
+ }
+
+ SafetySourceStatus safetySourceStatus = safetySourceData.getStatus();
+
+ if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY
+ && safetySourceStatus != null) {
+ throw new IllegalArgumentException(
+ "Unexpected status for issue only safety source: " + safetySourceId);
+ }
+
+ if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
+ && safetySourceStatus == null) {
+ throw new IllegalArgumentException(
+ "Missing status for dynamic safety source: " + safetySourceId);
+ }
+
+ if (safetySourceStatus != null) {
+ int sourceSeverityLevel = safetySourceStatus.getSeverityLevel();
+
+ if (externalSafetySource.hasEntryInRigidGroup()
+ && sourceSeverityLevel != SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED) {
+ throw new IllegalArgumentException(
+ "Safety source: "
+ + safetySourceId
+ + " is in a rigid group but specified a severity level: "
+ + sourceSeverityLevel);
+ }
+
+ int maxSourceSeverityLevel =
+ Math.max(
+ SafetySourceData.SEVERITY_LEVEL_INFORMATION,
+ safetySource.getMaxSeverityLevel());
+
+ if (sourceSeverityLevel > maxSourceSeverityLevel) {
+ throw new IllegalArgumentException(
+ "Unexpected severity level: "
+ + sourceSeverityLevel
+ + ", for safety source: "
+ + safetySourceId);
+ }
+ }
+
+ List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
+
+ for (int i = 0; i < safetySourceIssues.size(); i++) {
+ SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
+ int issueSeverityLevel = safetySourceIssue.getSeverityLevel();
+ if (issueSeverityLevel > safetySource.getMaxSeverityLevel()) {
+ throw new IllegalArgumentException(
+ "Unexpected severity level: "
+ + issueSeverityLevel
+ + ", for issue in safety source: "
+ + safetySourceId);
+ }
+
+ int issueCategory = safetySourceIssue.getIssueCategory();
+ if (!SafetyCenterFlags.isIssueCategoryAllowedForSource(issueCategory, safetySourceId)) {
+ throw new IllegalArgumentException(
+ "Unexpected issue category: "
+ + issueCategory
+ + ", for issue in safety source: "
+ + safetySourceId);
+ }
+ }
+
+ return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
+ }
+
+ private boolean processSafetyEvent(
+ @NonNull String safetySourceId,
+ @NonNull SafetyEvent safetyEvent,
+ @UserIdInt int userId,
+ boolean isError) {
+ int type = safetyEvent.getType();
+ switch (type) {
+ case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
+ String refreshBroadcastId = safetyEvent.getRefreshBroadcastId();
+ if (refreshBroadcastId == null) {
+ Log.w(
+ TAG,
+ "Received safety event of type "
+ + safetyEvent.getType()
+ + " without a refresh broadcast id");
+ return false;
+ }
+ return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted(
+ refreshBroadcastId, safetySourceId, userId, !isError);
+ case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
+ case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
+ String safetySourceIssueId = safetyEvent.getSafetySourceIssueId();
+ if (safetySourceIssueId == null) {
+ Log.w(
+ TAG,
+ "Received safety event of type "
+ + safetyEvent.getType()
+ + " without a safety source issue id");
+ return false;
+ }
+ String safetySourceIssueActionId = safetyEvent.getSafetySourceIssueActionId();
+ if (safetySourceIssueActionId == null) {
+ Log.w(
+ TAG,
+ "Received safety event of type "
+ + safetyEvent.getType()
+ + " without a safety source issue action id");
+ return false;
+ }
+ SafetyCenterIssueKey safetyCenterIssueKey =
+ SafetyCenterIssueKey.newBuilder()
+ .setSafetySourceId(safetySourceId)
+ .setSafetySourceIssueId(safetySourceIssueId)
+ .setUserId(userId)
+ .build();
+ SafetyCenterIssueActionId safetyCenterIssueActionId =
+ SafetyCenterIssueActionId.newBuilder()
+ .setSafetyCenterIssueKey(safetyCenterIssueKey)
+ .setSafetySourceIssueActionId(safetySourceIssueActionId)
+ .build();
+ boolean success = type == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED;
+ int result = toSystemEventResult(success);
+ return unmarkSafetyCenterIssueActionInFlight(safetyCenterIssueActionId, result);
+ case SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED:
+ case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED:
+ case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED:
+ return false;
+ }
+ Log.w(TAG, "Unexpected SafetyEvent.Type: " + type);
+ return false;
+ }
+}
diff --git a/service/java/com/android/safetycenter/SafetyCenterService.java b/service/java/com/android/safetycenter/SafetyCenterService.java
index d5486d43a..99aab2ba5 100644
--- a/service/java/com/android/safetycenter/SafetyCenterService.java
+++ b/service/java/com/android/safetycenter/SafetyCenterService.java
@@ -144,9 +144,14 @@ public final class SafetyCenterService extends SystemService {
@GuardedBy("mApiLock")
@NonNull
+ private final SafetyCenterRepository mSafetyCenterRepository;
+
+ @GuardedBy("mApiLock")
+ @NonNull
private final SafetyCenterDataTracker mSafetyCenterDataTracker;
@GuardedBy("mApiLock")
+ @NonNull
private final SafetyCenterListeners mSafetyCenterListeners;
@GuardedBy("mApiLock")
@@ -154,6 +159,10 @@ public final class SafetyCenterService extends SystemService {
@GuardedBy("mApiLock")
@NonNull
+ private final SafetyCenterIssueCache mSafetyCenterIssueCache;
+
+ @GuardedBy("mApiLock")
+ @NonNull
private final SafetyCenterBroadcastDispatcher mSafetyCenterBroadcastDispatcher;
@NonNull private final AppOpsManager mAppOpsManager;
@@ -168,15 +177,23 @@ public final class SafetyCenterService extends SystemService {
mSafetyCenterConfigReader = new SafetyCenterConfigReader(mSafetyCenterResourcesContext);
WestworldLogger westworldLogger = new WestworldLogger(context, mSafetyCenterConfigReader);
mSafetyCenterRefreshTracker = new SafetyCenterRefreshTracker(westworldLogger);
+ mSafetyCenterIssueCache = new SafetyCenterIssueCache(mSafetyCenterConfigReader);
+ mSafetyCenterRepository =
+ new SafetyCenterRepository(
+ context,
+ mSafetyCenterConfigReader,
+ mSafetyCenterRefreshTracker,
+ westworldLogger,
+ mSafetyCenterIssueCache);
mSafetyCenterDataTracker =
new SafetyCenterDataTracker(
- context,
mSafetyCenterResourcesContext,
mSafetyCenterConfigReader,
mSafetyCenterRefreshTracker,
westworldLogger,
new PendingIntentFactory(context),
- new SafetyCenterIssueCache(mSafetyCenterConfigReader));
+ mSafetyCenterIssueCache,
+ mSafetyCenterRepository);
mSafetyCenterListeners = new SafetyCenterListeners(mSafetyCenterDataTracker);
mSafetyCenterBroadcastDispatcher =
new SafetyCenterBroadcastDispatcher(
@@ -264,7 +281,7 @@ public final class SafetyCenterService extends SystemService {
UserProfileGroup userProfileGroup = UserProfileGroup.from(getContext(), userId);
synchronized (mApiLock) {
boolean hasUpdate =
- mSafetyCenterDataTracker.setSafetySourceData(
+ mSafetyCenterRepository.setSafetySourceData(
safetySourceData, safetySourceId, safetyEvent, packageName, userId);
mSafetyCenterListeners.deliverUpdateForUserProfileGroup(
userProfileGroup, hasUpdate, null);
@@ -290,7 +307,7 @@ public final class SafetyCenterService extends SystemService {
}
synchronized (mApiLock) {
- return mSafetyCenterDataTracker.getSafetySourceData(
+ return mSafetyCenterRepository.getSafetySourceData(
safetySourceId, packageName, userId);
}
}
@@ -316,7 +333,7 @@ public final class SafetyCenterService extends SystemService {
UserProfileGroup userProfileGroup = UserProfileGroup.from(getContext(), userId);
synchronized (mApiLock) {
boolean hasUpdate =
- mSafetyCenterDataTracker.reportSafetySourceError(
+ mSafetyCenterRepository.reportSafetySourceError(
errorDetails, safetySourceId, packageName, userId);
SafetyCenterErrorDetails safetyCenterErrorDetails = null;
if (hasUpdate
@@ -447,7 +464,7 @@ public final class SafetyCenterService extends SystemService {
"dismissSafetyCenterIssue", userProfileGroup, safetyCenterIssueKey.getUserId());
synchronized (mApiLock) {
SafetySourceIssue safetySourceIssue =
- mSafetyCenterDataTracker.getSafetySourceIssue(safetyCenterIssueKey);
+ mSafetyCenterRepository.getSafetySourceIssue(safetyCenterIssueKey);
if (safetySourceIssue == null) {
Log.w(
TAG,
@@ -457,7 +474,7 @@ public final class SafetyCenterService extends SystemService {
// button multiple times in a row.
return;
}
- mSafetyCenterDataTracker.dismissSafetyCenterIssue(safetyCenterIssueKey);
+ mSafetyCenterRepository.dismissSafetyCenterIssue(safetyCenterIssueKey);
scheduleWriteSafetyCenterIssueCacheFileIfNeededLocked();
PendingIntent onDismissPendingIntent =
safetySourceIssue.getOnDismissPendingIntent();
@@ -510,7 +527,7 @@ public final class SafetyCenterService extends SystemService {
safetyCenterIssueKey.getUserId());
synchronized (mApiLock) {
SafetySourceIssue.Action safetySourceIssueAction =
- mSafetyCenterDataTracker.getSafetySourceIssueAction(
+ mSafetyCenterRepository.getSafetySourceIssueAction(
safetyCenterIssueActionId);
if (safetySourceIssueAction == null) {
Log.w(
@@ -543,7 +560,7 @@ public final class SafetyCenterService extends SystemService {
return;
}
if (safetySourceIssueAction.willResolve()) {
- mSafetyCenterDataTracker.markSafetyCenterIssueActionInFlight(
+ mSafetyCenterRepository.markSafetyCenterIssueActionInFlight(
safetyCenterIssueActionId);
ResolvingActionTimeout resolvingActionTimeout =
new ResolvingActionTimeout(safetyCenterIssueActionId, userProfileGroup);
@@ -721,7 +738,8 @@ public final class SafetyCenterService extends SystemService {
SafetyCenterService.this.dumpLocked(fd, fout);
SafetyCenterFlags.dump(fout);
mSafetyCenterConfigReader.dump(fout);
- mSafetyCenterDataTracker.dump(fout);
+ mSafetyCenterRepository.dump(fout);
+ mSafetyCenterIssueCache.dump(fout);
mSafetyCenterRefreshTracker.dump(fout);
mSafetyCenterTimeouts.dump(fout);
mSafetyCenterListeners.dump(fout);
@@ -870,7 +888,7 @@ public final class SafetyCenterService extends SystemService {
SafetyCenterFlags.getShowErrorEntriesOnTimeout();
if (showErrorEntriesOnTimeout) {
for (int i = 0; i < stillInFlight.size(); i++) {
- mSafetyCenterDataTracker.setSafetySourceError(stillInFlight.valueAt(i));
+ mSafetyCenterRepository.setSafetySourceError(stillInFlight.valueAt(i));
}
}
mSafetyCenterListeners.deliverUpdateForUserProfileGroup(
@@ -918,7 +936,7 @@ public final class SafetyCenterService extends SystemService {
synchronized (mApiLock) {
mSafetyCenterTimeouts.remove(this);
boolean safetyCenterDataHasChanged =
- mSafetyCenterDataTracker.unmarkSafetyCenterIssueActionInFlight(
+ mSafetyCenterRepository.unmarkSafetyCenterIssueActionInFlight(
mSafetyCenterIssueActionId,
SAFETY_CENTER_SYSTEM_EVENT_REPORTED__RESULT__TIMEOUT);
if (!safetyCenterDataHasChanged) {
@@ -1006,7 +1024,7 @@ public final class SafetyCenterService extends SystemService {
UserProfileGroup userProfileGroup = UserProfileGroup.from(getContext(), userId);
synchronized (mApiLock) {
if (clearDataPermanently) {
- mSafetyCenterDataTracker.clearForUser(userId);
+ mSafetyCenterRepository.clearForUser(userId);
}
mSafetyCenterListeners.clearForUser(userId);
mSafetyCenterRefreshTracker.clearRefreshForUser(userId);
@@ -1019,7 +1037,7 @@ public final class SafetyCenterService extends SystemService {
@RefreshReason int refreshReason, @UserIdInt int userId) {
UserProfileGroup userProfileGroup = UserProfileGroup.from(getContext(), userId);
synchronized (mApiLock) {
- mSafetyCenterDataTracker.clearSafetySourceErrors(userProfileGroup);
+ mSafetyCenterRepository.clearSafetySourceErrors(userProfileGroup);
String refreshBroadcastId =
mSafetyCenterBroadcastDispatcher.sendRefreshSafetySources(
@@ -1040,7 +1058,7 @@ public final class SafetyCenterService extends SystemService {
/** Schedule writing the cache to file. */
@GuardedBy("mApiLock")
private void scheduleWriteSafetyCenterIssueCacheFileIfNeededLocked() {
- if (!mSafetyCenterDataTracker.isSafetyCenterIssueCacheDirty()) {
+ if (!mSafetyCenterIssueCache.isDirty()) {
return;
}
if (!mSafetyCenterIssueCacheWriteScheduled) {
@@ -1056,7 +1074,7 @@ public final class SafetyCenterService extends SystemService {
synchronized (mApiLock) {
mSafetyCenterIssueCacheWriteScheduled = false;
- persistedSafetyCenterIssues = mSafetyCenterDataTracker.snapshotSafetyCenterIssueCache();
+ persistedSafetyCenterIssues = mSafetyCenterIssueCache.snapshot();
// Since all write operations are scheduled in the same background thread, we can safely
// release the lock after creating a snapshot and know that all snapshots will be
// written in the correct order even if we are not holding the lock.
@@ -1078,7 +1096,7 @@ public final class SafetyCenterService extends SystemService {
Log.e(TAG, "Cannot read Safety Center persisted issues", e);
}
- mSafetyCenterDataTracker.loadSafetyCenterIssueCache(persistedSafetyCenterIssues);
+ mSafetyCenterIssueCache.load(persistedSafetyCenterIssues);
scheduleWriteSafetyCenterIssueCacheFileIfNeededLocked();
}
@@ -1092,7 +1110,7 @@ public final class SafetyCenterService extends SystemService {
@GuardedBy("mApiLock")
private void clearDataLocked() {
- mSafetyCenterDataTracker.clear();
+ mSafetyCenterRepository.clear();
mSafetyCenterTimeouts.clear();
mSafetyCenterRefreshTracker.clearRefresh();
scheduleWriteSafetyCenterIssueCacheFileIfNeededLocked();