• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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