• 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 static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__DATA_PROVIDED;
22 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__NO_DATA_PROVIDED;
23 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__REFRESH_TIMEOUT;
24 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED;
25 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR;
26 
27 import android.annotation.Nullable;
28 import android.annotation.UptimeMillisLong;
29 import android.annotation.UserIdInt;
30 import android.content.Context;
31 import android.os.SystemClock;
32 import android.safetycenter.SafetyCenterData;
33 import android.safetycenter.SafetyEvent;
34 import android.safetycenter.SafetySourceData;
35 import android.safetycenter.SafetySourceErrorDetails;
36 import android.safetycenter.SafetySourceIssue;
37 import android.util.ArrayMap;
38 import android.util.ArraySet;
39 import android.util.Log;
40 
41 import androidx.annotation.RequiresApi;
42 
43 import com.android.safetycenter.SafetySourceKey;
44 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
45 import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
46 import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
47 
48 import java.io.PrintWriter;
49 import java.util.List;
50 import java.util.Objects;
51 
52 import javax.annotation.concurrent.NotThreadSafe;
53 
54 /**
55  * Repository for {@link SafetySourceData} and other data managed by Safety Center including {@link
56  * SafetySourceErrorDetails}.
57  *
58  * <p>This class isn't thread safe. Thread safety must be handled by the caller.
59  */
60 @RequiresApi(TIRAMISU)
61 @NotThreadSafe
62 final class SafetySourceDataRepository {
63 
64     private static final String TAG = "SafetySourceDataRepo";
65 
66     private final ArrayMap<SafetySourceKey, SafetySourceData> mSafetySourceData = new ArrayMap<>();
67     private final ArraySet<SafetySourceKey> mSafetySourceErrors = new ArraySet<>();
68     private final ArrayMap<SafetySourceKey, Long> mSafetySourceLastUpdated = new ArrayMap<>();
69     private final ArrayMap<SafetySourceKey, Integer> mSourceStates = new ArrayMap<>();
70 
71     private final Context mContext;
72     private final SafetyCenterInFlightIssueActionRepository
73             mSafetyCenterInFlightIssueActionRepository;
74     private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository;
75 
SafetySourceDataRepository( Context context, SafetyCenterInFlightIssueActionRepository safetyCenterInFlightIssueActionRepository, SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository)76     SafetySourceDataRepository(
77             Context context,
78             SafetyCenterInFlightIssueActionRepository safetyCenterInFlightIssueActionRepository,
79             SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository) {
80         mContext = context;
81         mSafetyCenterInFlightIssueActionRepository = safetyCenterInFlightIssueActionRepository;
82         mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository;
83     }
84 
85     /**
86      * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link
87      * SafetyEvent}, {@code packageName} and {@code userId}, and returns {@code true} if this caused
88      * any changes which would alter {@link SafetyCenterData}.
89      *
90      * <p>This method does not perform any validation, {@link
91      * SafetyCenterDataManager#setSafetySourceData(SafetySourceData, String, SafetyEvent, String,
92      * int)} should be called wherever validation is required.
93      *
94      * <p>Setting a {@code null} {@link SafetySourceData} evicts the current {@link
95      * SafetySourceData} entry and clears the {@link SafetyCenterIssueDismissalRepository} for the
96      * source.
97      *
98      * <p>This method may modify the {@link SafetyCenterIssueDismissalRepository}.
99      */
setSafetySourceData( @ullable SafetySourceData safetySourceData, String safetySourceId, @UserIdInt int userId)100     boolean setSafetySourceData(
101             @Nullable SafetySourceData safetySourceData,
102             String safetySourceId,
103             @UserIdInt int userId) {
104         SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
105         safetySourceData =
106                 AndroidLockScreenFix.maybeOverrideSafetySourceData(
107                         mContext, safetySourceId, safetySourceData);
108 
109         boolean sourceDataDiffers = !Objects.equals(safetySourceData, mSafetySourceData.get(key));
110         boolean removedSourceError = mSafetySourceErrors.remove(key);
111 
112         if (sourceDataDiffers) {
113             setSafetySourceDataInternal(key, safetySourceData);
114         }
115 
116         setLastUpdatedNow(key);
117         return sourceDataDiffers || removedSourceError;
118     }
119 
setSafetySourceDataInternal(SafetySourceKey key, @Nullable SafetySourceData data)120     private void setSafetySourceDataInternal(SafetySourceKey key, @Nullable SafetySourceData data) {
121         ArraySet<String> issueIds = new ArraySet<>();
122         if (data == null) {
123             mSafetySourceData.remove(key);
124             mSourceStates.put(key, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED);
125         } else {
126             mSafetySourceData.put(key, data);
127             for (int i = 0; i < data.getIssues().size(); i++) {
128                 issueIds.add(data.getIssues().get(i).getId());
129             }
130             mSourceStates.put(key, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__DATA_PROVIDED);
131         }
132         mSafetyCenterIssueDismissalRepository.updateIssuesForSource(
133                 issueIds, key.getSourceId(), key.getUserId());
134     }
135 
136     /**
137      * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
138      * the given {@link SafetySourceKey}.
139      *
140      * <p>This method does not perform any validation, {@link
141      * SafetyCenterDataManager#getSafetySourceData(String, String, int)} should be called wherever
142      * validation is required.
143      *
144      * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
145      * {@link #setSafetySourceData} with a {@code null} value.
146      */
147     @Nullable
getSafetySourceData(SafetySourceKey safetySourceKey)148     SafetySourceData getSafetySourceData(SafetySourceKey safetySourceKey) {
149         return mSafetySourceData.get(safetySourceKey);
150     }
151 
152     /** Returns {@code true} if the given source has an error. */
sourceHasError(SafetySourceKey safetySourceKey)153     boolean sourceHasError(SafetySourceKey safetySourceKey) {
154         return mSafetySourceErrors.contains(safetySourceKey);
155     }
156 
157     /**
158      * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and
159      * {@code userId}, and returns {@code true} if this changed the repository's data.
160      *
161      * <p>This method does not perform any validation, {@link
162      * SafetyCenterDataManager#reportSafetySourceError(SafetySourceErrorDetails, String, String,
163      * int)} should be called wherever validation is required.
164      */
reportSafetySourceError( SafetySourceErrorDetails safetySourceErrorDetails, String safetySourceId, @UserIdInt int userId)165     boolean reportSafetySourceError(
166             SafetySourceErrorDetails safetySourceErrorDetails,
167             String safetySourceId,
168             @UserIdInt int userId) {
169         SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
170         Log.w(TAG, "Error reported from source: " + safetySourceId + ", for event: " + safetyEvent);
171 
172         int safetyEventType = safetyEvent.getType();
173         if (safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED
174                 || safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) {
175             return false;
176         }
177 
178         SafetySourceKey sourceKey = SafetySourceKey.of(safetySourceId, userId);
179         mSourceStates.put(sourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR);
180         return setSafetySourceError(sourceKey);
181     }
182 
183     /**
184      * Marks the given {@link SafetySourceKey} as having timed out during a refresh, and returns
185      * {@code true} if it caused a change to the stored data.
186      *
187      * @param setError whether we should clear the data associated with the source and set an error
188      */
markSafetySourceRefreshTimedOut(SafetySourceKey sourceKey, boolean setError)189     boolean markSafetySourceRefreshTimedOut(SafetySourceKey sourceKey, boolean setError) {
190         mSourceStates.put(sourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__REFRESH_TIMEOUT);
191         if (!setError) {
192             return false;
193         }
194         return setSafetySourceError(sourceKey);
195     }
196 
197     /**
198      * Marks the given {@link SafetySourceKey} as being in an error state and returns {@code true}
199      * if this changed the repository's data.
200      */
setSafetySourceError(SafetySourceKey safetySourceKey)201     private boolean setSafetySourceError(SafetySourceKey safetySourceKey) {
202         setLastUpdatedNow(safetySourceKey);
203         boolean removingSafetySourceDataChangedSafetyCenterData =
204                 mSafetySourceData.remove(safetySourceKey) != null;
205         boolean addingSafetySourceErrorChangedSafetyCenterData =
206                 mSafetySourceErrors.add(safetySourceKey);
207         return removingSafetySourceDataChangedSafetyCenterData
208                 || addingSafetySourceErrorChangedSafetyCenterData;
209     }
210 
211     /**
212      * Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueKey}.
213      *
214      * <p>Returns {@code null} if there is no such {@link SafetySourceIssue}.
215      */
216     @Nullable
getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey)217     SafetySourceIssue getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey) {
218         SafetySourceKey key =
219                 SafetySourceKey.of(
220                         safetyCenterIssueKey.getSafetySourceId(), safetyCenterIssueKey.getUserId());
221         SafetySourceData safetySourceData = mSafetySourceData.get(key);
222         if (safetySourceData == null) {
223             return null;
224         }
225         List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues();
226 
227         SafetySourceIssue targetIssue = null;
228         for (int i = 0; i < safetySourceIssues.size(); i++) {
229             SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i);
230 
231             if (safetyCenterIssueKey.getSafetySourceIssueId().equals(safetySourceIssue.getId())) {
232                 targetIssue = safetySourceIssue;
233                 break;
234             }
235         }
236 
237         return targetIssue;
238     }
239 
240     /**
241      * Returns the {@link SafetySourceIssue.Action} associated with the given {@link
242      * SafetyCenterIssueActionId}.
243      *
244      * <p>Returns {@code null} if there is no associated {@link SafetySourceIssue}.
245      *
246      * <p>Returns {@code null} if the {@link SafetySourceIssue.Action} is currently in flight.
247      */
248     @Nullable
getSafetySourceIssueAction( SafetyCenterIssueActionId safetyCenterIssueActionId)249     SafetySourceIssue.Action getSafetySourceIssueAction(
250             SafetyCenterIssueActionId safetyCenterIssueActionId) {
251         SafetySourceIssue safetySourceIssue =
252                 getSafetySourceIssue(safetyCenterIssueActionId.getSafetyCenterIssueKey());
253 
254         if (safetySourceIssue == null) {
255             return null;
256         }
257 
258         return mSafetyCenterInFlightIssueActionRepository.getSafetySourceIssueAction(
259                 safetyCenterIssueActionId, safetySourceIssue);
260     }
261 
262     /**
263      * Returns the elapsed realtime millis of when the data of the given {@link SafetySourceKey} was
264      * last updated, or {@code 0L} if no update has occurred.
265      *
266      * @see SystemClock#elapsedRealtime()
267      */
268     @UptimeMillisLong
getSafetySourceLastUpdated(SafetySourceKey sourceKey)269     long getSafetySourceLastUpdated(SafetySourceKey sourceKey) {
270         Long lastUpdated = mSafetySourceLastUpdated.get(sourceKey);
271         if (lastUpdated != null) {
272             return lastUpdated;
273         } else {
274             return 0L;
275         }
276     }
277 
setLastUpdatedNow(SafetySourceKey sourceKey)278     private void setLastUpdatedNow(SafetySourceKey sourceKey) {
279         mSafetySourceLastUpdated.put(sourceKey, SystemClock.elapsedRealtime());
280     }
281 
282     /**
283      * Returns the current {@link SafetyCenterStatsdLogger.SourceState} of the given {@link
284      * SafetySourceKey}.
285      */
286     @SafetyCenterStatsdLogger.SourceState
getSourceState(SafetySourceKey sourceKey)287     int getSourceState(SafetySourceKey sourceKey) {
288         Integer sourceState = mSourceStates.get(sourceKey);
289         if (sourceState != null) {
290             return sourceState;
291         } else {
292             return SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__NO_DATA_PROVIDED;
293         }
294     }
295 
296     /** Clears all data for all users. */
clear()297     void clear() {
298         mSafetySourceData.clear();
299         mSafetySourceErrors.clear();
300         mSafetySourceLastUpdated.clear();
301         mSourceStates.clear();
302     }
303 
304     /** Clears all data for the given user. */
clearForUser(@serIdInt int userId)305     void clearForUser(@UserIdInt int userId) {
306         // Loop in reverse index order to be able to remove entries while iterating.
307         for (int i = mSafetySourceData.size() - 1; i >= 0; i--) {
308             SafetySourceKey sourceKey = mSafetySourceData.keyAt(i);
309             if (sourceKey.getUserId() == userId) {
310                 mSafetySourceData.removeAt(i);
311             }
312         }
313         for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) {
314             SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i);
315             if (sourceKey.getUserId() == userId) {
316                 mSafetySourceErrors.removeAt(i);
317             }
318         }
319         for (int i = mSafetySourceLastUpdated.size() - 1; i >= 0; i--) {
320             SafetySourceKey sourceKey = mSafetySourceLastUpdated.keyAt(i);
321             if (sourceKey.getUserId() == userId) {
322                 mSafetySourceLastUpdated.removeAt(i);
323             }
324         }
325         for (int i = mSourceStates.size() - 1; i >= 0; i--) {
326             SafetySourceKey sourceKey = mSourceStates.keyAt(i);
327             if (sourceKey.getUserId() == userId) {
328                 mSourceStates.removeAt(i);
329             }
330         }
331     }
332 
333     /** Dumps state for debugging purposes. */
dump(PrintWriter fout)334     void dump(PrintWriter fout) {
335         dumpArrayMap(fout, mSafetySourceData, "SOURCE DATA");
336         int errorCount = mSafetySourceErrors.size();
337         fout.println("SOURCE ERRORS (" + errorCount + ")");
338         for (int i = 0; i < errorCount; i++) {
339             SafetySourceKey key = mSafetySourceErrors.valueAt(i);
340             fout.println("\t[" + i + "] " + key);
341         }
342         dumpArrayMap(fout, mSafetySourceLastUpdated, "LAST UPDATED");
343         dumpArrayMap(fout, mSourceStates, "SOURCE STATES");
344         fout.println();
345     }
346 
dumpArrayMap(PrintWriter fout, ArrayMap<K, V> map, String label)347     private static <K, V> void dumpArrayMap(PrintWriter fout, ArrayMap<K, V> map, String label) {
348         int count = map.size();
349         fout.println(label + " (" + count + ")");
350         for (int i = 0; i < count; i++) {
351             fout.println("\t[" + i + "] " + map.keyAt(i) + " -> " + map.valueAt(i));
352         }
353     }
354 }
355