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