1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.safetycenter.data; 18 19 import static com.android.safetycenter.data.SafetyCenterIssueDeduplicator.DeduplicationInfo; 20 21 import static java.util.Collections.emptyList; 22 import static java.util.Collections.emptyMap; 23 import static java.util.Collections.emptySet; 24 25 import android.annotation.UserIdInt; 26 import android.content.Context; 27 import android.safetycenter.SafetySourceData; 28 import android.safetycenter.SafetySourceIssue; 29 import android.safetycenter.config.SafetySource; 30 import android.safetycenter.config.SafetySourcesGroup; 31 import android.util.SparseArray; 32 33 import com.android.modules.utils.build.SdkLevel; 34 import com.android.safetycenter.SafetyCenterConfigReader; 35 import com.android.safetycenter.SafetySourceIssueInfo; 36 import com.android.safetycenter.SafetySourceKey; 37 import com.android.safetycenter.SafetySources; 38 import com.android.safetycenter.UserProfileGroup; 39 import com.android.safetycenter.UserProfileGroup.ProfileType; 40 import com.android.safetycenter.internaldata.SafetyCenterIssueKey; 41 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.Comparator; 45 import java.util.List; 46 import java.util.Set; 47 48 import javax.annotation.concurrent.NotThreadSafe; 49 50 /** 51 * Contains issue related data. 52 * 53 * <p>Responsible for generating lists of issues and deduplication of issues. 54 */ 55 @NotThreadSafe 56 final class SafetyCenterIssueRepository { 57 58 private static final SafetySourceIssuesInfoBySeverityDescending 59 SAFETY_SOURCE_ISSUES_INFO_BY_SEVERITY_DESCENDING = 60 new SafetySourceIssuesInfoBySeverityDescending(); 61 62 private static final DeduplicationInfo EMPTY_DEDUP_INFO = 63 new DeduplicationInfo(emptyList(), emptyList(), emptyMap()); 64 65 private final Context mContext; 66 private final SafetySourceDataRepository mSafetySourceDataRepository; 67 private final SafetyCenterConfigReader mSafetyCenterConfigReader; 68 private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository; 69 private final SafetyCenterIssueDeduplicator mSafetyCenterIssueDeduplicator; 70 71 private final SparseArray<DeduplicationInfo> mUserIdToDedupInfo = new SparseArray<>(); 72 SafetyCenterIssueRepository( Context context, SafetySourceDataRepository safetySourceDataRepository, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository, SafetyCenterIssueDeduplicator safetyCenterIssueDeduplicator)73 SafetyCenterIssueRepository( 74 Context context, 75 SafetySourceDataRepository safetySourceDataRepository, 76 SafetyCenterConfigReader safetyCenterConfigReader, 77 SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository, 78 SafetyCenterIssueDeduplicator safetyCenterIssueDeduplicator) { 79 mContext = context; 80 mSafetySourceDataRepository = safetySourceDataRepository; 81 mSafetyCenterConfigReader = safetyCenterConfigReader; 82 mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository; 83 mSafetyCenterIssueDeduplicator = safetyCenterIssueDeduplicator; 84 } 85 86 /** 87 * Updates the class as per the current state of issues. Should be called after any state update 88 * that can affect issues. 89 */ updateIssues(@serIdInt int userId)90 void updateIssues(@UserIdInt int userId) { 91 updateIssues(userId, UserProfileGroup.getProfileTypeOfUser(userId, mContext)); 92 } 93 updateIssues(@serIdInt int userId, @ProfileType int profileType)94 private void updateIssues(@UserIdInt int userId, @ProfileType int profileType) { 95 List<SafetySourceIssueInfo> issues = 96 getAllStoredIssuesFromRawSourceData(userId, profileType); 97 98 issues.sort(SAFETY_SOURCE_ISSUES_INFO_BY_SEVERITY_DESCENDING); 99 100 mUserIdToDedupInfo.put(userId, produceDedupInfo(issues)); 101 } 102 produceDedupInfo(List<SafetySourceIssueInfo> issues)103 private DeduplicationInfo produceDedupInfo(List<SafetySourceIssueInfo> issues) { 104 if (SdkLevel.isAtLeastU()) { 105 return mSafetyCenterIssueDeduplicator.deduplicateIssues(issues); 106 } 107 return new DeduplicationInfo(issues, emptyList(), emptyMap()); 108 } 109 110 /** 111 * Fetches a list of issues related to the given {@link UserProfileGroup}. 112 * 113 * <p>Issues in the list are sorted in descending order and deduplicated (if applicable, only on 114 * Android U+). 115 * 116 * <p>Only includes issues related to active/running {@code userId}s in the given {@link 117 * UserProfileGroup}. 118 */ getIssuesDedupedSortedDescFor(UserProfileGroup userProfileGroup)119 List<SafetySourceIssueInfo> getIssuesDedupedSortedDescFor(UserProfileGroup userProfileGroup) { 120 List<SafetySourceIssueInfo> issuesInfo = getIssuesFor(userProfileGroup); 121 issuesInfo.sort(SAFETY_SOURCE_ISSUES_INFO_BY_SEVERITY_DESCENDING); 122 return issuesInfo; 123 } 124 125 /** 126 * Counts the total number of issues from loggable sources, in the given {@link 127 * UserProfileGroup}. 128 * 129 * <p>Only includes issues related to active/running {@code userId}s in the given {@link 130 * UserProfileGroup}. 131 */ countLoggableIssuesFor(UserProfileGroup userProfileGroup)132 int countLoggableIssuesFor(UserProfileGroup userProfileGroup) { 133 List<SafetySourceIssueInfo> relevantIssues = getIssuesFor(userProfileGroup); 134 int issueCount = 0; 135 for (int i = 0; i < relevantIssues.size(); i++) { 136 SafetySourceIssueInfo safetySourceIssueInfo = relevantIssues.get(i); 137 if (SafetySources.isLoggable(safetySourceIssueInfo.getSafetySource())) { 138 issueCount++; 139 } 140 } 141 return issueCount; 142 } 143 144 /** Gets a list of all issues for the given {@code userId}. */ getIssuesForUser(@serIdInt int userId)145 List<SafetySourceIssueInfo> getIssuesForUser(@UserIdInt int userId) { 146 return filterOutHiddenIssues( 147 mUserIdToDedupInfo.get(userId, EMPTY_DEDUP_INFO).getDeduplicatedIssues()); 148 } 149 150 /** 151 * Returns a set of {@link SafetySourcesGroup} IDs that the given {@link SafetyCenterIssueKey} 152 * is mapped to, or an empty list if no such mapping is configured. 153 */ getGroupMappingFor(SafetyCenterIssueKey issueKey)154 Set<String> getGroupMappingFor(SafetyCenterIssueKey issueKey) { 155 return mUserIdToDedupInfo 156 .get(issueKey.getUserId(), EMPTY_DEDUP_INFO) 157 .getIssueToGroupMapping() 158 .getOrDefault(issueKey, emptySet()); 159 } 160 161 /** 162 * Returns the list of issues for the given {@code userId} which were removed from the given 163 * list of issues in the most recent {@link SafetyCenterIssueDeduplicator#deduplicateIssues} 164 * call. These issues were removed because they were duplicates of other issues. 165 * 166 * <p>If this method is called before any calls to {@link 167 * SafetyCenterIssueDeduplicator#deduplicateIssues} then an empty list is returned. 168 */ getLatestDuplicates(@serIdInt int userId)169 List<SafetySourceIssueInfo> getLatestDuplicates(@UserIdInt int userId) { 170 return mUserIdToDedupInfo.get(userId, EMPTY_DEDUP_INFO).getFilteredOutDuplicateIssues(); 171 } 172 filterOutHiddenIssues(List<SafetySourceIssueInfo> issues)173 private List<SafetySourceIssueInfo> filterOutHiddenIssues(List<SafetySourceIssueInfo> issues) { 174 List<SafetySourceIssueInfo> result = new ArrayList<>(); 175 for (int i = 0; i < issues.size(); i++) { 176 SafetySourceIssueInfo issueInfo = issues.get(i); 177 if (!mSafetyCenterIssueDismissalRepository.isIssueHidden( 178 issueInfo.getSafetyCenterIssueKey())) { 179 result.add(issueInfo); 180 } 181 } 182 return result; 183 } 184 getAllStoredIssuesFromRawSourceData( @serIdInt int userId, @ProfileType int profileType)185 private List<SafetySourceIssueInfo> getAllStoredIssuesFromRawSourceData( 186 @UserIdInt int userId, @ProfileType int profileType) { 187 List<SafetySourceIssueInfo> allIssuesInfo = new ArrayList<>(); 188 189 List<SafetySourcesGroup> safetySourcesGroups = 190 mSafetyCenterConfigReader.getSafetySourcesGroups(); 191 for (int j = 0; j < safetySourcesGroups.size(); j++) { 192 addSafetySourceIssuesInfo( 193 allIssuesInfo, safetySourcesGroups.get(j), userId, profileType); 194 } 195 196 return allIssuesInfo; 197 } 198 addSafetySourceIssuesInfo( List<SafetySourceIssueInfo> issuesInfo, SafetySourcesGroup safetySourcesGroup, @UserIdInt int userId, @ProfileType int profileType)199 private void addSafetySourceIssuesInfo( 200 List<SafetySourceIssueInfo> issuesInfo, 201 SafetySourcesGroup safetySourcesGroup, 202 @UserIdInt int userId, 203 @ProfileType int profileType) { 204 List<SafetySource> safetySources = safetySourcesGroup.getSafetySources(); 205 for (int i = 0; i < safetySources.size(); i++) { 206 SafetySource safetySource = safetySources.get(i); 207 208 if (!SafetySources.isExternal(safetySource)) { 209 continue; 210 } 211 if (!SafetySources.supportsProfileType(safetySource, profileType)) { 212 continue; 213 } 214 215 addSafetySourceIssuesInfo(issuesInfo, safetySource, safetySourcesGroup, userId); 216 } 217 } 218 addSafetySourceIssuesInfo( List<SafetySourceIssueInfo> issuesInfo, SafetySource safetySource, SafetySourcesGroup safetySourcesGroup, @UserIdInt int userId)219 private void addSafetySourceIssuesInfo( 220 List<SafetySourceIssueInfo> issuesInfo, 221 SafetySource safetySource, 222 SafetySourcesGroup safetySourcesGroup, 223 @UserIdInt int userId) { 224 SafetySourceKey key = SafetySourceKey.of(safetySource.getId(), userId); 225 SafetySourceData safetySourceData = mSafetySourceDataRepository.getSafetySourceData(key); 226 227 if (safetySourceData == null) { 228 return; 229 } 230 231 List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues(); 232 for (int i = 0; i < safetySourceIssues.size(); i++) { 233 SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i); 234 235 SafetySourceIssueInfo safetySourceIssueInfo = 236 new SafetySourceIssueInfo( 237 safetySourceIssue, safetySource, safetySourcesGroup, userId); 238 issuesInfo.add(safetySourceIssueInfo); 239 } 240 } 241 242 /** 243 * Only includes issues related to active/running {@code userId}s in the given {@link 244 * UserProfileGroup}. 245 */ getIssuesFor(UserProfileGroup userProfileGroup)246 private List<SafetySourceIssueInfo> getIssuesFor(UserProfileGroup userProfileGroup) { 247 List<SafetySourceIssueInfo> issues = new ArrayList<>(); 248 249 int[] allRunningProfileUserIds = userProfileGroup.getAllRunningProfilesUserIds(); 250 for (int i = 0; i < allRunningProfileUserIds.length; i++) { 251 issues.addAll(getIssuesForUser(allRunningProfileUserIds[i])); 252 } 253 254 return issues; 255 } 256 257 /** A comparator to order {@link SafetySourceIssueInfo} by severity level descending. */ 258 private static final class SafetySourceIssuesInfoBySeverityDescending 259 implements Comparator<SafetySourceIssueInfo> { 260 SafetySourceIssuesInfoBySeverityDescending()261 private SafetySourceIssuesInfoBySeverityDescending() {} 262 263 @Override compare(SafetySourceIssueInfo left, SafetySourceIssueInfo right)264 public int compare(SafetySourceIssueInfo left, SafetySourceIssueInfo right) { 265 return Integer.compare( 266 right.getSafetySourceIssue().getSeverityLevel(), 267 left.getSafetySourceIssue().getSeverityLevel()); 268 } 269 } 270 271 /** Dumps state for debugging purposes. */ dump(PrintWriter fout)272 void dump(PrintWriter fout) { 273 fout.println("ISSUE REPOSITORY"); 274 for (int i = 0; i < mUserIdToDedupInfo.size(); i++) { 275 fout.println(); 276 fout.println("\tUSER ID=" + mUserIdToDedupInfo.keyAt(i)); 277 fout.println("\tDEDUPLICATION INFO=" + mUserIdToDedupInfo.valueAt(i)); 278 } 279 fout.println(); 280 } 281 282 /** Clears all the data from the repository. */ clear()283 void clear() { 284 mUserIdToDedupInfo.clear(); 285 } 286 287 /** Clears all data related to the given {@code userId}. */ clearForUser(@serIdInt int userId)288 void clearForUser(@UserIdInt int userId) { 289 mUserIdToDedupInfo.delete(userId); 290 } 291 } 292