• 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 android.annotation.ElapsedRealtimeLong;
22 import android.annotation.Nullable;
23 import android.content.Context;
24 import android.safetycenter.SafetyCenterManager;
25 import android.safetycenter.SafetyEvent;
26 import android.safetycenter.SafetySourceData;
27 import android.safetycenter.SafetySourceIssue;
28 import android.safetycenter.SafetySourceStatus;
29 
30 import androidx.annotation.RequiresApi;
31 
32 import com.android.permission.util.UserUtils;
33 import com.android.safetycenter.SafetySourceIssueInfo;
34 import com.android.safetycenter.SafetySourceKey;
35 import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
36 import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
37 
38 import java.util.Collections;
39 import java.util.List;
40 
41 import javax.annotation.concurrent.NotThreadSafe;
42 
43 /**
44  * Collates information from various data-related classes and uses that information to log {@code
45  * SafetySourceStateCollected} atoms.
46  */
47 @RequiresApi(TIRAMISU)
48 @NotThreadSafe
49 final class SafetySourceStateCollectedLogger {
50 
51     private final Context mContext;
52     private final SafetySourceDataRepository mSourceDataRepository;
53     private final SafetyCenterIssueDismissalRepository mIssueDismissalRepository;
54     private final SafetyCenterIssueRepository mIssueRepository;
55 
SafetySourceStateCollectedLogger( Context context, SafetySourceDataRepository sourceDataRepository, SafetyCenterIssueDismissalRepository issueDismissalRepository, SafetyCenterIssueRepository issueRepository)56     SafetySourceStateCollectedLogger(
57             Context context,
58             SafetySourceDataRepository sourceDataRepository,
59             SafetyCenterIssueDismissalRepository issueDismissalRepository,
60             SafetyCenterIssueRepository issueRepository) {
61         mContext = context;
62         mSourceDataRepository = sourceDataRepository;
63         mIssueDismissalRepository = issueDismissalRepository;
64         mIssueRepository = issueRepository;
65     }
66 
67     /**
68      * Writes a SafetySourceStateCollected atom for the given source in response to a stats pull.
69      */
writeAutomaticAtom(SafetySourceKey sourceKey, boolean isManagedProfile)70     void writeAutomaticAtom(SafetySourceKey sourceKey, boolean isManagedProfile) {
71         logSafetySourceStateCollected(
72                 sourceKey,
73                 mSourceDataRepository.getSafetySourceData(sourceKey),
74                 /* refreshReason= */ null,
75                 /* sourceDataDiffers= */ false,
76                 isManagedProfile,
77                 /* safetyEvent= */ null,
78                 mSourceDataRepository.getSafetySourceLastUpdated(sourceKey));
79     }
80 
81     /**
82      * Writes a SafetySourceStateCollected atom for the given source in response to that source
83      * updating its own state.
84      */
writeSourceUpdatedAtom( SafetySourceKey key, @Nullable SafetySourceData safetySourceData, @Nullable @SafetyCenterManager.RefreshReason Integer refreshReason, boolean sourceDataDiffers, int userId, SafetyEvent safetyEvent)85     void writeSourceUpdatedAtom(
86             SafetySourceKey key,
87             @Nullable SafetySourceData safetySourceData,
88             @Nullable @SafetyCenterManager.RefreshReason Integer refreshReason,
89             boolean sourceDataDiffers,
90             int userId,
91             SafetyEvent safetyEvent) {
92         logSafetySourceStateCollected(
93                 key,
94                 safetySourceData,
95                 refreshReason,
96                 sourceDataDiffers,
97                 UserUtils.isManagedProfile(userId, mContext),
98                 safetyEvent,
99                 /* lastUpdatedElapsedTimeMillis= */ null);
100     }
101 
logSafetySourceStateCollected( SafetySourceKey sourceKey, @Nullable SafetySourceData sourceData, @Nullable @SafetyCenterManager.RefreshReason Integer refreshReason, boolean sourceDataDiffers, boolean isManagedProfile, @Nullable SafetyEvent safetyEvent, @Nullable @ElapsedRealtimeLong Long lastUpdatedElapsedTimeMillis)102     private void logSafetySourceStateCollected(
103             SafetySourceKey sourceKey,
104             @Nullable SafetySourceData sourceData,
105             @Nullable @SafetyCenterManager.RefreshReason Integer refreshReason,
106             boolean sourceDataDiffers,
107             boolean isManagedProfile,
108             @Nullable SafetyEvent safetyEvent,
109             @Nullable @ElapsedRealtimeLong Long lastUpdatedElapsedTimeMillis) {
110         SafetySourceStatus sourceStatus = sourceData == null ? null : sourceData.getStatus();
111         List<SafetySourceIssue> sourceIssues =
112                 sourceData == null ? Collections.emptyList() : sourceData.getIssues();
113 
114         int maxSeverityLevel = Integer.MIN_VALUE;
115         if (sourceStatus != null) {
116             maxSeverityLevel = sourceStatus.getSeverityLevel();
117         } else if (sourceData != null) {
118             // In this case we know we have an issue-only source because of the checks carried out
119             // in the validateRequest function.
120             maxSeverityLevel = SafetySourceData.SEVERITY_LEVEL_UNSPECIFIED;
121         }
122 
123         long openIssuesCount = 0;
124         long dismissedIssuesCount = 0;
125         for (int i = 0; i < sourceIssues.size(); i++) {
126             SafetySourceIssue issue = sourceIssues.get(i);
127             if (isIssueDismissed(issue, sourceKey)) {
128                 dismissedIssuesCount++;
129             } else {
130                 openIssuesCount++;
131                 maxSeverityLevel = Math.max(maxSeverityLevel, issue.getSeverityLevel());
132             }
133         }
134 
135         SafetyCenterStatsdLogger.writeSafetySourceStateCollected(
136                 sourceKey.getSourceId(),
137                 isManagedProfile,
138                 maxSeverityLevel > Integer.MIN_VALUE ? maxSeverityLevel : null,
139                 openIssuesCount,
140                 dismissedIssuesCount,
141                 getDuplicateCount(sourceKey),
142                 mSourceDataRepository.getSourceState(sourceKey),
143                 safetyEvent,
144                 refreshReason,
145                 sourceDataDiffers,
146                 lastUpdatedElapsedTimeMillis);
147     }
148 
isIssueDismissed(SafetySourceIssue issue, SafetySourceKey sourceKey)149     private boolean isIssueDismissed(SafetySourceIssue issue, SafetySourceKey sourceKey) {
150         SafetyCenterIssueKey issueKey =
151                 SafetyCenterIssueKey.newBuilder()
152                         .setSafetySourceId(sourceKey.getSourceId())
153                         .setSafetySourceIssueId(issue.getId())
154                         .setUserId(sourceKey.getUserId())
155                         .build();
156         return mIssueDismissalRepository.isIssueDismissed(issueKey, issue.getSeverityLevel());
157     }
158 
getDuplicateCount(SafetySourceKey sourceKey)159     private long getDuplicateCount(SafetySourceKey sourceKey) {
160         long count = 0;
161         List<SafetySourceIssueInfo> duplicates =
162                 mIssueRepository.getLatestDuplicates(sourceKey.getUserId());
163         for (int i = 0; i < duplicates.size(); i++) {
164             if (duplicates.get(i).getSafetySource().getId().equals(sourceKey.getSourceId())) {
165                 count++;
166             }
167         }
168         return count;
169     }
170 }
171