blob: e344bf5aa0086e59e4cc52295fa68ba5d8358b30 [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 android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.StringRes;
import android.annotation.UserIdInt;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Resources;
import android.icu.text.ListFormatter;
import android.icu.util.ULocale;
import android.os.Binder;
import android.os.UserHandle;
import android.safetycenter.SafetyCenterData;
import android.safetycenter.SafetyCenterEntry;
import android.safetycenter.SafetyCenterEntryGroup;
import android.safetycenter.SafetyCenterEntryOrGroup;
import android.safetycenter.SafetyCenterErrorDetails;
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;
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 androidx.annotation.RequiresApi;
import com.android.permission.util.UserUtils;
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.resources.SafetyCenterResourcesContext;
import java.util.ArrayList;
import java.util.List;
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.
*
* <p>This class isn't thread safe. Thread safety must be handled by the caller.
*/
@RequiresApi(TIRAMISU)
@NotThreadSafe
final class SafetyCenterDataTracker {
private static final String TAG = "SafetyCenterDataTracker";
private static final String ANDROID_LOCK_SCREEN_SOURCES_ID = "AndroidLockScreenSources";
private final ArrayMap<SafetySourceKey, SafetySourceData> mSafetySourceDataForKey =
new ArrayMap<>();
// TODO(b/221406600): Add persistent storage for dismissed issues.
private final ArraySet<SafetyCenterIssueId> mDismissedSafetyCenterIssues = new ArraySet<>();
private final ArraySet<SafetyCenterIssueActionId> mSafetyCenterIssueActionsInFlight =
new ArraySet<>();
@NonNull private final Context mContext;
@NonNull private final SafetyCenterResourcesContext mSafetyCenterResourcesContext;
@NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
@NonNull private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
/**
* Creates a {@link SafetyCenterDataTracker} using the given {@link Context}, {@link
* SafetyCenterResourcesContext}, {@link SafetyCenterConfigReader} and {@link
* SafetyCenterRefreshTracker}.
*/
SafetyCenterDataTracker(
@NonNull Context context,
@NonNull SafetyCenterResourcesContext safetyCenterResourcesContext,
@NonNull SafetyCenterConfigReader safetyCenterConfigReader,
@NonNull SafetyCenterRefreshTracker safetyCenterRefreshTracker) {
mContext = context;
mSafetyCenterResourcesContext = safetyCenterResourcesContext;
mSafetyCenterConfigReader = safetyCenterConfigReader;
mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
}
/**
* 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.
*/
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 safetyCenterDataHasChanged =
processSafetyEvent(safetySourceId, safetyEvent, userId);
SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key);
if (Objects.equals(safetySourceData, existingSafetySourceData)) {
return safetyCenterDataHasChanged;
}
if (safetySourceData == null) {
mSafetySourceDataForKey.remove(key);
} else {
mSafetySourceDataForKey.put(key, safetySourceData);
}
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;
}
return processSafetyEvent(
safetySourceId, safetySourceErrorDetails.getSafetyEvent(), userId);
}
/**
* Returns a {@link SafetyCenterErrorDetails} based on a {@link SafetySourceErrorDetails}, if
* any should be displayed.
*/
@Nullable
SafetyCenterErrorDetails getSafetyCenterErrorDetails(
@NonNull String safetySourceId,
@NonNull SafetySourceErrorDetails safetySourceErrorDetails) {
if (!mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId)) {
return null;
}
// TODO(b/229080761): Implement proper error message.
return new SafetyCenterErrorDetails("Error reported from source: " + safetySourceId);
}
/**
* Returns the current {@link SafetyCenterData} for the given {@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 getSafetyCenterData(@NonNull UserProfileGroup userProfileGroup) {
return getSafetyCenterData(
mSafetyCenterConfigReader.getSafetySourcesGroups(), userProfileGroup);
}
/** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */
void markSafetyCenterIssueActionAsInFlight(
@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
mSafetyCenterIssueActionsInFlight.add(safetyCenterIssueActionId);
}
/**
* Unmarks the given {@link SafetyCenterIssueActionId} as in-flight and returns whether it
* caused the underlying {@link SafetyCenterData} to change.
*/
boolean unmarkSafetyCenterIssueActionAsInFlight(
@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
return mSafetyCenterIssueActionsInFlight.remove(safetyCenterIssueActionId)
&& getSafetySourceIssueAction(safetyCenterIssueActionId) != null;
}
/** Dismisses the given {@link SafetyCenterIssueId}. */
void dismissSafetyCenterIssue(@NonNull SafetyCenterIssueId safetyCenterIssueId) {
mDismissedSafetyCenterIssues.add(safetyCenterIssueId);
}
/**
* Clears all the {@link SafetySourceData}, dismissed {@link SafetyCenterIssueId}, in flight
* {@link SafetyCenterIssueActionId} and any refresh in progress so far, for all users.
*/
void clear() {
mSafetySourceDataForKey.clear();
mDismissedSafetyCenterIssues.clear();
mSafetyCenterIssueActionsInFlight.clear();
mSafetyCenterRefreshTracker.clearRefresh();
}
/**
* Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueId}.
*
* <p>Returns {@code null} if there is no such {@link SafetySourceIssue}, or if it's been
* dismissed.
*/
@Nullable
SafetySourceIssue getSafetySourceIssue(@NonNull SafetyCenterIssueId safetyCenterIssueId) {
if (isDismissed(safetyCenterIssueId)) {
return null;
}
SafetySourceKey key =
SafetySourceKey.of(
safetyCenterIssueId.getSafetySourceId(), safetyCenterIssueId.getUserId());
SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
if (safetySourceData == null) {
return null;
}
List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
for (int i = 0; i < safetySourceIssues.size(); i++) {
SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
if (safetyCenterIssueId.getSafetySourceIssueId().equals(safetySourceIssue.getId())) {
return safetySourceIssue;
}
}
return null;
}
/**
* 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.getSafetyCenterIssueId());
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.
*/
@NonNull
SafetyCenterData getDefaultSafetyCenterData() {
return new SafetyCenterData(
new SafetyCenterStatus.Builder(
getSafetyCenterStatusTitle(
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN),
getSafetyCenterStatusSummary(
SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN))
.setSeverityLevel(SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN)
.build(),
emptyList(),
emptyList(),
emptyList());
}
private boolean isDismissed(@NonNull SafetyCenterIssueId safetyCenterIssueId) {
return mDismissedSafetyCenterIssues.contains(safetyCenterIssueId);
}
private boolean isInFlight(@NonNull SafetyCenterIssueActionId safetyCenterIssueActionId) {
return mSafetyCenterIssueActionsInFlight.contains(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) {
SafetySource safetySource =
mSafetyCenterConfigReader.getExternalSafetySource(safetySourceId);
if (safetySource == null) {
throw new IllegalArgumentException("Unexpected safety source: " + safetySourceId);
}
// 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);
}
if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY
&& safetySourceData.getStatus() != null) {
throw new IllegalArgumentException(
"Unexpected status for issue only safety source: " + safetySourceId);
}
if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
&& safetySourceData.getStatus() == null) {
throw new IllegalArgumentException(
"Missing status for dynamic safety source: " + safetySourceId);
}
if (safetySourceData.getStatus() != null) {
int sourceSeverityLevel = safetySourceData.getStatus().getSeverityLevel();
int maxSourceSeverityLevel =
Math.max(
SafetySourceData.SEVERITY_LEVEL_INFORMATION,
safetySource.getMaxSeverityLevel());
if (sourceSeverityLevel > maxSourceSeverityLevel) {
throw new IllegalArgumentException(
"Unexpected severity level: "
+ sourceSeverityLevel
+ ", for safety source: "
+ safetySourceId);
}
}
for (int i = 0; i < safetySourceData.getIssues().size(); i++) {
int issueSeverityLevel = safetySourceData.getIssues().get(i).getSeverityLevel();
if (issueSeverityLevel > safetySource.getMaxSeverityLevel()) {
throw new IllegalArgumentException(
"Unexpected severity level: "
+ issueSeverityLevel
+ ", for issue in safety source: "
+ safetySourceId);
}
}
return mSafetyCenterConfigReader.isExternalSafetySourceActive(safetySourceId);
}
private boolean processSafetyEvent(
@NonNull String safetySourceId,
@NonNull SafetyEvent safetyEvent,
@UserIdInt int userId) {
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(
safetySourceId, refreshBroadcastId, userId);
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;
}
SafetyCenterIssueId safetyCenterIssueId =
SafetyCenterIssueId.newBuilder()
.setSafetySourceId(safetySourceId)
.setSafetySourceIssueId(safetySourceIssueId)
.setUserId(userId)
.build();
SafetyCenterIssueActionId safetyCenterIssueActionId =
SafetyCenterIssueActionId.newBuilder()
.setSafetyCenterIssueId(safetyCenterIssueId)
.setSafetySourceIssueActionId(safetySourceIssueActionId)
.build();
return unmarkSafetyCenterIssueActionAsInFlight(safetyCenterIssueActionId);
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,
@NonNull UserProfileGroup userProfileGroup) {
int maxSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
List<SafetyCenterIssue> safetyCenterIssues = new ArrayList<>();
List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups = new ArrayList<>();
List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups = new ArrayList<>();
for (int i = 0; i < safetySourcesGroups.size(); i++) {
SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
int groupSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
switch (safetySourcesGroup.getType()) {
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_COLLAPSIBLE:
groupSafetyCenterEntryLevel =
Math.max(
addSafetyCenterIssues(
safetyCenterIssues,
safetySourcesGroup,
userProfileGroup),
addSafetyCenterEntryGroup(
safetyCenterEntryOrGroups,
safetySourcesGroup,
userProfileGroup));
break;
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID:
addSafetyCenterStaticEntryGroup(
safetyCenterStaticEntryGroups, safetySourcesGroup, userProfileGroup);
break;
case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN:
groupSafetyCenterEntryLevel =
addSafetyCenterIssues(
safetyCenterIssues, safetySourcesGroup, userProfileGroup);
break;
}
maxSafetyCenterEntryLevel =
Math.max(maxSafetyCenterEntryLevel, groupSafetyCenterEntryLevel);
}
// TODO(b/223349473): Reorder safetyCenterIssues based on some criteria.
int safetyCenterOverallSeverityLevel =
entryToSafetyCenterStatusOverallLevel(maxSafetyCenterEntryLevel);
return new SafetyCenterData(
new SafetyCenterStatus.Builder(
getSafetyCenterStatusTitle(safetyCenterOverallSeverityLevel),
getSafetyCenterStatusSummary(safetyCenterOverallSeverityLevel))
.setSeverityLevel(safetyCenterOverallSeverityLevel)
.setRefreshStatus(mSafetyCenterRefreshTracker.getRefreshStatus())
.build(),
safetyCenterIssues,
safetyCenterEntryOrGroups,
safetyCenterStaticEntryGroups);
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterIssues(
@NonNull List<SafetyCenterIssue> safetyCenterIssues,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull UserProfileGroup userProfileGroup) {
int maxSafetyCenterEntrySeverityLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
for (int i = 0; i < safetySources.size(); i++) {
SafetySource safetySource = safetySources.get(i);
if (!SafetySources.isExternal(safetySource)) {
continue;
}
maxSafetyCenterEntrySeverityLevel =
Math.max(
maxSafetyCenterEntrySeverityLevel,
addSafetyCenterIssues(
safetyCenterIssues,
safetySource,
userProfileGroup.getProfileOwnerUserId()));
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
}
int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
for (int j = 0; j < managedProfilesUserIds.length; j++) {
int managedProfileUserId = managedProfilesUserIds[j];
maxSafetyCenterEntrySeverityLevel =
Math.max(
maxSafetyCenterEntrySeverityLevel,
addSafetyCenterIssues(
safetyCenterIssues, safetySource, managedProfileUserId));
}
}
return maxSafetyCenterEntrySeverityLevel;
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterIssues(
@NonNull List<SafetyCenterIssue> safetyCenterIssues,
@NonNull SafetySource safetySource,
@UserIdInt int userId) {
SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId);
SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
if (safetySourceData == null) {
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
int maxSafetyCenterEntrySeverityLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
for (int i = 0; i < safetySourceIssues.size(); i++) {
SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
SafetyCenterIssue safetyCenterIssue =
toSafetyCenterIssue(safetySourceIssue, safetySource, userId);
if (safetyCenterIssue == null) {
continue;
}
maxSafetyCenterEntrySeverityLevel =
Math.max(
maxSafetyCenterEntrySeverityLevel,
issueToSafetyCenterEntryLevel(safetyCenterIssue.getSeverityLevel()));
safetyCenterIssues.add(safetyCenterIssue);
}
return maxSafetyCenterEntrySeverityLevel;
}
@Nullable
private SafetyCenterIssue toSafetyCenterIssue(
@NonNull SafetySourceIssue safetySourceIssue,
@NonNull SafetySource safetySource,
@UserIdInt int userId) {
SafetyCenterIssueId safetyCenterIssueId =
SafetyCenterIssueId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setSafetySourceIssueId(safetySourceIssue.getId())
.setUserId(userId)
.build();
if (isDismissed(safetyCenterIssueId)) {
return null;
}
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));
}
// TODO(b/218817233): Add dismissible and shouldConfirmDismissal. Still TBD by UX: green
// issues won't have confirm on dismiss and red might not be dismissible.
return new SafetyCenterIssue.Builder(
SafetyCenterIds.encodeToString(safetyCenterIssueId),
safetySourceIssue.getTitle(),
safetySourceIssue.getSummary())
.setSeverityLevel(
sourceToSafetyCenterIssueSeverityLevel(
safetySourceIssue.getSeverityLevel()))
.setSubtitle(safetySourceIssue.getSubtitle())
.setActions(safetyCenterIssueActions)
.build();
}
@NonNull
private SafetyCenterIssue.Action toSafetyCenterIssueAction(
@NonNull SafetySourceIssue.Action safetySourceIssueAction,
@NonNull SafetyCenterIssueId safetyCenterIssueId) {
SafetyCenterIssueActionId safetyCenterIssueActionId =
SafetyCenterIssueActionId.newBuilder()
.setSafetyCenterIssueId(safetyCenterIssueId)
.setSafetySourceIssueActionId(safetySourceIssueAction.getId())
.build();
return new SafetyCenterIssue.Action.Builder(
SafetyCenterIds.encodeToString(safetyCenterIssueActionId),
safetySourceIssueAction.getLabel(),
safetySourceIssueAction.getPendingIntent())
.setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
.setIsInFlight(isInFlight(safetyCenterIssueActionId))
.setWillResolve(safetySourceIssueAction.willResolve())
.build();
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterEntryGroup(
@NonNull List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups,
@NonNull SafetySourcesGroup safetySourcesGroup,
@NonNull UserProfileGroup userProfileGroup) {
int maxSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
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);
maxSafetyCenterEntryLevel =
Math.max(
maxSafetyCenterEntryLevel,
addSafetyCenterEntry(
entries,
safetySource,
false,
userProfileGroup.getProfileOwnerUserId()));
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
}
int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
for (int j = 0; j < managedProfilesUserIds.length; j++) {
int managedProfileUserId = managedProfilesUserIds[j];
maxSafetyCenterEntryLevel =
Math.max(
maxSafetyCenterEntryLevel,
addSafetyCenterEntry(
entries, safetySource, true, managedProfileUserId));
}
}
if (entries.size() == 1) {
safetyCenterEntryOrGroups.add(new SafetyCenterEntryOrGroup(entries.get(0)));
} else if (entries.size() > 1) {
SafetyCenterEntryGroupId safetyCenterEntryGroupId =
SafetyCenterEntryGroupId.newBuilder()
.setSafetySourcesGroupId(safetySourcesGroup.getId())
.build();
CharSequence groupSummary = getOptionalString(safetySourcesGroup.getSummaryResId());
if (maxSafetyCenterEntryLevel > 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() == maxSafetyCenterEntryLevel
&& entrySummary != null) {
groupSummary = entrySummary;
break;
}
}
} else if (safetySourcesGroup.getId().equals(ANDROID_LOCK_SCREEN_SOURCES_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.UNITS,
ListFormatter.Width.WIDE)
.format(titles);
}
safetyCenterEntryOrGroups.add(
new SafetyCenterEntryOrGroup(
new SafetyCenterEntryGroup.Builder(
SafetyCenterIds.encodeToString(
safetyCenterEntryGroupId),
getString(safetySourcesGroup.getTitleResId()))
.setSeverityLevel(maxSafetyCenterEntryLevel)
.setSummary(groupSummary)
.setEntries(entries)
.setSeverityUnspecifiedIconType(
toGroupSeverityUnspecifiedIconType(
safetySourcesGroup.getStatelessIconType()))
.build()));
}
return maxSafetyCenterEntryLevel;
}
@SafetyCenterEntry.EntrySeverityLevel
private int addSafetyCenterEntry(
@NonNull List<SafetyCenterEntry> entries,
@NonNull SafetySource safetySource,
boolean isUserManaged,
@UserIdInt int userId) {
SafetyCenterEntry safetyCenterEntry =
toSafetyCenterEntry(safetySource, isUserManaged, userId);
if (safetyCenterEntry == null) {
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
entries.add(safetyCenterEntry);
// TODO(b/228614655): Refine this logic. The entry severity level should not necessarily
// contribute to the overall severity level in case of dismissals.
return safetyCenterEntry.getSeverityLevel();
}
@Nullable
private SafetyCenterEntry toSafetyCenterEntry(
@NonNull SafetySource safetySource, boolean isUserManaged, @UserIdInt int userId) {
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(mSafetySourceDataForKey.get(key));
if (safetySourceStatus != null) {
PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
boolean enabled = safetySourceStatus.isEnabled();
if (pendingIntent == null) {
pendingIntent =
toPendingIntent(
safetySource.getIntentAction(),
safetySource.getPackageName(),
userId);
enabled = enabled && pendingIntent != null;
}
SafetyCenterEntryId safetyCenterEntryId =
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build();
int severityUnspecifiedIconType =
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION;
SafetyCenterEntry.Builder builder =
new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId),
safetySourceStatus.getTitle())
.setSeverityLevel(
sourceToSafetyCenterEntrySeverityLevel(
safetySourceStatus.getSeverityLevel()))
.setSummary(safetySourceStatus.getSummary())
.setEnabled(enabled)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.setPendingIntent(pendingIntent);
SafetySourceStatus.IconAction iconAction = safetySourceStatus.getIconAction();
if (iconAction != null) {
builder.setIconAction(
new SafetyCenterEntry.IconAction(
sourceToSafetyCenterEntryIconActionType(
iconAction.getIconType()),
iconAction.getPendingIntent()));
}
return builder.build();
}
return toDefaultSafetyCenterEntry(
safetySource,
safetySource.getPackageName(),
SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_RECOMMENDATION,
isUserManaged,
userId);
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterEntry(
safetySource,
null,
SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED,
SafetyCenterEntry.SEVERITY_UNSPECIFIED_ICON_TYPE_NO_ICON,
isUserManaged,
userId);
}
Log.w(
TAG,
"Unknown safety source type found in collapsible group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterEntry toDefaultSafetyCenterEntry(
@NonNull SafetySource safetySource,
@Nullable String packageName,
@SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel,
@SafetyCenterEntry.SeverityUnspecifiedIconType int severityUnspecifiedIconType,
boolean isUserManaged,
@UserIdInt int userId) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
SafetyCenterEntryId safetyCenterEntryId =
SafetyCenterEntryId.newBuilder()
.setSafetySourceId(safetySource.getId())
.setUserId(userId)
.build();
PendingIntent pendingIntent =
toPendingIntent(safetySource.getIntentAction(), packageName, userId);
boolean enabled =
pendingIntent != null && !SafetySources.isDefaultEntryDisabled(safetySource);
CharSequence title =
getString(
isUserManaged
? safetySource.getTitleForWorkResId()
: safetySource.getTitleResId());
return new SafetyCenterEntry.Builder(
SafetyCenterIds.encodeToString(safetyCenterEntryId), title)
.setSeverityLevel(entrySeverityLevel)
.setSummary(getOptionalString(safetySource.getSummaryResId()))
.setEnabled(enabled)
.setPendingIntent(pendingIntent)
.setSeverityUnspecifiedIconType(severityUnspecifiedIconType)
.build();
}
private void addSafetyCenterStaticEntryGroup(
@NonNull List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups,
@NonNull SafetySourcesGroup safetySourcesGroup,
@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(
staticEntries, safetySource, false, userProfileGroup.getProfileOwnerUserId());
if (!SafetySources.supportsManagedProfiles(safetySource)) {
continue;
}
int[] managedProfilesUserIds = userProfileGroup.getManagedProfilesUserIds();
for (int j = 0; j < managedProfilesUserIds.length; j++) {
int managedProfileUserId = managedProfilesUserIds[j];
addSafetyCenterStaticEntry(staticEntries, safetySource, true, managedProfileUserId);
}
}
safetyCenterStaticEntryGroups.add(
new SafetyCenterStaticEntryGroup(
getString(safetySourcesGroup.getTitleResId()), staticEntries));
}
private void addSafetyCenterStaticEntry(
@NonNull List<SafetyCenterStaticEntry> staticEntries,
@NonNull SafetySource safetySource,
boolean isUserManaged,
@UserIdInt int userId) {
SafetyCenterStaticEntry staticEntry =
toSafetyCenterStaticEntry(safetySource, isUserManaged, userId);
if (staticEntry == null) {
return;
}
staticEntries.add(staticEntry);
}
@Nullable
private SafetyCenterStaticEntry toSafetyCenterStaticEntry(
@NonNull SafetySource safetySource, boolean isUserManaged, @UserIdInt int userId) {
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(mSafetySourceDataForKey.get(key));
if (safetySourceStatus != null) {
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(), isUserManaged, userId);
case SafetySource.SAFETY_SOURCE_TYPE_STATIC:
return toDefaultSafetyCenterStaticEntry(safetySource, null, isUserManaged, userId);
}
Log.w(TAG, "Unknown safety source type found in rigid group: " + safetySource.getType());
return null;
}
@Nullable
private SafetyCenterStaticEntry toDefaultSafetyCenterStaticEntry(
@NonNull SafetySource safetySource,
@Nullable String packageName,
boolean isUserManaged,
@UserIdInt int userId) {
if (SafetySources.isDefaultEntryHidden(safetySource)) {
return null;
}
PendingIntent pendingIntent =
toPendingIntent(safetySource.getIntentAction(), packageName, userId);
if (pendingIntent == null) {
// TODO(b/222838784): Decide strategy for static entries when the intent is null.
return null;
}
CharSequence title =
getString(
isUserManaged
? safetySource.getTitleForWorkResId()
: safetySource.getTitleResId());
return new SafetyCenterStaticEntry.Builder(title)
.setSummary(getOptionalString(safetySource.getSummaryResId()))
.setPendingIntent(pendingIntent)
.build();
}
@Nullable
private PendingIntent toPendingIntent(
@Nullable String intentAction, @Nullable String packageName, @UserIdInt int userId) {
if (intentAction == null) {
return null;
}
Context context = toPackageContextAsUser(packageName, userId);
if (context == null) {
return null;
}
// TODO(b/222838784): Validate that the intent action is available.
// This call is required for getIntentSender() to be allowed to send as another package.
final long identity = Binder.clearCallingIdentity();
try {
return PendingIntent.getActivity(
context, 0, new Intent(intentAction), PendingIntent.FLAG_IMMUTABLE);
} finally {
Binder.restoreCallingIdentity(identity);
}
}
@Nullable
private Context toPackageContextAsUser(@Nullable String packageName, @UserIdInt int userId) {
String contextPackageName =
packageName == null
// TODO(b/233047525): We should likely use the listener's or caller's
// package name here.
? mContext.getPackageManager().getPermissionControllerPackageName()
: packageName;
// This call requires the INTERACT_ACROSS_USERS permission.
final long identity = Binder.clearCallingIdentity();
try {
return mContext.createPackageContextAsUser(
contextPackageName, 0, UserHandle.of(userId));
} catch (NameNotFoundException e) {
Log.w(TAG, "Package name " + contextPackageName + " not found", e);
return null;
} finally {
Binder.restoreCallingIdentity(identity);
}
}
/**
* Returns a {@link String} resource from the given {@code name}, using the {@link
* SafetyCenterResourcesContext}.
*
* <p>Returns an empty string if the resource cannot be accessed.
*/
@NonNull
private String getStringByName(@NonNull String name) {
String value = mSafetyCenterResourcesContext.getStringByName(name);
if (value == null) {
Log.w(TAG, "String resource \"" + name + "\" not found");
return "";
}
return value;
}
/**
* Returns a {@link String} resource from the given {@code stringId}, using the {@link
* SafetyCenterResourcesContext}.
*
* <p>Throws a {@link Resources.NotFoundException} if the resource cannot be accessed.
*/
@NonNull
private String getString(@StringRes int stringId) {
return mSafetyCenterResourcesContext.getString(stringId);
}
/**
* Returns an optional {@link String} resource from the given {@code stringId}, using the {@link
* SafetyCenterResourcesContext}.
*
* <p>Returns {@code null} if {@code stringId} is equal to {@link Resources#ID_NULL}. Otherwise,
* throws a {@link Resources.NotFoundException} if the resource cannot be accessed.
*/
@Nullable
private String getOptionalString(@StringRes int stringId) {
if (stringId == Resources.ID_NULL) {
return null;
}
return getString(stringId);
}
@Nullable
private static SafetySourceStatus getSafetySourceStatus(
@Nullable SafetySourceData safetySourceData) {
if (safetySourceData == null) {
return null;
}
return safetySourceData.getStatus();
}
@SafetyCenterStatus.OverallSeverityLevel
private static int entryToSafetyCenterStatusOverallLevel(
@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 issueToSafetyCenterEntryLevel(
@SafetyCenterIssue.IssueSeverityLevel int safetyCenterIssueSeverityLevel) {
switch (safetyCenterIssueSeverityLevel) {
case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_OK:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_OK;
case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_RECOMMENDATION:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_RECOMMENDATION;
case SafetyCenterIssue.ISSUE_SEVERITY_LEVEL_CRITICAL_WARNING:
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_CRITICAL_WARNING;
}
Log.w(
TAG,
"Unexpected SafetyCenterIssue.IssueSeverityLevel: "
+ safetyCenterIssueSeverityLevel);
return SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
}
@SafetyCenterEntry.EntrySeverityLevel
private static int sourceToSafetyCenterEntrySeverityLevel(
@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 sourceToSafetyCenterIssueSeverityLevel(
@SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
switch (safetySourceIssueSeverityLevel) {
case SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED:
Log.w(
TAG,
"Unexpected use of SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED in "
+ "SafetySourceStatus");
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 sourceToSafetyCenterEntryIconActionType(
@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) {
switch (overallSeverityLevel) {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
return getStringByName("overall_severity_level_unknown_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
return getStringByName("overall_severity_level_ok_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
return getStringByName("overall_severity_level_recommendation_title");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getStringByName("overall_severity_level_critical_warning_title");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
return "";
}
private String getSafetyCenterStatusSummary(
@SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel) {
switch (overallSeverityLevel) {
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
return getStringByName("overall_severity_level_unknown_summary");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
return getStringByName("overall_severity_level_ok_summary");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
return getStringByName("overall_severity_level_recommendation_summary");
case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
return getStringByName("overall_severity_level_critical_warning_summary");
}
Log.w(TAG, "Unexpected SafetyCenterStatus.OverallSeverityLevel: " + overallSeverityLevel);
return "";
}
}