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