/*
* 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.UPSIDE_DOWN_CAKE;
import static android.os.Build.VERSION_CODES.VANILLA_ICE_CREAM;
import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_MANAGED;
import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIMARY;
import static com.android.safetycenter.UserProfileGroup.PROFILE_TYPE_PRIVATE;
import static com.android.safetycenter.internaldata.SafetyCenterBundles.ISSUES_TO_GROUPS_BUNDLE_KEY;
import static com.android.safetycenter.internaldata.SafetyCenterBundles.STATIC_ENTRIES_TO_IDS_BUNDLE_KEY;
import static java.util.Collections.emptyList;
import android.annotation.TargetApi;
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.Bundle;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
import android.safetycenter.SafetyCenterEntryOrGroup;
import android.safetycenter.SafetyCenterIssue;
import android.safetycenter.SafetyCenterIssue.Action.ConfirmationDialogDetails;
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.Nullable;
import com.android.modules.utils.build.SdkLevel;
import com.android.permission.flags.Flags;
import com.android.safetycenter.UserProfileGroup.ProfileType;
import com.android.safetycenter.data.SafetyCenterDataManager;
import com.android.safetycenter.internaldata.SafetyCenterBundles;
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.SafetyCenterResourcesApk;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import javax.annotation.concurrent.NotThreadSafe;
/**
* Aggregates {@link SafetySourceData} to build {@link SafetyCenterData} instances which are shared
* with Safety Center listeners, including PermissionController.
*
*
This class isn't thread safe. Thread safety must be handled by the caller.
*
* @hide
*/
@NotThreadSafe
public final class SafetyCenterDataFactory {
private static final String TAG = "SafetyCenterDataFactory";
private static final String ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID = "AndroidLockScreenSources";
private final Context mContext;
private final SafetyCenterResourcesApk mSafetyCenterResourcesApk;
private final SafetyCenterConfigReader mSafetyCenterConfigReader;
private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
private final PendingIntentFactory mPendingIntentFactory;
private final SafetyCenterDataManager mSafetyCenterDataManager;
SafetyCenterDataFactory(
Context context,
SafetyCenterResourcesApk safetyCenterResourcesApk,
SafetyCenterConfigReader safetyCenterConfigReader,
SafetyCenterRefreshTracker safetyCenterRefreshTracker,
PendingIntentFactory pendingIntentFactory,
SafetyCenterDataManager safetyCenterDataManager) {
mContext = context;
mSafetyCenterResourcesApk = safetyCenterResourcesApk;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
mPendingIntentFactory = pendingIntentFactory;
mSafetyCenterDataManager = safetyCenterDataManager;
}
/**
* Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
*/
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.
*
*
If a {@link SafetySourceData} was not set, the default value from the {@link
* SafetyCenterConfig} is used.
*/
SafetyCenterData assembleSafetyCenterData(
String packageName, 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.
*
*
If a {@link SafetySourceData} was not set, the default value from the {@link
* SafetyCenterConfig} is used.
*/
public SafetyCenterData assembleSafetyCenterData(
String packageName,
UserProfileGroup userProfileGroup,
List safetySourcesGroups) {
List safetyCenterEntryOrGroups = new ArrayList<>();
List safetyCenterStaticEntryGroups = new ArrayList<>();
SafetyCenterOverallState safetyCenterOverallState = new SafetyCenterOverallState();
Bundle staticEntriesToIds = new Bundle();
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(
staticEntriesToIds,
safetyCenterOverallState,
safetyCenterStaticEntryGroups,
safetySourcesGroup,
packageName,
userProfileGroup);
break;
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN:
break;
default:
Log.w(TAG, "Unexpected SafetySourceGroupType: " + safetySourcesGroupType);
break;
}
}
List issuesInfo =
mSafetyCenterDataManager.getIssuesDedupedSortedDescFor(userProfileGroup);
List safetyCenterIssues = new ArrayList<>();
List safetyCenterDismissedIssues = new ArrayList<>();
SafetySourceIssueInfo topNonDismissedIssueInfo = null;
int numTipIssues = 0;
int numAutomaticIssues = 0;
Bundle issuesToGroups = new Bundle();
for (int i = 0; i < issuesInfo.size(); i++) {
SafetySourceIssueInfo issueInfo = issuesInfo.get(i);
SafetyCenterIssue safetyCenterIssue =
toSafetyCenterIssue(
issueInfo.getSafetySourceIssue(),
issueInfo.getSafetySourcesGroup(),
issueInfo.getSafetyCenterIssueKey());
if (mSafetyCenterDataManager.isIssueDismissed(
issueInfo.getSafetyCenterIssueKey(),
issueInfo.getSafetySourceIssue().getSeverityLevel())) {
safetyCenterDismissedIssues.add(safetyCenterIssue);
} else {
safetyCenterIssues.add(safetyCenterIssue);
safetyCenterOverallState.addIssueOverallSeverityLevel(
toSafetyCenterStatusOverallSeverityLevel(
issueInfo.getSafetySourceIssue().getSeverityLevel()));
if (topNonDismissedIssueInfo == null) {
topNonDismissedIssueInfo = issueInfo;
}
if (isTip(issueInfo.getSafetySourceIssue())) {
numTipIssues++;
} else if (isAutomatic(issueInfo.getSafetySourceIssue())) {
numAutomaticIssues++;
}
}
if (SdkLevel.isAtLeastU()) {
updateIssuesToGroups(
issuesToGroups,
issueInfo.getSafetyCenterIssueKey(),
safetyCenterIssue.getId());
}
}
int refreshStatus = mSafetyCenterRefreshTracker.getRefreshStatus();
SafetyCenterStatus safetyCenterStatus =
new SafetyCenterStatus.Builder(
getSafetyCenterStatusTitle(
safetyCenterOverallState.getOverallSeverityLevel(),
topNonDismissedIssueInfo,
refreshStatus,
safetyCenterOverallState.hasSettingsToReview()),
getSafetyCenterStatusSummary(
safetyCenterOverallState,
topNonDismissedIssueInfo,
refreshStatus,
numTipIssues,
numAutomaticIssues,
safetyCenterIssues.size()))
.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));
}
Bundle extras = new Bundle();
if (!issuesToGroups.isEmpty()) {
extras.putBundle(ISSUES_TO_GROUPS_BUNDLE_KEY, issuesToGroups);
}
if (!staticEntriesToIds.isEmpty()) {
extras.putBundle(STATIC_ENTRIES_TO_IDS_BUNDLE_KEY, staticEntriesToIds);
}
if (!issuesToGroups.isEmpty() || !staticEntriesToIds.isEmpty()) {
builder.setExtras(extras);
}
return builder.build();
} else {
return new SafetyCenterData(
safetyCenterStatus,
safetyCenterIssues,
safetyCenterEntryOrGroups,
safetyCenterStaticEntryGroups);
}
}
private List getAllGroups() {
return mSafetyCenterConfigReader.getSafetySourcesGroups();
}
private void updateIssuesToGroups(
Bundle issuesToGroups, SafetyCenterIssueKey issueKey, String safetyCenterIssueId) {
Set groups = mSafetyCenterDataManager.getGroupMappingFor(issueKey);
if (!groups.isEmpty()) {
issuesToGroups.putStringArrayList(safetyCenterIssueId, new ArrayList<>(groups));
}
}
private SafetyCenterIssue toSafetyCenterIssue(
SafetySourceIssue safetySourceIssue,
SafetySourcesGroup safetySourcesGroup,
SafetyCenterIssueKey safetyCenterIssueKey) {
SafetyCenterIssueId safetyCenterIssueId =
SafetyCenterIssueId.newBuilder()
.setSafetyCenterIssueKey(safetyCenterIssueKey)
.setIssueTypeId(safetySourceIssue.getIssueTypeId())
.build();
List safetySourceIssueActions = safetySourceIssue.getActions();
List 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())
? mSafetyCenterResourcesApk.getOptionalString(
safetySourcesGroup.getTitleResId())
: safetySourceIssue.getAttributionTitle();
safetyCenterIssueBuilder.setAttributionTitle(issueAttributionTitle);
safetyCenterIssueBuilder.setGroupId(safetySourcesGroup.getId());
}
return safetyCenterIssueBuilder.build();
}
private SafetyCenterIssue.Action toSafetyCenterIssueAction(
SafetySourceIssue.Action safetySourceIssueAction,
SafetyCenterIssueKey safetyCenterIssueKey) {
SafetyCenterIssueActionId safetyCenterIssueActionId =
SafetyCenterIssueActionId.newBuilder()
.setSafetyCenterIssueKey(safetyCenterIssueKey)
.setSafetySourceIssueActionId(safetySourceIssueAction.getId())
.build();
SafetyCenterIssue.Action.Builder builder =
new SafetyCenterIssue.Action.Builder(
SafetyCenterIds.encodeToString(safetyCenterIssueActionId),
safetySourceIssueAction.getLabel(),
safetySourceIssueAction.getPendingIntent())
.setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
.setIsInFlight(
mSafetyCenterDataManager.actionIsInFlight(
safetyCenterIssueActionId))
.setWillResolve(safetySourceIssueAction.willResolve());
if (SdkLevel.isAtLeastU()) {
if (safetySourceIssueAction.getConfirmationDialogDetails() != null) {
SafetySourceIssue.Action.ConfirmationDialogDetails detailsFrom =
safetySourceIssueAction.getConfirmationDialogDetails();
ConfirmationDialogDetails detailsTo =
new ConfirmationDialogDetails(
detailsFrom.getTitle(),
detailsFrom.getText(),
detailsFrom.getAcceptButtonText(),
detailsFrom.getDenyButtonText());
builder.setConfirmationDialogDetails(detailsTo);
}
}
return builder.build();
}
private void addSafetyCenterEntryGroup(
SafetyCenterOverallState safetyCenterOverallState,
List safetyCenterEntryOrGroups,
SafetySourcesGroup safetySourcesGroup,
String defaultPackageName,
UserProfileGroup userProfileGroup) {
int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
List safetySources = safetySourcesGroup.getSafetySources();
List entries = new ArrayList<>(safetySources.size());
for (int safetySourceIdx = 0; safetySourceIdx < safetySources.size(); ++safetySourceIdx) {
SafetySource safetySource = safetySources.get(safetySourceIdx);
for (int profileTypeIdx = 0;
profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length;
++profileTypeIdx) {
@ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx];
if (!SafetySources.supportsProfileType(safetySource, profileType)) {
continue;
}
int[] profileIds = userProfileGroup.getProfilesOfType(profileType);
for (int profileIdx = 0; profileIdx < profileIds.length; profileIdx++) {
int profileId = profileIds[profileIdx];
boolean isUserRunning =
userProfileGroup.containsRunningUserId(profileId, profileType);
if (profileType == PROFILE_TYPE_PRIVATE && !isUserRunning) {
continue;
}
groupSafetyCenterEntryLevel =
mergeSafetyCenterEntrySeverityLevels(
groupSafetyCenterEntryLevel,
addSafetyCenterEntry(
safetyCenterOverallState,
entries,
safetySource,
defaultPackageName,
profileId,
profileType,
isUserRunning));
}
}
}
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(),
mSafetyCenterResourcesApk.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(
SafetySourcesGroup safetySourcesGroup,
@SafetyCenterEntry.EntrySeverityLevel int groupSafetyCenterEntryLevel,
List 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 =
mSafetyCenterDataManager.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:
for (int i = 0; i < entries.size(); i++) {
SafetySourceKey key = toSafetySourceKey(entries.get(i).getId());
if (mSafetyCenterDataManager.sourceHasError(key)) {
return getRefreshErrorString();
}
}
return mSafetyCenterResourcesApk.getStringByName("group_unknown_summary");
}
Log.w(
TAG,
"Unexpected SafetyCenterEntry.EntrySeverityLevel for SafetyCenterEntryGroup: "
+ groupSafetyCenterEntryLevel);
return getDefaultGroupSummary(safetySourcesGroup, entries);
}
@Nullable
private CharSequence getDefaultGroupSummary(
SafetySourcesGroup safetySourcesGroup, List entries) {
CharSequence groupSummary =
mSafetyCenterResourcesApk.getOptionalString(safetySourcesGroup.getSummaryResId());
if (safetySourcesGroup.getId().equals(ANDROID_LOCK_SCREEN_SOURCES_GROUP_ID)
&& TextUtils.isEmpty(groupSummary)) {
List titles = new ArrayList<>();
for (int i = 0; i < entries.size(); i++) {
SafetyCenterEntry entry = entries.get(i);
SafetyCenterEntryId entryId = SafetyCenterIds.entryIdFromString(entry.getId());
if (UserProfileGroup.getProfileTypeOfUser(entryId.getUserId(), mContext)
!= PROFILE_TYPE_PRIMARY) {
continue;
}
titles.add(entry.getTitle());
}
groupSummary =
ListFormatter.getInstance(
ULocale.getDefault(ULocale.Category.FORMAT),
ListFormatter.Type.AND,
ListFormatter.Width.NARROW)
.format(titles);
}
return groupSummary;
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterEntry(
SafetyCenterOverallState safetyCenterOverallState,
List entries,
SafetySource safetySource,
String defaultPackageName,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
SafetyCenterEntry safetyCenterEntry =
toSafetyCenterEntry(
safetySource,
defaultPackageName,
userId,
profileType,
isUserRunning);
if (safetyCenterEntry == null) {
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED;
}
safetyCenterOverallState.addEntryOverallSeverityLevel(
entryToSafetyCenterStatusOverallSeverityLevel(
safetyCenterEntry.getSeverityLevel()));
entries.add(safetyCenterEntry);
return safetyCenterEntry.getSeverityLevel();
}
@Nullable
private SafetyCenterEntry toSafetyCenterEntry(
SafetySource safetySource,
String defaultPackageName,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
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(
mSafetyCenterDataManager.getSafetySourceDataInternal(key));
boolean inQuietMode = (PROFILE_TYPE_MANAGED == profileType) && !isUserRunning;
if (safetySourceStatus == null) {
int severityLevel =
inQuietMode
? SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED
: SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
return toDefaultSafetyCenterEntry(
safetySource,
safetySource.getPackageName(),
severityLevel,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION,
userId,
profileType,
isUserRunning);
}
PendingIntent sourceProvidedPendingIntent =
inQuietMode ? null : safetySourceStatus.getPendingIntent();
PendingIntent entryPendingIntent =
sourceProvidedPendingIntent != null
? sourceProvidedPendingIntent
: mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
safetySource.getPackageName(),
userId,
inQuietMode);
boolean enabled =
safetySourceStatus.isEnabled()
&& !inQuietMode
&& 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(
inQuietMode
? DevicePolicyResources.getWorkProfilePausedString(
mSafetyCenterResourcesApk)
: safetySourceStatus.getSummary())
.setEnabled(enabled)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.setPendingIntent(entryPendingIntent);
SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction();
if (iconAction == null) {
return builder.build();
}
return builder.setIconAction(
new SafetyCenterEntry.IconAction(
toSafetyCenterEntryIconActionType(iconAction.getIconType()),
iconAction.getPendingIntent()))
.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,
profileType,
isUserRunning);
}
Log.w(
TAG,
"Unknown safety source type found in collapsible group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterEntry toDefaultSafetyCenterEntry(
SafetySource safetySource,
String packageName,
@SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel,
@SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
SafetyCenterEntryId safetyCenterEntryId =
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build();
boolean isQuietModeEnabled = (PROFILE_TYPE_MANAGED == profileType) && !isUserRunning;
PendingIntent pendingIntent =
mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
packageName,
userId,
isQuietModeEnabled);
boolean enabled =
pendingIntent != null && !SafetySources.isDefaultEntryDisabled(safetySource);
CharSequence title = getTitleForProfileType(profileType, safetySource);
CharSequence summary =
mSafetyCenterDataManager.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString()
: mSafetyCenterResourcesApk.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
enabled = false;
summary = DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesApk);
}
return new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId), title)
.setSeverityLevel(entrySeverityLevel)
.setSummary(summary)
.setEnabled(enabled)
.setPendingIntent(pendingIntent)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.build();
}
private void addSafetyCenterStaticEntryGroup(
Bundle staticEntriesToIds,
SafetyCenterOverallState safetyCenterOverallState,
List safetyCenterStaticEntryGroups,
SafetySourcesGroup safetySourcesGroup,
String defaultPackageName,
UserProfileGroup userProfileGroup) {
List safetySources = safetySourcesGroup.getSafetySources();
List staticEntries = new ArrayList<>(safetySources.size());
for (int safetySourceIdx = 0; safetySourceIdx < safetySources.size(); safetySourceIdx++) {
SafetySource safetySource = safetySources.get(safetySourceIdx);
for (int profileTypeIdx = 0;
profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length;
++profileTypeIdx) {
@ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx];
if (!SafetySources.supportsProfileType(safetySource, profileType)) {
continue;
}
int[] profileIds = userProfileGroup.getProfilesOfType(profileType);
for (int profileIdx = 0; profileIdx < profileIds.length; ++profileIdx) {
int profileId = profileIds[profileIdx];
boolean isUserRunning =
userProfileGroup.containsRunningUserId(profileId, profileType);
if (profileType == PROFILE_TYPE_PRIVATE && !isUserRunning) {
continue;
}
addSafetyCenterStaticEntry(
staticEntriesToIds,
safetyCenterOverallState,
staticEntries,
safetySource,
defaultPackageName,
profileId,
profileType,
isUserRunning);
}
}
}
if (staticEntries.isEmpty()) {
return;
}
safetyCenterStaticEntryGroups.add(
new SafetyCenterStaticEntryGroup(
mSafetyCenterResourcesApk.getString(safetySourcesGroup.getTitleResId()),
staticEntries));
}
private void addSafetyCenterStaticEntry(
Bundle staticEntriesToIds,
SafetyCenterOverallState safetyCenterOverallState,
List staticEntries,
SafetySource safetySource,
String defaultPackageName,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
SafetyCenterStaticEntry staticEntry =
toSafetyCenterStaticEntry(
safetySource,
defaultPackageName,
userId,
profileType,
isUserRunning);
if (staticEntry == null) {
return;
}
if (SdkLevel.isAtLeastU()) {
staticEntriesToIds.putString(
SafetyCenterBundles.toBundleKey(staticEntry),
SafetyCenterIds.encodeToString(
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build()));
}
boolean hasError =
mSafetyCenterDataManager.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId));
if (hasError) {
safetyCenterOverallState.addEntryOverallSeverityLevel(
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN);
}
staticEntries.add(staticEntry);
}
@Nullable
private SafetyCenterStaticEntry toSafetyCenterStaticEntry(
SafetySource safetySource,
String defaultPackageName,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
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(
mSafetyCenterDataManager.getSafetySourceDataInternal(key));
boolean inQuietMode = (profileType == PROFILE_TYPE_MANAGED) && !isUserRunning;
if (safetySourceStatus != null) {
PendingIntent sourceProvidedPendingIntent =
inQuietMode ? null : safetySourceStatus.getPendingIntent();
PendingIntent entryPendingIntent =
sourceProvidedPendingIntent != null
? sourceProvidedPendingIntent
: mPendingIntentFactory.getPendingIntent(
safetySource.getId(),
safetySource.getIntentAction(),
safetySource.getPackageName(),
userId,
inQuietMode);
if (entryPendingIntent == null) {
// TODO(b/222838784): Decide strategy for static entries when the intent is
// null.
return null;
}
return new SafetyCenterStaticEntry.Builder(safetySourceStatus.getTitle())
.setSummary(
inQuietMode
? DevicePolicyResources.getWorkProfilePausedString(
mSafetyCenterResourcesApk)
: safetySourceStatus.getSummary())
.setPendingIntent(entryPendingIntent)
.build();
}
return toDefaultSafetyCenterStaticEntry(
safetySource,
safetySource.getPackageName(),
userId,
profileType,
isUserRunning);
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterStaticEntry(
safetySource,
getStaticSourcePackageNameOrDefault(safetySource, defaultPackageName),
userId,
profileType,
isUserRunning);
}
Log.w(TAG, "Unknown safety source type found in rigid group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterStaticEntry toDefaultSafetyCenterStaticEntry(
SafetySource safetySource,
String packageName,
@UserIdInt int userId,
@ProfileType int profileType,
boolean isUserRunning) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
boolean isQuietModeEnabled = (profileType == PROFILE_TYPE_MANAGED) && !isUserRunning;
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 = getTitleForProfileType(profileType, safetySource);
CharSequence summary =
mSafetyCenterDataManager.sourceHasError(
SafetySourceKey.of(safetySource.getId(), userId))
? getRefreshErrorString()
: mSafetyCenterResourcesApk.getOptionalString(
safetySource.getSummaryResId());
if (isQuietModeEnabled) {
summary = DevicePolicyResources.getWorkProfilePausedString(mSafetyCenterResourcesApk);
}
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();
}
private static String getStaticSourcePackageNameOrDefault(
SafetySource safetySource, 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_RECOMMENDATION;
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;
}
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 mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_title");
}
return mSafetyCenterResourcesApk.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 "";
}
@TargetApi(UPSIDE_DOWN_CAKE)
private String getStatusTitleFromIssueCategories(
@Nullable SafetySourceIssueInfo topNonDismissedIssueInfo,
String deviceResourceName,
String accountResourceName,
String generalResourceName,
String dataResourceName,
String passwordsResourceName,
String personalSafetyResourceName) {
String generalString = mSafetyCenterResourcesApk.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 mSafetyCenterResourcesApk.getStringByName(deviceResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_ACCOUNT:
return mSafetyCenterResourcesApk.getStringByName(accountResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_GENERAL:
return generalString;
case SafetySourceIssue.ISSUE_CATEGORY_DATA:
return mSafetyCenterResourcesApk.getStringByName(dataResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_PASSWORDS:
return mSafetyCenterResourcesApk.getStringByName(passwordsResourceName);
case SafetySourceIssue.ISSUE_CATEGORY_PERSONAL_SAFETY:
return mSafetyCenterResourcesApk.getStringByName(personalSafetyResourceName);
}
Log.w(TAG, "Unexpected SafetySourceIssue.IssueCategory: " + issueCategory);
return generalString;
}
private String getSafetyCenterStatusSummary(
SafetyCenterOverallState safetyCenterOverallState,
@Nullable SafetySourceIssueInfo topNonDismissedIssue,
@SafetyCenterStatus.RefreshStatus int refreshStatus,
int numTipIssues,
int numAutomaticIssues,
int numIssues) {
String refreshStatusSummary = getSafetyCenterRefreshStatusSummary(refreshStatus);
if (refreshStatusSummary != null) {
return refreshStatusSummary;
}
int overallSeverityLevel = safetyCenterOverallState.getOverallSeverityLevel();
switch (overallSeverityLevel) {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
if (topNonDismissedIssue == null) {
if (safetyCenterOverallState.hasSettingsToReview()) {
return mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_review_summary");
}
return mSafetyCenterResourcesApk.getStringByName(
"overall_severity_level_ok_summary");
} else if (isTip(topNonDismissedIssue.getSafetySourceIssue())) {
return getIcuPluralsString("overall_severity_level_tip_summary", numTipIssues);
} else if (isAutomatic(topNonDismissedIssue.getSafetySourceIssue())) {
return getIcuPluralsString(
"overall_severity_level_action_taken_summary", numAutomaticIssues);
}
// Fall through.
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getIcuPluralsString("overall_severity_n_alerts_summary", numIssues);
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
return "";
}
private static boolean isTip(SafetySourceIssue safetySourceIssue) {
return SdkLevel.isAtLeastU()
&& safetySourceIssue.getIssueActionability()
== SafetySourceIssue.ISSUE_ACTIONABILITY_TIP;
}
private static boolean isAutomatic(SafetySourceIssue safetySourceIssue) {
return SdkLevel.isAtLeastU()
&& safetySourceIssue.getIssueActionability()
== SafetySourceIssue.ISSUE_ACTIONABILITY_AUTOMATIC;
}
private String getRefreshErrorString() {
return getIcuPluralsString("refresh_error", /* count= */ 1);
}
private String getIcuPluralsString(String name, int count, Object... formatArgs) {
MessageFormat messageFormat =
new MessageFormat(
mSafetyCenterResourcesApk.getStringByName(name, formatArgs),
Locale.getDefault());
ArrayMap 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 mSafetyCenterResourcesApk.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 mSafetyCenterResourcesApk.getStringByName("loading_summary");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.RefreshStatus: " + refreshStatus);
return null;
}
private CharSequence getTitleForProfileType(
@ProfileType int profileType, SafetySource safetySource) {
switch (profileType) {
case PROFILE_TYPE_PRIMARY:
return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
case PROFILE_TYPE_MANAGED:
return DevicePolicyResources.getSafetySourceWorkString(
mSafetyCenterResourcesApk,
safetySource.getId(),
safetySource.getTitleForWorkResId());
case PROFILE_TYPE_PRIVATE:
if (SdkLevel.isAtLeastV() && Flags.privateProfileTitleApi()) {
return mSafetyCenterResourcesApk.getString(
safetySource.getTitleForPrivateProfileResId());
}
Log.w(TAG, "unsupported private profile type encountered");
return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
default:
Log.w(TAG, "unexpected value for the profile type " + profileType);
return mSafetyCenterResourcesApk.getString(safetySource.getTitleResId());
}
}
private static SafetySourceKey toSafetySourceKey(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.
*
* 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.
*
*
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.
*
*
Returns {@link SafetyCenterStatus#OVERALL_SEVERITY_LEVEL_UNKNOWN} if any entry is
* unknown / has errored-out and there are no recommendations or critical issues.
*
*
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);
}
}
}