708 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Java
		
	
	
	
			
		
		
	
	
			708 lines
		
	
	
		
			31 KiB
		
	
	
	
		
			Java
		
	
	
	
| /*
 | |
|  * 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.UserIdInt;
 | |
| import android.app.PendingIntent;
 | |
| import android.content.Context;
 | |
| import android.content.Intent;
 | |
| import android.content.pm.PackageManager.NameNotFoundException;
 | |
| import android.os.Binder;
 | |
| 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.util.Log;
 | |
| 
 | |
| import androidx.annotation.RequiresApi;
 | |
| 
 | |
| import java.util.ArrayList;
 | |
| import java.util.HashMap;
 | |
| import java.util.List;
 | |
| import java.util.Map;
 | |
| import java.util.Objects;
 | |
| 
 | |
| /**
 | |
|  * 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 permission controller.
 | |
|  *
 | |
|  * <p>This class isn't thread safe. Thread safety must be handled by the caller.
 | |
|  */
 | |
| @RequiresApi(TIRAMISU)
 | |
| final class SafetyCenterDataTracker {
 | |
| 
 | |
|     private static final String TAG = "SafetyCenterDataTracker";
 | |
| 
 | |
|     private final Map<Key, SafetySourceData> mSafetySourceDataForKey = new HashMap<>();
 | |
| 
 | |
|     @NonNull private final Context mContext;
 | |
|     @NonNull private final SafetyCenterConfigReader mSafetyCenterConfigReader;
 | |
| 
 | |
|     /**
 | |
|      * Creates a {@link SafetyCenterDataTracker} using the given {@link Context} and {@link
 | |
|      * SafetyCenterConfigReader}.
 | |
|      */
 | |
|     SafetyCenterDataTracker(
 | |
|             @NonNull Context context, @NonNull SafetyCenterConfigReader safetyCenterConfigReader) {
 | |
|         mContext = context;
 | |
|         mSafetyCenterConfigReader = safetyCenterConfigReader;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId} and {@code
 | |
|      * userId}, and returns the updated {@link SafetyCenterData} of the {@code userId}.
 | |
|      *
 | |
|      * <p>Returns {@code null} if there was no update to the underlying {@link SafetyCenterData}, or
 | |
|      * if the {@link SafetyCenterConfig} is not available.
 | |
|      */
 | |
|     @Nullable
 | |
|     SafetyCenterData setSafetySourceData(
 | |
|             @NonNull String safetySourceId,
 | |
|             @Nullable SafetySourceData safetySourceData,
 | |
|             @NonNull String packageName,
 | |
|             @UserIdInt int userId) {
 | |
|         if (!configContains(safetySourceId, packageName)) {
 | |
|             // TODO(b/218801292): Should this be hard error for the caller?
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         Key key = Key.of(safetySourceId, packageName, userId);
 | |
|         SafetySourceData existingSafetySourceData = mSafetySourceDataForKey.get(key);
 | |
|         if (Objects.equals(safetySourceData, existingSafetySourceData)) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         mSafetySourceDataForKey.put(key, safetySourceData);
 | |
|         return getSafetyCenterData(userId);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the latest {@link SafetySourceData} for the given {@code safetySourceId} and {@code
 | |
|      * userId}.
 | |
|      *
 | |
|      * <p>Returns {@code null} if there was no data set.
 | |
|      */
 | |
|     @Nullable
 | |
|     SafetySourceData getSafetySourceData(
 | |
|             @NonNull String safetySourceId, @NonNull String packageName, @UserIdInt int userId) {
 | |
|         if (!configContains(safetySourceId, packageName)) {
 | |
|             // TODO(b/218801292): Should this be hard error for the caller?
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         return mSafetySourceDataForKey.get(Key.of(safetySourceId, packageName, userId));
 | |
|     }
 | |
| 
 | |
|     /** Clears all the {@link SafetySourceData} set received so far, for all users. */
 | |
|     void clear() {
 | |
|         mSafetySourceDataForKey.clear();
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns the current {@link SafetyCenterData} for the given {@code userId}, aggregated from
 | |
|      * all the {@link SafetySourceData} set so far.
 | |
|      *
 | |
|      * <p>Returns an arbitrary default value if no data has been received for the user so far, or if
 | |
|      * the {@link SafetyCenterConfig} is not available.
 | |
|      */
 | |
|     @NonNull
 | |
|     SafetyCenterData getSafetyCenterData(@UserIdInt int userId) {
 | |
|         SafetyCenterConfig safetyCenterConfig = mSafetyCenterConfigReader.getSafetyCenterConfig();
 | |
|         if (safetyCenterConfig == null) {
 | |
|             Log.w(TAG, "SafetyCenterConfig unavailable, returning default SafetyCenterData");
 | |
|             return getDefaultSafetyCenterData();
 | |
|         }
 | |
| 
 | |
|         // TODO(b/218819144): Merge for all profiles.
 | |
|         return getSafetyCenterData(safetyCenterConfig, userId);
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Returns a default {@link SafetyCenterData} object to be returned when the API is disabled.
 | |
|      */
 | |
|     @NonNull
 | |
|     static 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());
 | |
|     }
 | |
| 
 | |
|     // TODO(b/219702252): Create a more efficient data structure for this, and update it when the
 | |
|     //  config changes.
 | |
|     private boolean configContains(@NonNull String safetySourceId, @NonNull String packageName) {
 | |
|         SafetyCenterConfig safetyCenterConfig = mSafetyCenterConfigReader.getSafetyCenterConfig();
 | |
|         if (safetyCenterConfig == null) {
 | |
|             Log.w(TAG, "SafetyCenterConfig unavailable, assuming no sources can send/get data");
 | |
|             return false;
 | |
|         }
 | |
| 
 | |
|         // TODO(b/217944317): Remove this allowlisting once the test API for the config is
 | |
|         //  available.
 | |
|         if (packageName.equals("android.safetycenter.cts")) {
 | |
|             return true;
 | |
|         }
 | |
| 
 | |
|         List<SafetySourcesGroup> safetySourcesGroups = safetyCenterConfig.getSafetySourcesGroups();
 | |
|         for (int i = 0; i < safetySourcesGroups.size(); i++) {
 | |
|             SafetySourcesGroup safetySourcesGroup = safetySourcesGroups.get(i);
 | |
| 
 | |
|             List<SafetySource> safetySources = safetySourcesGroup.getSafetySources();
 | |
|             for (int j = 0; j < safetySources.size(); j++) {
 | |
|                 SafetySource safetySource = safetySources.get(j);
 | |
| 
 | |
|                 if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
 | |
|                     continue;
 | |
|                 }
 | |
| 
 | |
|                 if (safetySourceId.equals(safetySource.getId())
 | |
|                         && packageName.equals(safetySource.getPackageName())) {
 | |
|                     return true;
 | |
|                 }
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return false;
 | |
|     }
 | |
| 
 | |
|     @NonNull
 | |
|     private SafetyCenterData getSafetyCenterData(
 | |
|             @NonNull SafetyCenterConfig safetyCenterConfig, @UserIdInt int userId) {
 | |
|         int maxSafetyCenterEntryLevel = SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN;
 | |
|         List<SafetyCenterIssue> safetyCenterIssues = new ArrayList<>();
 | |
|         List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups = new ArrayList<>();
 | |
|         List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups = new ArrayList<>();
 | |
| 
 | |
|         List<SafetySourcesGroup> safetySourcesGroups = safetyCenterConfig.getSafetySourcesGroups();
 | |
|         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, userId),
 | |
|                                     addSafetyCenterEntryGroup(
 | |
|                                             safetyCenterEntryOrGroups,
 | |
|                                             safetySourcesGroup,
 | |
|                                             userId));
 | |
|                     break;
 | |
|                 }
 | |
|                 case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_RIGID: {
 | |
|                     addSafetyCenterStaticEntryGroup(
 | |
|                             safetyCenterStaticEntryGroups, safetySourcesGroup);
 | |
|                     break;
 | |
|                 }
 | |
|                 case SafetySourcesGroup.SAFETY_SOURCES_GROUP_TYPE_HIDDEN: {
 | |
|                     groupSafetyCenterEntryLevel =
 | |
|                             addSafetyCenterIssues(
 | |
|                                     safetyCenterIssues, safetySourcesGroup, userId);
 | |
|                     break;
 | |
|                 }
 | |
|             }
 | |
|             // TODO(b/219700241): Should we rely on ordering for severity levels?
 | |
|             maxSafetyCenterEntryLevel =
 | |
|                     Math.max(maxSafetyCenterEntryLevel, groupSafetyCenterEntryLevel);
 | |
|         }
 | |
| 
 | |
|         int safetyCenterOverallSeverityLevel =
 | |
|                 entryToSafetyCenterStatusOverallLevel(maxSafetyCenterEntryLevel);
 | |
|         return new SafetyCenterData(
 | |
|                 new SafetyCenterStatus.Builder(
 | |
|                                 getSafetyCenterStatusTitle(safetyCenterOverallSeverityLevel),
 | |
|                                 getSafetyCenterStatusSummary(safetyCenterOverallSeverityLevel))
 | |
|                         .setSeverityLevel(safetyCenterOverallSeverityLevel)
 | |
|                         .build(),
 | |
|                 safetyCenterIssues,
 | |
|                 safetyCenterEntryOrGroups,
 | |
|                 safetyCenterStaticEntryGroups);
 | |
|     }
 | |
