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