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