| 
 | |
|     @SafetyCenterEntry.EntrySeverityLevel
 | |
|     private int addSafetyCenterIssues(
 | |
|             @NonNull List<SafetyCenterIssue> safetyCenterIssues,
 | |
|             @NonNull SafetySourcesGroup safetySourcesGroup,
 | |
|             @UserIdInt int userId) {
 | |
|         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 (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             Key key = Key.of(safetySource.getId(), safetySource.getPackageName(), userId);
 | |
|             SafetySourceData safetySourceData = mSafetySourceDataForKey.get(key);
 | |
|             if (safetySourceData == null) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
 | |
|             for (int j = 0; j < safetySourceIssues.size(); j++) {
 | |
|                 SafetySourceIssue safetySourceIssue = safetySourceIssues.get(j);
 | |
| 
 | |
|                 SafetyCenterIssue safetyCenterIssue = toSafetyCenterIssue(safetySourceIssue);
 | |
|                 maxSafetyCenterEntrySeverityLevel =
 | |
|                         Math.max(
 | |
|                                 maxSafetyCenterEntrySeverityLevel,
 | |
|                                 issueToSafetyCenterEntryLevel(
 | |
|                                         safetyCenterIssue.getSeverityLevel()));
 | |
|                 safetyCenterIssues.add(safetyCenterIssue);
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return maxSafetyCenterEntrySeverityLevel;
 | |
|     }
 | |
| 
 | |
|     @NonNull
 | |
|     private static SafetyCenterIssue toSafetyCenterIssue(
 | |
|             @NonNull SafetySourceIssue safetySourceIssue) {
 | |
|         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(
 | |
|                     new SafetyCenterIssue.Action.Builder(
 | |
|                                     safetySourceIssue.getId(),
 | |
|                                     safetySourceIssueAction.getLabel(),
 | |
|                                     safetySourceIssueAction.getPendingIntent())
 | |
|                             .setSuccessMessage(safetySourceIssueAction.getSuccessMessage())
 | |
|                             .build());
 | |
|         }
 | |
| 
 | |
|         // TODO(b/218817233): Add missing fields like: dismissible, shouldConfirmDismissal.
 | |
|         return new SafetyCenterIssue.Builder(
 | |
|                         safetySourceIssue.getId(),
 | |
|                         safetySourceIssue.getTitle(),
 | |
|                         safetySourceIssue.getSummary())
 | |
|                 .setSeverityLevel(
 | |
|                         sourceToSafetyCenterIssueSeverityLevel(
 | |
|                                 safetySourceIssue.getSeverityLevel()))
 | |
|                 .setSubtitle(safetySourceIssue.getSubtitle())
 | |
|                 .setActions(safetyCenterIssueActions)
 | |
|                 .build();
 | |
|     }
 | |
| 
 | |
|     @SafetyCenterEntry.EntrySeverityLevel
 | |
|     private int addSafetyCenterEntryGroup(
 | |
|             @NonNull List<SafetyCenterEntryOrGroup> safetyCenterEntryOrGroups,
 | |
|             @NonNull SafetySourcesGroup safetySourcesGroup,
 | |
|             @UserIdInt int userId) {
 | |
|         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);
 | |
| 
 | |
|             SafetyCenterEntry safetyCenterEntry = toSafetyCenterEntry(safetySource, userId);
 | |
|             if (safetyCenterEntry == null) {
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             // TODO(b/219700241): Should we rely on ordering for severity levels?
 | |
|             maxSafetyCenterEntryLevel =
 | |
|                     Math.max(maxSafetyCenterEntryLevel, safetyCenterEntry.getSeverityLevel());
 | |
|             entries.add(safetyCenterEntry);
 | |
|         }
 | |
| 
 | |
|         // TODO(b/218817233): Add missing fields like: statelessIconType.
 | |
|         safetyCenterEntryOrGroups.add(
 | |
|                 new SafetyCenterEntryOrGroup(
 | |
|                         new SafetyCenterEntryGroup.Builder(
 | |
|                                         safetySourcesGroup.getId(),
 | |
|                                         mSafetyCenterConfigReader.readStringResource(
 | |
|                                                 safetySourcesGroup.getTitleResId()))
 | |
|                                 .setSeverityLevel(maxSafetyCenterEntryLevel)
 | |
|                                 .setSummary(
 | |
|                                         mSafetyCenterConfigReader.readStringResource(
 | |
|                                                 safetySourcesGroup.getSummaryResId()))
 | |
|                                 .setEntries(entries)
 | |
|                                 .build()));
 | |
| 
 | |
|         return maxSafetyCenterEntryLevel;
 | |
|     }
 | |
