blob: 2ab380f6e1abfbbee9b6bce4d34973a86d2cb3a5 [file] [log] [blame]
/*
* 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 java.util.Collections.emptyList;
import static java.util.Objects.requireNonNull;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.icu.text.ListFormatter;
import android.icu.text.MessageFormat;
import android.icu.util.ULocale;
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.safetycenter.SafetyCenterStatus;
import android.safetycenter.SafetySourceData;
import android.safetycenter.SafetySourceIssue;
import android.safetycenter.SafetySourceStatus;
import android.safetycenter.config.SafetyCenterConfig;
import android.safetycenter.config.SafetySource;
import android.safetycenter.config.SafetySourcesGroup;
import android.text.TextUtils;
import android.util.ArrayMap;
import android.util.Log;
import androidx.annotation.RequiresApi;
import com.android.modules.utils.build.SdkLevel;
import com.android.safetycenter.data.SafetyCenterInFlightIssueActionRepository;
import com.android.safetycenter.data.SafetyCenterIssueDismissalRepository;
import com.android.safetycenter.data.SafetyCenterIssueRepository;
import com.android.safetycenter.data.SafetySourceDataRepository;
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.resources.SafetyCenterResourcesContext;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Aggregates {@link SafetySourceData} to build {@link SafetyCenterData} instances which are shared
* with Safety Center listeners, including PermissionController.
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*
* @hide
*/
@RequiresApi(TIRAMISU)
@NotThreadSafe
public final class SafetyCenterDataFactory {
private static final String TAG = "SafetyCenterDataFactory";
private static final String ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID = "AndroidLockScreenSources";
@NonNull private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
@NonNull private final PendingIntentFactory mPendingIntentFactory;
@NonNull
private final SafetyCenterInFlightIssueActionRepository
mSafetyCenterInFlightIssueActionRepository;
@NonNull
private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository;
@NonNull private final SafetySourceDataRepository mSafetySourceDataRepository;
@NonNull private final SafetyCenterIssueRepository mSafetyCenterIssueRepository;
SafetyCenterDataFactory(
@NonNull SafetyCenterResourcesContext safetyCenterResourcesContext,
@NonNull SafetyCenterConfigReader safetyCenterConfigReader,
@NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker,
@NonNull PendingIntentFactory pendingIntentFactory,
@NonNull
SafetyCenterInFlightIssueActionRepository
safetyCenterInFlightIssueActionRepository,
@NonNull SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository,
@NonNull SafetySourceDataRepository safetySourceDataRepository,
@NonNull SafetyCenterIssueRepository safetyCenterIssueRepository) {
mSafetyCenterResourcesContext = safetyCenterResourcesContext;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mPendingIntentFactory = pendingIntentFactory;
mSafetyCenterInFlightIssueActionRepository = safetyCenterInFlightIssueActionRepository;
mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository;
mSafetySourceDataRepository = safetySourceDataRepository;
mSafetyCenterIssueRepository = safetyCenterIssueRepository;
}
/**
* Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
*/
@NonNull
static SafetyCenterData getDefaultSafetyCenterData() {
SafetyCenterStatus defaultSafetyCenterStatus =
new SafetyCenterStatus.Builder("", "")
.setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
.build();
if (SdkLevel.isAtLeastU()) {
return new SafetyCenterData.Builder(defaultSafetyCenterStatus).build();
} else {
return new SafetyCenterData(
defaultSafetyCenterStatus, emptyList(), emptyList(), emptyList());
}
}
/**
* Returns the current {@link SafetyCenterData} for the given {@code packageName} and {@link
* UserProfileGroup}, aggregated from all the {@link SafetySourceData} set so far.
*
* <p>If a {@link SafetySourceData} was not set, the default value from the {@link
* SafetyCenterConfig} is used.
*/
@NonNull
SafetyCenterData assembleSafetyCenterData(
@NonNull String packageName, @NonNull UserProfileGroup userProfileGroup) {
return assembleSafetyCenterData(packageName, userProfileGroup, getAllGroups());
}
/**
* Returns the current {@link SafetyCenterData} for the given {@code packageName} and {@link
* UserProfileGroup}, aggregated from {@link SafetySourceData} set by the specified {@link
* SafetySourcesGroup}s.
*
* <p>If a {@link SafetySourceData} was not set, the default value from the {@link
* SafetyCenterConfig} is used.
*/
@NonNull
public SafetyCenterData assembleSafetyCenterData(
@NonNull String packageName,
@NonNull UserProfileGroup userProfileGroup,
@NonNull List<SafetySourcesGroup> safetySourcesGroups) {
List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups = new ArrayList<>();
List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups = new ArrayList<>();
SafetyCenterOverallState safetyCenterOverallState = new SafetyCenterOverallState();
for (int i = 0; i < safetySourcesGroups.size(); i++) {
SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
int safetySourcesGroupType = safetySourcesGroup.getType();
switch (safetySourcesGroupType) {
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATEFUL:
addSafetyCenterEntryGroup(
safetyCenterOverallState,
safetyCenterEntryOrGroups,
safetySourcesGroup,
packageName,
userProfileGroup);
break;
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_STATELESS:
addSafetyCenterStaticEntryGroup(
safetyCenterOverallState,
safetyCenterStaticEntryGroups,
safetySourcesGroup,
packageName,
userProfileGroup);
break;
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN:
break;
default:
Log.w(TAG, "Unexpected SafetySourceGroupType: " + safetySourcesGroupType);
break;
}
}
List<SafetySourceIssueInfo> issuesInfo =
mSafetyCenterIssueRepository.getIssuesDedupedSortedDescFor(userProfileGroup);
List<SafetyCenterIssue> safetyCenterIssues = new ArrayList<>();
List<SafetyCenterIssue> safetyCenterDismissedIssues = new ArrayList<>();
SafetySourceIssueInfo topNonDismissedIssueInfo = null;
for (int i = 0; i < issuesInfo.size(); i++) {
SafetySourceIssueInfo issueInfo = issuesInfo.get(i);
SafetyCenterIssue safetyCenterIssue =
toSafetyCenterIssue(
issueInfo.getSafetySourceIssue(),
issueInfo.getSafetySourcesGroup(),
issueInfo.getSafetyCenterIssueKey());
if (mSafetyCenterIssueDismissalRepository.isIssueDismissed(
issueInfo.getSafetyCenterIssueKey(),
issueInfo.getSafetySourceIssue().getSeverityLevel())) {
safetyCenterDismissedIssues.add(safetyCenterIssue);
} else {
safetyCenterIssues.add(safetyCenterIssue);
safetyCenterOverallState.addIssueOverallSeverityLevel(
toSafetyCenterStatusOverallSeverityLevel(
issueInfo.getSafetySourceIssue().getSeverityLevel()));
if (topNonDismissedIssueInfo == null) {
topNonDismissedIssueInfo = issueInfo;
}
}
}
int refreshStatus = mSafetyCenterRefreshTracker.getRefreshStatus();
SafetyCenterStatus safetyCenterStatus =
new SafetyCenterStatus.Builder(
getSafetyCenterStatusTitle(
safetyCenterOverallState.getOverallSeverityLevel(),
topNonDismissedIssueInfo,
refreshStatus,
safetyCenterOverallState.hasSettingsToReview()),
getSafetyCenterStatusSummary(
safetyCenterOverallState.getOverallSeverityLevel(),
refreshStatus,
safetyCenterIssues.size(),
safetyCenterOverallState.hasSettingsToReview()))
.setSeverityLevel(safetyCenterOverallState.getOverallSeverityLevel())
.setRefreshStatus(refreshStatus)
.build();
if (SdkLevel.isAtLeastU()) {
SafetyCenterData.Builder builder = new SafetyCenterData.Builder(safetyCenterStatus);
for (int i = 0; i < safetyCenterIssues.size(); i++) {
builder.addIssue(safetyCenterIssues.get(i));
}
for (int i = 0; i < safetyCenterEntryOrGroups.size(); i++) {
builder.addEntryOrGroup(safetyCenterEntryOrGroups.get(i));
}
for (int i = 0; i < safetyCenterStaticEntryGroups.size(); i++) {
builder.addStaticEntryGroup(safetyCenterStaticEntryGroups.get(i));
}
for (int i = 0; i < safetyCenterDismissedIssues.size(); i++) {
builder.addDismissedIssue(safetyCenterDismissedIssues.get(i));
}
return builder.build();
} else {
return new SafetyCenterData(
safetyCenterStatus,
safetyCenterIssues,
safetyCenterEntryOrGroups,
safetyCenterStaticEntryGroups);
}
}
@NonNull
private List<SafetySourcesGroup> getAllGroups() {
return mSafetyCenterConfigReader.getSafetySourcesGroups();
}
@Nullable
private SafetyCenterIssue toSafetyCenterIssue(
@NonNull SafetySourceIssue safetySourceIssue,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
SafetyCenterIssueId safetyCenterIssueId =
SafetyCenterIssueId.newBuilder()
.setSafetyCenterIssueKey(safetyCenterIssueKey)
.setIssueTypeId(safetySourceIssue.getIssueTypeId())
.build();
List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
List<SafetyCenterIssue.Action> safetyCenterIssueActions =
new ArrayList<>(safetySourceIssueActions.size());
for (int i = 0; i < safetySourceIssueActions.size(); i++) {
SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
safetyCenterIssueActions.add(
toSafetyCenterIssueAction(
safetySourceIssueAction,
safetyCenterIssueId.getSafetyCenterIssueKey()));
}
int safetyCenterIssueSeverityLevel =
toSafetyCenterIssueSeverityLevel(safetySourceIssue.getSeverityLevel());
SafetyCenterIssue.Builder safetyCenterIssueBuilder =
new SafetyCenterIssue.Builder(
SafetyCenterIds.encodeToString(safetyCenterIssueId),
safetySourceIssue.getTitle(),
safetySourceIssue.getSummary())
.setSeverityLevel(safetyCenterIssueSeverityLevel)
.setShouldConfirmDismissal(
safetyCenterIssueSeverityLevel
> SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK)
.setSubtitle(safetySourceIssue.getSubtitle())
.setActions(safetyCenterIssueActions);
if (SdkLevel.isAtLeastU()) {
CharSequence issueAttributionTitle =
TextUtils.isEmpty(safetySourceIssue.getAttributionTitle())
? mSafetyCenterResourcesContext.getOptionalString(
safetySourcesGroup.getTitleResId())
: safetySourceIssue.getAttributionTitle();
safetyCenterIssueBuilder.setAttributionTitle(issueAttributionTitle);
safetyCenterIssueBuilder.setGroupId(safetySourcesGroup.getId());
}
return safetyCenterIssueBuilder.build();
}
@NonNull
private SafetyCenterIssue.Action toSafetyCenterIssueAction(
@NonNull SafetySourceIssue.Action safetySourceIssueAction,
@NonNull SafetyCenterIssueKey safetyCenterIssueKey) {
SafetyCenterIssueActionId safetyCenterIssueActionId =
SafetyCenterIssueActionId.newBuilder()
.setSafetyCenterIssueKey(safetyCenterIssueKey)
.setSafetySourceIssueActionId(safetySourceIssueAction.getId())
.build();
PendingIntent issueActionPendingIntent =
mPendingIntentFactory.maybeOverridePendingIntent(
safetyCenterIssueKey.getSafetySourceId(),
safetySourceIssueAction.getPendingIntent(),
false);
return new SafetyCenterIssue.Action.Builder(
SafetyCenterIds.encodeToString(safetyCenterIssueActionId),
safetySourceIssueAction.getLabel(),
requireNonNull(issueActionPendingIntent))
.setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
.setIsInFlight(
mSafetyCenterInFlightIssueActionRepository.actionIsInFlight(
safetyCenterIssueActionId))
.setWillResolve(safetySourceIssueAction.willResolve())
.build();
}
private void addSafetyCenterEntryGroup(
@NonNull SafetyCenterOverallState safetyCenterOverallState,
@NonNull List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull String defaultPackageName,
@NonNull UserProfileGroup userProfileGroup) {
int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
List<SafetyCenterEntry> entries = new ArrayList<>(safetySources.size());
for (int i = 0; i < safetySources.size(); i++) {
SafetySource safetySource = safetySources.get(i);
groupSafetyCenterEntryLevel =
mergeSafetyCenterEntrySeverityLevels(
groupSafetyCenterEntryLevel,
addSafetyCenterEntry(
safetyCenterOverallState,
entries,
safetySource,
defaultPackageName,
userProfileGroup.getProfileParentUserId(),
false,
false));
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
}
int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
for (int j = 0; j < managedProfilesUserIds.length; j++) {
int managedProfileUserId = managedProfilesUserIds[j];
boolean isManagedUserRunning =
userProfileGroup.isManagedUserRunning(managedProfileUserId);
groupSafetyCenterEntryLevel =
mergeSafetyCenterEntrySeverityLevels(
groupSafetyCenterEntryLevel,
addSafetyCenterEntry(
safetyCenterOverallState,
entries,
safetySource,
defaultPackageName,
managedProfileUserId,
true,
isManagedUserRunning));
}
}
if (entries.size() == 0) {
return;
}
if (!SafetyCenterFlags.getShowSubpages() && entries.size() == 1) {
safetyCenterEntryOrGroups.add(new SafetyCenterEntryOrGroup(entries.get(0)));
return;
}
CharSequence groupSummary =
getSafetyCenterEntryGroupSummary(
safetySourcesGroup, groupSafetyCenterEntryLevel, entries);
safetyCenterEntryOrGroups.add(
new SafetyCenterEntryOrGroup(
new SafetyCenterEntryGroup.Builder(
safetySourcesGroup.getId(),
mSafetyCenterResourcesContext.getString(
safetySourcesGroup.getTitleResId()))
.setSeverityLevel(groupSafetyCenterEntryLevel)
.setSummary(groupSummary)
.setEntries(entries)
.setSeverityUnspecifiedIconType(
toGroupSeverityUnspecifiedIconType(
safetySourcesGroup.getStatelessIconType()))
.build()));
}
@SafetyCenterEntry.EntrySeverityLevel
private static int mergeSafetyCenterEntrySeverityLevels(
@SafetyCenterEntry.EntrySeverityLevel int left,
@SafetyCenterEntry.EntrySeverityLevel int right) {
if (left > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK
|| right > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK) {
return Math.max(left, right);
}
if (left == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN
|| right == SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN) {
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
return Math.max(left, right);
}
@Nullable
private CharSequence getSafetyCenterEntryGroupSummary(
@NonNull SafetySourcesGroup safetySourcesGroup,
@SafetyCenterEntry.EntrySeverityLevel int groupSafetyCenterEntryLevel,
@NonNull List<SafetyCenterEntry> entries) {
switch (groupSafetyCenterEntryLevel) {
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
for (int i = 0; i < entries.size(); i++) {
SafetyCenterEntry entry = entries.get(i);
CharSequence entrySummary = entry.getSummary();
if (entry.getSeverityLevel() != groupSafetyCenterEntryLevel
|| entrySummary == null) {
continue;
}
if (groupSafetyCenterEntryLevel > SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK) {
return entrySummary;
}
SafetySourceKey key = toSafetySourceKey(entry.getId());
SafetySourceData safetySourceData =
mSafetySourceDataRepository.getSafetySourceDataInternal(key);
boolean hasIssues =
safetySourceData != null && !safetySourceData.getIssues().isEmpty();
if (hasIssues) {
return entrySummary;
}
}
return getDefaultGroupSummary(safetySourcesGroup, entries);
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
return getDefaultGroupSummary(safetySourcesGroup, entries);
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
int errorEntries = 0;
for (int i = 0; i < entries.size(); i++) {
SafetyCenterEntry entry = entries.get(i);
SafetySourceKey key = toSafetySourceKey(entry.getId());
if (mSafetySourceDataRepository.sourceHasError(key)) {
errorEntries++;
}
}
if (errorEntries > 0) {
return getRefreshErrorString(errorEntries);
}
return mSafetyCenterResourcesContext.getStringByName("group_unknown_summary");
}
Log.w(
TAG,
"Unexpected SafetyCenterEntry.EntrySeverityLevel for SafetyCenterEntryGroup: "
+ groupSafetyCenterEntryLevel);
return getDefaultGroupSummary(safetySourcesGroup, entries);
}
@Nullable
private CharSequence getDefaultGroupSummary(
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull List<SafetyCenterEntry> entries) {
CharSequence groupSummary =
mSafetyCenterResourcesContext.getOptionalString(
safetySourcesGroup.getSummaryResId());
if (safetySourcesGroup.getId().equals(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
&& TextUtils.isEmpty(groupSummary)) {
List<CharSequence> titles = new ArrayList<>();
for (int i = 0; i < entries.size(); i++) {
titles.add(entries.get(i).getTitle());
}
groupSummary =
ListFormatter.getInstance(
ULocale.getDefault(ULocale.Category.FORMAT),
ListFormatter.Type.AND,
ListFormatter.Width.NARROW)
.format(titles);
}
return groupSummary;
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterEntry(
@NonNull SafetyCenterOverallState safetyCenterOverallState,
@NonNull List<SafetyCenterEntry> entries,
@NonNull SafetySource safetySource,
@NonNull String defaultPackageName,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
SafetyCenterEntry safetyCenterEntry =
toSafetyCenterEntry(
safetySource,
defaultPackageName,
userId,
isUserManaged,
isManagedUserRunning);
if (safetyCenterEntry == null) {
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
}
safetyCenterOverallState.addEntryOverallSeverityLevel(
entryToSafetyCenterStatusOverallSeverityLevel(
safetyCenterEntry.getSeverityLevel()));
entries.add(safetyCenterEntry);
return safetyCenterEntry.getSeverityLevel();
}
@Nullable
private SafetyCenterEntry toSafetyCenterEntry(
@NonNull SafetySource safetySource,
@NonNull String defaultPackageName,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
switch (safetySource.getType()) {
case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY:
return null;
case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceStatus safetySourceStatus =
getSafetySourceStatus(
mSafetySourceDataRepository.getSafetySourceDataInternal(key));
boolean defaultEntryDueToQuietMode = isUserManaged && !isManagedUserRunning;
if (safetySourceStatus == null || defaultEntryDueToQuietMode) {
int severityLevel =
defaultEntryDueToQuietMode
? SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
: SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
return toDefaultSafetyCenterEntry(
safetySource,
safetySource.getPackageName(),
severityLevel,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION,
userId,
isUserManaged,
isManagedUserRunning);
}
PendingIntent entryPendingIntent = safetySourceStatus.getPendingIntent();
boolean enabled = safetySourceStatus.isEnabled();
if (entryPendingIntent == null) {
entryPendingIntent =
mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
safetySource.getPackageName(),
userId,
false);
enabled = enabled && entryPendingIntent != null;
}
SafetyCenterEntryId safetyCenterEntryId =
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build();
int severityUnspecifiedIconType =
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION;
int severityLevel =
enabled
? toSafetyCenterEntrySeverityLevel(
safetySourceStatus.getSeverityLevel())
: SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
SafetyCenterEntry.Builder builder =
new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId),
safetySourceStatus.getTitle())
.setSeverityLevel(severityLevel)
.setSummary(safetySourceStatus.getSummary())
.setEnabled(enabled)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.setPendingIntent(
mPendingIntentFactory.maybeOverridePendingIntent(
safetySource.getId(), entryPendingIntent, false));
SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction();
if (iconAction == null) {
return builder.build();
}
PendingIntent iconActionPendingIntent =
mPendingIntentFactory.maybeOverridePendingIntent(
safetySource.getId(), iconAction.getPendingIntent(), true);
return builder.setIconAction(
new SafetyCenterEntry.IconAction(
toSafetyCenterEntryIconActionType(iconAction.getIconType()),
requireNonNull(iconActionPendingIntent)))
.build();
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterEntry(
safetySource,
getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON,
userId,
isUserManaged,
isManagedUserRunning);
}
Log.w(
TAG,
"Unknown safety source type found in collapsible group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterEntry toDefaultSafetyCenterEntry(
@NonNull SafetySource safetySource,
@NonNull String packageName,
@SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel,
@SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
SafetyCenterEntryId safetyCenterEntryId =
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build();
boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning;
PendingIntent pendingIntent =
mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
packageName,
userId,
isQuietModeEnabled);
boolean enabled =
pendingIntent != null && !SafetySources.isDefaultEntryDisabled(safetySource);
CharSequence title =
isUserManaged
? DevicePolicyResources.getSafetySourceWorkString(
mSafetyCenterResourcesContext,
safetySource.getId(),
safetySource.getTitleForWorkResId())
: mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
CharSequence summary =
mSafetySourceDataRepository.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString(1)
: mSafetyCenterResourcesContext.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
enabled = false;
summary =
DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
}
return new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId), title)
.setSeverityLevel(entrySeverityLevel)
.setSummary(summary)
.setEnabled(enabled)
.setPendingIntent(pendingIntent)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.build();
}
private void addSafetyCenterStaticEntryGroup(
@NonNull SafetyCenterOverallState safetyCenterOverallState,
@NonNull List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull String defaultPackageName,
@NonNull UserProfileGroup userProfileGroup) {
List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
List<SafetyCenterStaticEntry> staticEntries = new ArrayList<>(safetySources.size());
for (int i = 0; i < safetySources.size(); i++) {
SafetySource safetySource = safetySources.get(i);
addSafetyCenterStaticEntry(
safetyCenterOverallState,
staticEntries,
safetySource,
defaultPackageName,
userProfileGroup.getProfileParentUserId(),
false,
false);
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
}
int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
for (int j = 0; j < managedProfilesUserIds.length; j++) {
int managedProfileUserId = managedProfilesUserIds[j];
boolean isManagedUserRunning =
userProfileGroup.isManagedUserRunning(managedProfileUserId);
addSafetyCenterStaticEntry(
safetyCenterOverallState,
staticEntries,
safetySource,
defaultPackageName,
managedProfileUserId,
true,
isManagedUserRunning);
}
}
if (staticEntries.isEmpty()) {
return;
}
safetyCenterStaticEntryGroups.add(
new SafetyCenterStaticEntryGroup(
mSafetyCenterResourcesContext.getString(safetySourcesGroup.getTitleResId()),
staticEntries));
}
private void addSafetyCenterStaticEntry(
@NonNull SafetyCenterOverallState safetyCenterOverallState,
@NonNull List<SafetyCenterStaticEntry> staticEntries,
@NonNull SafetySource safetySource,
@NonNull String defaultPackageName,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
SafetyCenterStaticEntry staticEntry =
toSafetyCenterStaticEntry(
safetySource,
defaultPackageName,
userId,
isUserManaged,
isManagedUserRunning);
if (staticEntry == null) {
return;
}
boolean hasError =
mSafetySourceDataRepository.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId));
if (hasError) {
safetyCenterOverallState.addEntryOverallSeverityLevel(
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN);
}
staticEntries.add(staticEntry);
}
@Nullable
private SafetyCenterStaticEntry toSafetyCenterStaticEntry(
@NonNull SafetySource safetySource,
@NonNull String defaultPackageName,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
switch (safetySource.getType()) {
case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY:
return null;
case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC:
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceStatus safetySourceStatus =
getSafetySourceStatus(
mSafetySourceDataRepository.getSafetySourceDataInternal(key));
boolean defaultEntryDueToQuietMode = isUserManaged && !isManagedUserRunning;
if (safetySourceStatus != null && !defaultEntryDueToQuietMode) {
PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
if (pendingIntent == null) {
// TODO(b/222838784): Decide strategy for static entries when the intent is
// null.
return null;
}
return new SafetyCenterStaticEntry.Builder(safetySourceStatus.getTitle())
.setSummary(safetySourceStatus.getSummary())
.setPendingIntent(pendingIntent)
.build();
}
return toDefaultSafetyCenterStaticEntry(
safetySource,
safetySource.getPackageName(),
userId,
isUserManaged,
isManagedUserRunning);
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterStaticEntry(
safetySource,
getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
userId,
isUserManaged,
isManagedUserRunning);
}
Log.w(TAG, "Unknown safety source type found in rigid group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterStaticEntry toDefaultSafetyCenterStaticEntry(
@NonNull SafetySource safetySource,
@NonNull String packageName,
@UserIdInt int userId,
boolean isUserManaged,
boolean isManagedUserRunning) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
boolean isQuietModeEnabled = isUserManaged && !isManagedUserRunning;
PendingIntent pendingIntent =
mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
packageName,
userId,
isQuietModeEnabled);
if (pendingIntent == null) {
// TODO(b/222838784): Decide strategy for static entries when the intent is null.
return null;
}
CharSequence title =
isUserManaged
? DevicePolicyResources.getSafetySourceWorkString(
mSafetyCenterResourcesContext,
safetySource.getId(),
safetySource.getTitleForWorkResId())
: mSafetyCenterResourcesContext.getString(safetySource.getTitleResId());
CharSequence summary =
mSafetySourceDataRepository.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString(1)
: mSafetyCenterResourcesContext.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
summary =
DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesContext);
}
return new SafetyCenterStaticEntry.Builder(title)
.setSummary(summary)
.setPendingIntent(pendingIntent)
.build();
}
@Nullable
private static SafetySourceStatus getSafetySourceStatus(
@Nullable SafetySourceData safetySourceData) {
if (safetySourceData == null) {
return null;
}
return safetySourceData.getStatus();
}
@NonNull
private static String getStaticSourcePackageNameOrDefault(
@NonNull SafetySource safetySource, @NonNull String defaultPackageName) {
if (!SdkLevel.isAtLeastU()) {
return defaultPackageName;
}
String sourcePackageName = safetySource.getOptionalPackageName();
if (sourcePackageName == null) {
return defaultPackageName;
}
return sourcePackageName;
}
@SafetyCenterStatus.OverallSeverityLevel
private static int toSafetyCenterStatusOverallSeverityLevel(
@SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
switch (safetySourceSeverityLevel) {
case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
}
Log.w(TAG, "Unexpected SafetySourceData.SeverityLevel: " + safetySourceSeverityLevel);
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
}
@SafetyCenterStatus.OverallSeverityLevel
private static int entryToSafetyCenterStatusOverallSeverityLevel(
@SafetyCenterEntry.EntrySeverityLevel int safetyCenterEntrySeverityLevel) {
switch (safetyCenterEntrySeverityLevel) {
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED:
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION;
case SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING:
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING;
}
Log.w(
TAG,
"Unexpected SafetyCenterEntry.EntrySeverityLevel: "
+ safetyCenterEntrySeverityLevel);
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
}
@SafetyCenterEntry.EntrySeverityLevel
private static int toSafetyCenterEntrySeverityLevel(
@SafetySourceData.SeverityLevel int safetySourceSeverityLevel) {
switch (safetySourceSeverityLevel) {
case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK;
case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION;
case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING;
}
Log.w(
TAG,
"Unexpected SafetySourceData.SeverityLevel in SafetySourceStatus: "
+ safetySourceSeverityLevel);
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
@SafetyCenterIssue.IssueSeverityLevel
private static int toSafetyCenterIssueSeverityLevel(
@SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
switch (safetySourceIssueSeverityLevel) {
case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
Log.w(
TAG,
"Unexpected use of SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED in "
+ "SafetySourceIssue");
return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
case SafetySourceData.SEVERITY_LEVEL_INFORMATION:
return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
case SafetySourceData.SEVERITY_LEVEL_RECOMMENDATION:
return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION;
case SafetySourceData.SEVERITY_LEVEL_CRITICAL_WARNING:
return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING;
}
Log.w(
TAG,
"Unexpected SafetySourceData.SeverityLevel in SafetySourceIssue: "
+ safetySourceIssueSeverityLevel);
return SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK;
}
@SafetyCenterEntry.SeverityUnspecifiedIconType
private static int toGroupSeverityUnspecifiedIconType(
@SafetySourcesGroup.StatelessIconType int statelessIconType) {
switch (statelessIconType) {
case SafetySourcesGroup.STATELESS_ICON_TYPE_NONE:
return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
case SafetySourcesGroup.STATELESS_ICON_TYPE_PRIVACY:
return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_PRIVACY;
}
Log.w(TAG, "Unexpected SafetySourcesGroup.StatelessIconType: " + statelessIconType);
return SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON;
}
@SafetyCenterEntry.IconAction.IconActionType
private static int toSafetyCenterEntryIconActionType(
@SafetySourceStatus.IconAction.IconType int safetySourceIconActionType) {
switch (safetySourceIconActionType) {
case SafetySourceStatus.IconAction.ICON_TYPE_GEAR:
return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_GEAR;
case SafetySourceStatus.IconAction.ICON_TYPE_INFO:
return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO;
}
Log.w(
TAG,
"Unexpected SafetySourceStatus.IconAction.IconActionType: "
+ safetySourceIconActionType);
return SafetyCenterEntry.IconAction.ICON_ACTION_TYPE_INFO;
}
@NonNull
private String getSafetyCenterStatusTitle(
@SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel,
@Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
@SafetyCenterStatus.RefreshStatus int refreshStatus,
boolean hasSettingsToReview) {
boolean overallSeverityUnknown =
overallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
String refreshStatusTitle =
getSafetyCenterRefreshStatusTitle(refreshStatus, overallSeverityUnknown);
if (refreshStatusTitle != null) {
return refreshStatusTitle;
}
switch (overallSeverityLevel) {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
if (hasSettingsToReview) {
return mSafetyCenterResourcesContext.getStringByName(
"overall_severity_level_ok_review_title");
}
return mSafetyCenterResourcesContext.getStringByName(
"overall_severity_level_ok_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
return getStatusTitleFromIssueCategories(
topNonDismissedIssueInfo,
"overall_severity_level_device_recommendation_title",
"overall_severity_level_account_recommendation_title",
"overall_severity_level_safety_recommendation_title",
"overall_severity_level_data_recommendation_title",
"overall_severity_level_passwords_recommendation_title",
"overall_severity_level_personal_recommendation_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getStatusTitleFromIssueCategories(
topNonDismissedIssueInfo,
"overall_severity_level_critical_device_warning_title",
"overall_severity_level_critical_account_warning_title",
"overall_severity_level_critical_safety_warning_title",
"overall_severity_level_critical_data_warning_title",
"overall_severity_level_critical_passwords_warning_title",
"overall_severity_level_critical_personal_warning_title");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
return "";
}
@NonNull
private String getStatusTitleFromIssueCategories(
@Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
@NonNull String deviceResourceName,
@NonNull String accountResourceName,
@NonNull String generalResourceName,
@NonNull String dataResourceName,
@NonNull String passwordsResourceName,
@NonNull String personalSafetyResourceName) {
String generalString = mSafetyCenterResourcesContext.getStringByName(generalResourceName);
if (topNonDismissedIssueInfo == null) {
Log.w(TAG, "No safety center issues found in a non-green status");
return generalString;
}
int issueCategory = topNonDismissedIssueInfo.getSafetySourceIssue().getIssueCategory();
switch (issueCategory) {
case SafetySourceIssue.ISSUE_CATEGORY_DEVICE:
return mSafetyCenterResourcesContext.getStringByName(deviceResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT:
return mSafetyCenterResourcesContext.getStringByName(accountResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_GENERAL:
return generalString;
}
if (SdkLevel.isAtLeastU()) {
switch (issueCategory) {
case SafetySourceIssue.ISSUE_CATEGORY_DATA:
return mSafetyCenterResourcesContext.getStringByName(dataResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
return mSafetyCenterResourcesContext.getStringByName(passwordsResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
return mSafetyCenterResourcesContext.getStringByName(
personalSafetyResourceName);
}
}
Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory);
return generalString;
}
@NonNull
private String getSafetyCenterStatusSummary(
@SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel,
@SafetyCenterStatus.RefreshStatus int refreshStatus,
int numberOfIssues,
boolean hasSettingsToReview) {
String refreshStatusSummary = getSafetyCenterRefreshStatusSummary(refreshStatus);
if (refreshStatusSummary != null) {
return refreshStatusSummary;
}
switch (overallSeverityLevel) {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
if (numberOfIssues == 0) {
if (hasSettingsToReview) {
return mSafetyCenterResourcesContext.getStringByName(
"overall_severity_level_ok_review_summary");
}
return mSafetyCenterResourcesContext.getStringByName(
"overall_severity_level_ok_summary");
}
// Fall through.
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getIcuPluralsString("overall_severity_n_alerts_summary", numberOfIssues);
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
return "";
}
@NonNull
private String getRefreshErrorString(int numberOfErrorEntries) {
return getIcuPluralsString("refresh_error", numberOfErrorEntries);
}
@NonNull
private String getIcuPluralsString(String name, int count, @NonNull Object... formatArgs) {
MessageFormat messageFormat =
new MessageFormat(
mSafetyCenterResourcesContext.getStringByName(name, formatArgs),
Locale.getDefault());
ArrayMap<String, Object> arguments = new ArrayMap<>();
arguments.put("count", count);
return messageFormat.format(arguments);
}
@Nullable
private String getSafetyCenterRefreshStatusTitle(
@SafetyCenterStatus.RefreshStatus int refreshStatus, boolean overallSeverityUnknown) {
switch (refreshStatus) {
case SafetyCenterStatus.REFRESH_STATUS_NONE:
return null;
case SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS:
if (!overallSeverityUnknown) {
return null;
}
// Fall through.
case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
return mSafetyCenterResourcesContext.getStringByName("scanning_title");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
return null;
}
@Nullable
private String getSafetyCenterRefreshStatusSummary(
@SafetyCenterStatus.RefreshStatus int refreshStatus) {
switch (refreshStatus) {
case SafetyCenterStatus.REFRESH_STATUS_NONE:
return null;
case SafetyCenterStatus.REFRESH_STATUS_DATA_FETCH_IN_PROGRESS:
case SafetyCenterStatus.REFRESH_STATUS_FULL_RESCAN_IN_PROGRESS:
return mSafetyCenterResourcesContext.getStringByName("loading_summary");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
return null;
}
@NonNull
private static SafetySourceKey toSafetySourceKey(@NonNull String safetyCenterEntryIdString) {
SafetyCenterEntryId id = SafetyCenterIds.entryIdFromString(safetyCenterEntryIdString);
return SafetySourceKey.of(id.getSafetySourceId(), id.getUserId());
}
/**
* An internal mutable class to keep track of the overall {@link SafetyCenterStatus} severity
* level and whether the list of entries provided requires attention.
*/
private static final class SafetyCenterOverallState {
@SafetyCenterStatus.OverallSeverityLevel
private int mIssuesOverallSeverityLevel = SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
@SafetyCenterStatus.OverallSeverityLevel
private int mEntriesOverallSeverityLevel = SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK;
/**
* Adds a {@link SafetyCenterStatus.OverallSeverityLevel} computed from an issue.
*
* <p>The {@code overallSeverityLevel} provided cannot be {@link
* SafetyCenterStatus#OVERALL_SEVERITY_LEVEL_UNKNOWN}. If the data for an issue is not
* provided yet, this will be reflected when calling {@link
* #addEntryOverallSeverityLevel(int)}. The exception to that are issue-only safety sources
* but since they do not have user-visible entries they do not affect whether the overall
* status is unknown.
*/
private void addIssueOverallSeverityLevel(
@SafetyCenterStatus.OverallSeverityLevel int issueOverallSeverityLevel) {
if (issueOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) {
return;
}
mIssuesOverallSeverityLevel =
mergeOverallSeverityLevels(
mIssuesOverallSeverityLevel, issueOverallSeverityLevel);
}
/**
* Adds a {@link SafetyCenterStatus.OverallSeverityLevel} computed from an entry.
*
* <p>Entries may be unknown (e.g. due to an error or no data provided yet). In this case,
* the overall status will be marked as unknown if there are no recommendations or critical
* issues.
*/
private void addEntryOverallSeverityLevel(
@SafetyCenterStatus.OverallSeverityLevel int entryOverallSeverityLevel) {
mEntriesOverallSeverityLevel =
mergeOverallSeverityLevels(
mEntriesOverallSeverityLevel, entryOverallSeverityLevel);
}
/**
* Returns the {@link SafetyCenterStatus.OverallSeverityLevel} computed.
*
* <p>Returns {@link SafetyCenterStatus#OVERALL_SEVERITY_LEVEL_UNKNOWN} if any entry is
* unknown / has errored-out and there are no recommendations or critical issues.
*
* <p>Otherwise, this is computed based on the maximum severity level of issues.
*/
@SafetyCenterStatus.OverallSeverityLevel
private int getOverallSeverityLevel() {
if (mEntriesOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
&& mIssuesOverallSeverityLevel
<= SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK) {
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
}
return mIssuesOverallSeverityLevel;
}
/**
* Returns whether there are settings to review (i.e. at least one entry has a more severe
* status than the overall status, or if any entry is not yet known / has errored-out).
*/
private boolean hasSettingsToReview() {
return mEntriesOverallSeverityLevel == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
|| mEntriesOverallSeverityLevel > mIssuesOverallSeverityLevel;
}
@SafetyCenterStatus.OverallSeverityLevel
private static int mergeOverallSeverityLevels(
@SafetyCenterStatus.OverallSeverityLevel int left,
@SafetyCenterStatus.OverallSeverityLevel int right) {
if (left == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN
|| right == SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN) {
return SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN;
}
return Math.max(left, right);
}
}
}