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.logging; 18 19 import static com.android.permission.PermissionStatsLog.SAFETY_STATE; 20 21 import android.annotation.UserIdInt; 22 import android.app.StatsManager; 23 import android.app.StatsManager.StatsPullAtomCallback; 24 import android.content.Context; 25 import android.safetycenter.SafetyCenterData; 26 import android.safetycenter.config.SafetySource; 27 import android.safetycenter.config.SafetySourcesGroup; 28 import android.util.Log; 29 import android.util.StatsEvent; 30 31 import com.android.internal.annotations.GuardedBy; 32 import com.android.modules.utils.build.SdkLevel; 33 import com.android.permission.PermissionStatsLog; 34 import com.android.safetycenter.ApiLock; 35 import com.android.safetycenter.SafetyCenterConfigReader; 36 import com.android.safetycenter.SafetyCenterDataFactory; 37 import com.android.safetycenter.SafetyCenterFlags; 38 import com.android.safetycenter.SafetySourceKey; 39 import com.android.safetycenter.SafetySources; 40 import com.android.safetycenter.UserProfileGroup; 41 import com.android.safetycenter.UserProfileGroup.ProfileType; 42 import com.android.safetycenter.data.SafetyCenterDataManager; 43 44 import java.util.List; 45 46 /** 47 * A {@link StatsPullAtomCallback} that provides a {@link PermissionStatsLog#SAFETY_STATE} atom that 48 * when requested by the {@link StatsManager}. 49 * 50 * <p>Whenever that atom, which describes the overall Safety Center, is pulled this class also 51 * separately writes one {@code SAFETY_SOURCE_STATE_COLLECTED} atom for each active source (per 52 * profile). 53 * 54 * @hide 55 */ 56 public final class SafetyCenterPullAtomCallback implements StatsPullAtomCallback { 57 58 private static final String TAG = "SafetyCenterPullAtom"; 59 60 private final Context mContext; 61 private final ApiLock mApiLock; 62 63 @GuardedBy("mApiLock") 64 private final SafetyCenterConfigReader mSafetyCenterConfigReader; 65 66 @GuardedBy("mApiLock") 67 private final SafetyCenterDataFactory mDataFactory; 68 69 @GuardedBy("mApiLock") 70 private final SafetyCenterDataManager mDataManager; 71 SafetyCenterPullAtomCallback( Context context, ApiLock apiLock, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterDataFactory dataFactory, SafetyCenterDataManager dataManager)72 public SafetyCenterPullAtomCallback( 73 Context context, 74 ApiLock apiLock, 75 SafetyCenterConfigReader safetyCenterConfigReader, 76 SafetyCenterDataFactory dataFactory, 77 SafetyCenterDataManager dataManager) { 78 mContext = context; 79 mApiLock = apiLock; 80 mSafetyCenterConfigReader = safetyCenterConfigReader; 81 mDataFactory = dataFactory; 82 mDataManager = dataManager; 83 } 84 85 @Override onPullAtom(int atomTag, List<StatsEvent> statsEvents)86 public int onPullAtom(int atomTag, List<StatsEvent> statsEvents) { 87 if (atomTag != SAFETY_STATE) { 88 Log.w(TAG, "Attempt to pull atom: " + atomTag + ", but only SAFETY_STATE is supported"); 89 return StatsManager.PULL_SKIP; 90 } 91 if (!SafetyCenterFlags.getSafetyCenterEnabled()) { 92 Log.i(TAG, "Attempt to pull SAFETY_STATE, but Safety Center is disabled"); 93 return StatsManager.PULL_SKIP; 94 } 95 List<UserProfileGroup> userProfileGroups = 96 UserProfileGroup.getAllUserProfileGroups(mContext); 97 synchronized (mApiLock) { 98 if (!SafetyCenterFlags.getAllowStatsdLogging()) { 99 Log.i(TAG, "Skipping pulling and writing atoms due to logging being disabled"); 100 return StatsManager.PULL_SKIP; 101 } 102 Log.d(TAG, "Pulling and writing atoms…"); 103 for (int i = 0; i < userProfileGroups.size(); i++) { 104 UserProfileGroup userProfileGroup = userProfileGroups.get(i); 105 List<SafetySourcesGroup> loggableGroups = 106 mSafetyCenterConfigReader.getLoggableSafetySourcesGroups(); 107 statsEvents.add( 108 createOverallSafetyStateAtomLocked(userProfileGroup, loggableGroups)); 109 // The SAFETY_SOURCE_STATE_COLLECTED atoms are written instead of being pulled, 110 // as they do not support pull. We still want to collect them at the same time as 111 // the above pulled atom, which is why they're written here. 112 writeSafetySourceStateCollectedAtomsLocked(userProfileGroup, loggableGroups); 113 } 114 } 115 return StatsManager.PULL_SUCCESS; 116 } 117 118 @GuardedBy("mApiLock") createOverallSafetyStateAtomLocked( UserProfileGroup userProfileGroup, List<SafetySourcesGroup> loggableGroups)119 private StatsEvent createOverallSafetyStateAtomLocked( 120 UserProfileGroup userProfileGroup, List<SafetySourcesGroup> loggableGroups) { 121 SafetyCenterData loggableData = 122 mDataFactory.assembleSafetyCenterData("android", userProfileGroup, loggableGroups); 123 long openIssuesCount = loggableData.getIssues().size(); 124 long dismissedIssuesCount = getDismissedIssuesCountLocked(loggableData, userProfileGroup); 125 126 return SafetyCenterStatsdLogger.createSafetyStateEvent( 127 loggableData.getStatus().getSeverityLevel(), openIssuesCount, dismissedIssuesCount); 128 } 129 130 @GuardedBy("mApiLock") getDismissedIssuesCountLocked( SafetyCenterData loggableData, UserProfileGroup userProfileGroup)131 private long getDismissedIssuesCountLocked( 132 SafetyCenterData loggableData, UserProfileGroup userProfileGroup) { 133 if (SdkLevel.isAtLeastU()) { 134 return loggableData.getDismissedIssues().size(); 135 } 136 long openIssuesCount = loggableData.getIssues().size(); 137 return mDataManager.countLoggableIssuesFor(userProfileGroup) - openIssuesCount; 138 } 139 140 @GuardedBy("mApiLock") writeSafetySourceStateCollectedAtomsLocked( UserProfileGroup userProfileGroup, List<SafetySourcesGroup> loggableGroups)141 private void writeSafetySourceStateCollectedAtomsLocked( 142 UserProfileGroup userProfileGroup, List<SafetySourcesGroup> loggableGroups) { 143 for (int i = 0; i < loggableGroups.size(); i++) { 144 List<SafetySource> loggableSources = loggableGroups.get(i).getSafetySources(); 145 146 for (int j = 0; j < loggableSources.size(); j++) { 147 SafetySource loggableSource = loggableSources.get(j); 148 149 if (!SafetySources.isExternal(loggableSource)) { 150 continue; 151 } 152 153 for (int profileTypeIdx = 0; 154 profileTypeIdx < ProfileType.ALL_PROFILE_TYPES.length; 155 ++profileTypeIdx) { 156 @ProfileType int profileType = ProfileType.ALL_PROFILE_TYPES[profileTypeIdx]; 157 if (!SafetySources.supportsProfileType(loggableSource, profileType)) { 158 continue; 159 } 160 161 int[] profileIds = userProfileGroup.getProfilesOfType(profileType); 162 for (int profileIdx = 0; profileIdx < profileIds.length; profileIdx++) { 163 writeSafetySourceStateCollectedAtomLocked( 164 loggableSource, profileIds[profileIdx], profileType); 165 } 166 } 167 } 168 } 169 } 170 171 @GuardedBy("mApiLock") writeSafetySourceStateCollectedAtomLocked( SafetySource safetySource, @UserIdInt int userId, @ProfileType int profileType)172 private void writeSafetySourceStateCollectedAtomLocked( 173 SafetySource safetySource, @UserIdInt int userId, @ProfileType int profileType) { 174 SafetySourceKey sourceKey = SafetySourceKey.of(safetySource.getId(), userId); 175 mDataManager.logSafetySourceStateCollectedAutomatic(sourceKey, profileType); 176 } 177 } 178