| 
 | |
|     @Nullable
 | |
|     private SafetyCenterEntry toSafetyCenterEntry(
 | |
|             @NonNull SafetySource safetySource, @UserIdInt int userId) {
 | |
|         switch (safetySource.getType()) {
 | |
|             case SafetySource.SAFETY_SOURCE_TYPE_ISSUE_ONLY: {
 | |
|                 Log.w(TAG, "Issue only safety source found in collapsible group");
 | |
|                 return null;
 | |
|             }
 | |
|             case SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC: {
 | |
|                 Key key = Key.of(safetySource.getId(), safetySource.getPackageName(), userId);
 | |
|                 SafetySourceStatus safetySourceStatus =
 | |
|                         getSafetySourceStatus(mSafetySourceDataForKey.get(key));
 | |
|                 // TODO(b/218817233): Add missing fields like: iconAction, statelessIconType.
 | |
|                 if (safetySourceStatus != null) {
 | |
|                     PendingIntent pendingIntent = safetySourceStatus.getPendingIntent();
 | |
|                     if (pendingIntent == null) {
 | |
|                         pendingIntent =
 | |
|                                 toPendingIntent(
 | |
|                                         safetySource.getIntentAction(),
 | |
|                                         safetySource.getPackageName());
 | |
|                         // TODO(b/222838784): Automatically mark the source as disabled if the
 | |
|                         //  pending intent is null again.
 | |
|                     }
 | |
|                     return new SafetyCenterEntry.Builder(
 | |
|                                     safetySource.getId(), safetySourceStatus.getTitle())
 | |
|                             .setSeverityLevel(
 | |
|                                     sourceToSafetyCenterEntrySeverityLevel(
 | |
|                                             safetySourceStatus.getSeverityLevel()))
 | |
|                             .setSummary(safetySourceStatus.getSummary())
 | |
|                             .setEnabled(safetySourceStatus.isEnabled())
 | |
|                             .setPendingIntent(pendingIntent)
 | |
|                             .build();
 | |
|                 }
 | |
|                 return toDefaultSafetyCenterEntry(
 | |
|                         safetySource,
 | |
|                         safetySource.getPackageName(),
 | |
|                         SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNSPECIFIED);
 | |
|             }
 | |
|             case SafetySource.SAFETY_SOURCE_TYPE_STATIC: {
 | |
|                 return toDefaultSafetyCenterEntry(
 | |
|                         safetySource, null, SafetyCenterEntry.ENTRY_SEVERITY_LEVEL_UNKNOWN);
 | |
|             }
 | |
|         }
 | |
|         Log.w(
 | |
|                 TAG,
 | |
|                 String.format(
 | |
|                         "Unknown safety source type found in collapsible group: %s",
 | |
|                         safetySource.getType()));
 | |
|         return null;
 | |
|     }
 | |
| 
 | |
|     @Nullable
 | |
|     private SafetyCenterEntry toDefaultSafetyCenterEntry(
 | |
|             @NonNull SafetySource safetySource,
 | |
|             @Nullable String packageName,
 | |
|             @SafetyCenterEntry.EntrySeverityLevel int entrySeverityLevel) {
 | |
|         if (safetySource.getType() == SafetySource.SAFETY_SOURCE_TYPE_DYNAMIC
 | |
|                 && safetySource.getInitialDisplayState()
 | |
|                         == SafetySource.INITIAL_DISPLAY_STATE_HIDDEN) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         PendingIntent pendingIntent = toPendingIntent(safetySource.getIntentAction(), packageName);
 | |
| 
 | |
|         // TODO(b/218817233): Add missing fields like: enabled.
 | |
|         // TODO(b/222838784): Automatically mark the source as disabled (both dynamic and static?)
 | |
|         //  if the pending intent is null.
 | |
|         return new SafetyCenterEntry.Builder(
 | |
|                         safetySource.getId(),
 | |
|                         mSafetyCenterConfigReader.readStringResource(safetySource.getTitleResId()))
 | |
|                 .setSeverityLevel(entrySeverityLevel)
 | |
|                 .setSummary(
 | |
|                         mSafetyCenterConfigReader.readStringResource(
 | |
|                                 safetySource.getSummaryResId()))
 | |
|                 .setPendingIntent(pendingIntent)
 | |
|                 .build();
 | |
|     }
 | |
| 
 | |
|     private void addSafetyCenterStaticEntryGroup(
 | |
|             @NonNull List<SafetyCenterStaticEntryGroup> safetyCenterStaticEntryGroups,
 | |
|             @NonNull SafetySourcesGroup safetySourcesGroup) {
 | |
|         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);
 | |
| 
 | |
|             if (safetySource.getType() != SafetySource.SAFETY_SOURCE_TYPE_STATIC) {
 | |
|                 Log.w(TAG, "Non-static safety source found in rigid group");
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             PendingIntent pendingIntent = toPendingIntent(safetySource.getIntentAction(), null);
 | |
|             if (pendingIntent == null) {
 | |
|                 // TODO(b/222838784): Decide strategy for static entries when the intent is null.
 | |
|                 continue;
 | |
|             }
 | |
| 
 | |
|             staticEntries.add(
 | |
|                     new SafetyCenterStaticEntry.Builder(
 | |
|                                     mSafetyCenterConfigReader.readStringResource(
 | |
|                                             safetySource.getTitleResId()))
 | |
|                             .setSummary(
 | |
|                                     mSafetyCenterConfigReader.readStringResource(
 | |
|                                             safetySource.getSummaryResId()))
 | |
|                             .setPendingIntent(pendingIntent)
 | |
|                             .build());
 | |
|         }
 | |
| 
 | |
|         safetyCenterStaticEntryGroups.add(
 | |
|                 new SafetyCenterStaticEntryGroup(
 | |
|                         mSafetyCenterConfigReader.readStringResource(
 | |
|                                 safetySourcesGroup.getTitleResId()),
 | |
|                         staticEntries));
 | |
|     }
 | |
| 
 | |
|     @Nullable
 | |
|     private PendingIntent toPendingIntent(
 | |
|             @Nullable String intentAction, @Nullable String packageName) {
 | |
|         if (intentAction == null) {
 | |
|             return null;
 | |
|         }
 | |
| 
 | |
|         Context context;
 | |
|         if (packageName == null) {
 | |
|             context = mContext;
 | |
|         } else {
 | |
|             final long identity = Binder.clearCallingIdentity();
 | |
|             try {
 | |
|                 context = mContext.createPackageContext(packageName, 0);
 | |
|             } catch (NameNotFoundException e) {
 | |
|                 Log.w(TAG, String.format("Package name %s not found", packageName), e);
 | |
|                 return null;
 | |
|             } finally {
 | |
|                 Binder.restoreCallingIdentity(identity);
 | |
|             }
 | |
|         }
 | |
|         // TODO(b/222838784): Validate that the intent action is available.
 | |
| 
 | |
|         // TODO(b/219699223): Is it safe to create a PendingIntent as system server here?
 | |
|         final long identity = Binder.clearCallingIdentity();
 | |
|         try {
 | |
|             // TODO(b/218816518): May need to create a unique requestCode per PendingIntent.
 | |
|             return PendingIntent.getActivity(
 | |
|                     context, 0, new Intent(intentAction), PendingIntent.FLAG_IMMUTABLE);
 | |
|         } finally {
 | |
|             Binder.restoreCallingIdentity(identity);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     @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;
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetyCenterEntry.EntrySeverityLevel: %s",
 | |
|                         safetyCenterEntrySeverityLevel));
 | |
|     }
 | |
| 
 | |
|     @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;
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetyCenterIssue.IssueSeverityLevel: %s",
 | |
|                         safetyCenterIssueSeverityLevel));
 | |
|     }
 | |
| 
 | |
|     @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;
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetySourceSeverity.Level in SafetySourceStatus: %s",
 | |
|                         safetySourceSeverityLevel));
 | |
|     }
 | |
| 
 | |
|     @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;
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetySourceSeverity.Level in SafetySourceIssue: %s",
 | |
|                         safetySourceIssueSeverityLevel));
 | |
|     }
 | |
| 
 | |
|     // TODO(b/218801295): Use the right strings and localize them.
 | |
|     private static String getSafetyCenterStatusTitle(
 | |
|             @SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel) {
 | |
|         switch (overallSeverityLevel) {
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
 | |
|                 return "Unknown";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
 | |
|                 return "All good";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
 | |
|                 return "Some warnings";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
 | |
|                 return "Uh-oh";
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetyCenterStatus.OverallSeverityLevel: %s",
 | |
|                         overallSeverityLevel));
 | |
|     }
 | |
| 
 | |
|     // TODO(b/218801295): Use the right strings and localize them.
 | |
|     private static String getSafetyCenterStatusSummary(
 | |
|             @SafetyCenterStatus.OverallSeverityLevel int overallSeverityLevel) {
 | |
|         switch (overallSeverityLevel) {
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_UNKNOWN:
 | |
|                 return "Unknown safety status";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_OK:
 | |
|                 return "No problemo maestro";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_RECOMMENDATION:
 | |
|                 return "Careful there";
 | |
|             case SafetyCenterStatus.OVERALL_SEVERITY_LEVEL_CRITICAL_WARNING:
 | |
|                 return "Code red";
 | |
|         }
 | |
| 
 | |
|         throw new IllegalArgumentException(
 | |
|                 String.format(
 | |
|                         "Unexpected SafetyCenterStatus.OverallSeverityLevel: %s",
 | |
|                         overallSeverityLevel));
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * A key for {@link SafetySourceData}; based on the {@code safetySourceId}, {@code packageName}
 | |
|      * and {@code userId}.
 | |
|      */
 | |
|     // TODO(b/219697341): Look into using AutoValue for this data class.
 | |
|     private static final class Key {
 | |
|         @NonNull private final String mSafetySourceId;
 | |
|         @NonNull private final String mPackageName;
 | |
|         @UserIdInt private final int mUserId;
 | |
| 
 | |
|         private Key(
 | |
|                 @NonNull String safetySourceId,
 | |
|                 @NonNull String packageName,
 | |
|                 @UserIdInt int userId) {
 | |
|             mSafetySourceId = safetySourceId;
 | |
|             mPackageName = packageName;
 | |
|             mUserId = userId;
 | |
|         }
 | |
| 
 | |
|         @NonNull
 | |
|         private static Key of(
 | |
|                 @NonNull String safetySourceId,
 | |
|                 @NonNull String packageName,
 | |
|                 @UserIdInt int userId) {
 | |
|             return new Key(safetySourceId, packageName, userId);
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public String toString() {
 | |
|             return "Key{"
 | |
|                     + "mSafetySourceId='"
 | |
|                     + mSafetySourceId
 | |
|                     + '\''
 | |
|                     + ", mPackageName='"
 | |
|                     + mPackageName
 | |
|                     + '\''
 | |
|                     + ", mUserId="
 | |
|                     + mUserId
 | |
|                     + '\''
 | |
|                     + '}';
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public boolean equals(Object o) {
 | |
|             if (this == o) return true;
 | |
|             if (!(o instanceof Key)) return false;
 | |
|             Key key = (Key) o;
 | |
|             return mSafetySourceId.equals(key.mSafetySourceId)
 | |
|                     && mPackageName.equals(key.mPackageName)
 | |
|                     && mUserId == key.mUserId;
 | |
|         }
 | |
| 
 | |
|         @Override
 | |
|         public int hashCode() {
 | |
|             return Objects.hash(mSafetySourceId, mPackageName, mUserId);
 | |
|         }
 | |
|     }
 | |
| }
 |