• 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.safetycenter.logging.SafetyCenterStatsdLogger.toSystemEventResult;
22 
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.content.Context;
26 import android.safetycenter.SafetyCenterData;
27 import android.safetycenter.SafetyEvent;
28 import android.safetycenter.SafetySourceData;
29 import android.safetycenter.SafetySourceErrorDetails;
30 import android.safetycenter.SafetySourceIssue;
31 import android.safetycenter.config.SafetyCenterConfig;
32 import android.safetycenter.config.SafetySourcesGroup;
33 import android.util.Log;
34 
35 import androidx.annotation.RequiresApi;
36 
37 import com.android.safetycenter.ApiLock;
38 import com.android.safetycenter.SafetyCenterConfigReader;
39 import com.android.safetycenter.SafetyCenterRefreshTracker;
40 import com.android.safetycenter.SafetySourceIssueInfo;
41 import com.android.safetycenter.SafetySourceKey;
42 import com.android.safetycenter.UserProfileGroup;
43 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
44 import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
45 import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
46 
47 import java.io.FileDescriptor;
48 import java.io.PrintWriter;
49 import java.time.Instant;
50 import java.util.List;
51 import java.util.Set;
52 
53 import javax.annotation.concurrent.NotThreadSafe;
54 
55 /**
56  * Manages updates and access to all data in the data subpackage.
57  *
58  * <p>Data entails what safety sources reported to safety center, including issues, entries,
59  * dismissals, errors, in-flight actions, etc.
60  *
61  * @hide
62  */
63 @RequiresApi(TIRAMISU)
64 @NotThreadSafe
65 public final class SafetyCenterDataManager {
66 
67     private static final String TAG = "SafetyCenterDataManager";
68 
69     private final SafetyCenterRefreshTracker mSafetyCenterRefreshTracker;
70     private final SafetySourceDataRepository mSafetySourceDataRepository;
71     private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository;
72     private final SafetyCenterIssueRepository mSafetyCenterIssueRepository;
73     private final SafetyCenterInFlightIssueActionRepository
74             mSafetyCenterInFlightIssueActionRepository;
75     private final SafetySourceDataValidator mSafetySourceDataValidator;
76     private final SafetySourceStateCollectedLogger mSafetySourceStateCollectedLogger;
77 
78     /** Creates an instance of {@link SafetyCenterDataManager}. */
SafetyCenterDataManager( Context context, SafetyCenterConfigReader safetyCenterConfigReader, SafetyCenterRefreshTracker safetyCenterRefreshTracker, ApiLock apiLock)79     public SafetyCenterDataManager(
80             Context context,
81             SafetyCenterConfigReader safetyCenterConfigReader,
82             SafetyCenterRefreshTracker safetyCenterRefreshTracker,
83             ApiLock apiLock) {
84         mSafetyCenterRefreshTracker = safetyCenterRefreshTracker;
85         mSafetyCenterInFlightIssueActionRepository =
86                 new SafetyCenterInFlightIssueActionRepository(context);
87         mSafetyCenterIssueDismissalRepository =
88                 new SafetyCenterIssueDismissalRepository(apiLock, safetyCenterConfigReader);
89         mSafetySourceDataRepository =
90                 new SafetySourceDataRepository(
91                         context,
92                         mSafetyCenterInFlightIssueActionRepository,
93                         mSafetyCenterIssueDismissalRepository);
94         mSafetyCenterIssueRepository =
95                 new SafetyCenterIssueRepository(
96                         context,
97                         mSafetySourceDataRepository,
98                         safetyCenterConfigReader,
99                         mSafetyCenterIssueDismissalRepository,
100                         new SafetyCenterIssueDeduplicator(mSafetyCenterIssueDismissalRepository));
101         mSafetySourceDataValidator =
102                 new SafetySourceDataValidator(context, safetyCenterConfigReader);
103         mSafetySourceStateCollectedLogger =
104                 new SafetySourceStateCollectedLogger(
105                         context,
106                         mSafetySourceDataRepository,
107                         mSafetyCenterIssueDismissalRepository,
108                         mSafetyCenterIssueRepository);
109     }
110 
111     ///////////////////////////////////////////////////////////////////////////////////////////////
112     //////////////////////   STATE UPDATES ////////////////////////////////////////////////////////
113     ///////////////////////////////////////////////////////////////////////////////////////////////
114 
115     /**
116      * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link
117      * SafetyEvent}, {@code packageName} and {@code userId}, and returns {@code true} if this caused
118      * any changes which would alter {@link SafetyCenterData}.
119      *
120      * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
121      * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected; or the {@link
122      * SafetySourceData} does not respect all constraints defined in the config.
123      *
124      * <p>Setting a {@code null} {@link SafetySourceData} evicts the current {@link
125      * SafetySourceData} entry and clears the {@link SafetyCenterIssueDismissalRepository} for the
126      * source.
127      *
128      * <p>This method may modify the {@link SafetyCenterIssueDismissalRepository}.
129      */
setSafetySourceData( @ullable SafetySourceData safetySourceData, String safetySourceId, SafetyEvent safetyEvent, String packageName, @UserIdInt int userId)130     public boolean setSafetySourceData(
131             @Nullable SafetySourceData safetySourceData,
132             String safetySourceId,
133             SafetyEvent safetyEvent,
134             String packageName,
135             @UserIdInt int userId) {
136         if (!mSafetySourceDataValidator.validateRequest(
137                 safetySourceData, safetySourceId, packageName, userId)) {
138             return false;
139         }
140         SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
141 
142         // Must fetch refresh reason before calling processSafetyEvent because the latter may
143         // complete and clear the current refresh.
144         // TODO(b/277174417): Restructure this code to avoid this error-prone sequencing concern
145         Integer refreshReason = null;
146         if (safetyEvent.getType() == SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) {
147             refreshReason = mSafetyCenterRefreshTracker.getRefreshReason();
148         }
149 
150         boolean sourceDataDiffers =
151                 mSafetySourceDataRepository.setSafetySourceData(
152                         safetySourceData, safetySourceId, userId);
153         boolean eventCausedChange =
154                 processSafetyEvent(safetySourceId, safetyEvent, userId, false, sourceDataDiffers);
155         boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
156 
157         if (safetyCenterDataChanged) {
158             mSafetyCenterIssueRepository.updateIssues(userId);
159         }
160 
161         mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom(
162                 key, safetySourceData, refreshReason, sourceDataDiffers, userId, safetyEvent);
163 
164         return safetyCenterDataChanged;
165     }
166 
167     /**
168      * Marks the issue with the given key as dismissed.
169      *
170      * <p>That issue's notification (if any) is also marked as dismissed.
171      */
dismissSafetyCenterIssue(SafetyCenterIssueKey safetyCenterIssueKey)172     public void dismissSafetyCenterIssue(SafetyCenterIssueKey safetyCenterIssueKey) {
173         mSafetyCenterIssueDismissalRepository.dismissIssue(safetyCenterIssueKey);
174         mSafetyCenterIssueRepository.updateIssues(safetyCenterIssueKey.getUserId());
175     }
176 
177     /**
178      * Marks the notification (if any) of the issue with the given key as dismissed.
179      *
180      * <p>The issue itself is <strong>not</strong> marked as dismissed and its warning card can
181      * still appear in the Safety Center UI.
182      */
dismissNotification(SafetyCenterIssueKey safetyCenterIssueKey)183     public void dismissNotification(SafetyCenterIssueKey safetyCenterIssueKey) {
184         mSafetyCenterIssueDismissalRepository.dismissNotification(safetyCenterIssueKey);
185         mSafetyCenterIssueRepository.updateIssues(safetyCenterIssueKey.getUserId());
186     }
187 
188     /**
189      * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and
190      * {@code userId}, and returns whether there was a change to the underlying {@link
191      * SafetyCenterData}.
192      *
193      * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
194      * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
195      */
reportSafetySourceError( SafetySourceErrorDetails safetySourceErrorDetails, String safetySourceId, String packageName, @UserIdInt int userId)196     public boolean reportSafetySourceError(
197             SafetySourceErrorDetails safetySourceErrorDetails,
198             String safetySourceId,
199             String packageName,
200             @UserIdInt int userId) {
201         if (!mSafetySourceDataValidator.validateRequest(
202                 null, safetySourceId, packageName, userId)) {
203             return false;
204         }
205         SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent();
206         SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId);
207 
208         // Must fetch refresh reason before calling processSafetyEvent because the latter may
209         // complete and clear the current refresh.
210         // TODO(b/277174417): Restructure this code to avoid this error-prone sequencing concern
211         Integer refreshReason = null;
212         if (safetyEvent.getType() == SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED) {
213             refreshReason = mSafetyCenterRefreshTracker.getRefreshReason();
214         }
215 
216         boolean sourceDataDiffers =
217                 mSafetySourceDataRepository.reportSafetySourceError(
218                         safetySourceErrorDetails, safetySourceId, userId);
219         boolean eventCausedChange =
220                 processSafetyEvent(safetySourceId, safetyEvent, userId, true, sourceDataDiffers);
221         boolean safetyCenterDataChanged = sourceDataDiffers || eventCausedChange;
222 
223         if (safetyCenterDataChanged) {
224             mSafetyCenterIssueRepository.updateIssues(userId);
225         }
226 
227         mSafetySourceStateCollectedLogger.writeSourceUpdatedAtom(
228                 key, null, refreshReason, sourceDataDiffers, userId, safetyEvent);
229 
230         return safetyCenterDataChanged;
231     }
232 
233     /**
234      * Marks the given {@link SafetySourceKey} as having timed out during a refresh.
235      *
236      * @param setError whether we should clear the data associated with the source and set an error
237      */
markSafetySourceRefreshTimedOut(SafetySourceKey safetySourceKey, boolean setError)238     public void markSafetySourceRefreshTimedOut(SafetySourceKey safetySourceKey, boolean setError) {
239         boolean dataUpdated =
240                 mSafetySourceDataRepository.markSafetySourceRefreshTimedOut(
241                         safetySourceKey, setError);
242         if (dataUpdated) {
243             mSafetyCenterIssueRepository.updateIssues(safetySourceKey.getUserId());
244         }
245     }
246 
247     /** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */
markSafetyCenterIssueActionInFlight( SafetyCenterIssueActionId safetyCenterIssueActionId)248     public void markSafetyCenterIssueActionInFlight(
249             SafetyCenterIssueActionId safetyCenterIssueActionId) {
250         mSafetyCenterInFlightIssueActionRepository.markSafetyCenterIssueActionInFlight(
251                 safetyCenterIssueActionId);
252         mSafetyCenterIssueRepository.updateIssues(
253                 safetyCenterIssueActionId.getSafetyCenterIssueKey().getUserId());
254     }
255 
256     /**
257      * Unmarks the given {@link SafetyCenterIssueActionId} as in-flight and returns {@code true} if
258      * this caused any changes which would alter {@link SafetyCenterData}.
259      *
260      * <p>Also logs an event to statsd with the given {@code result} value.
261      */
unmarkSafetyCenterIssueActionInFlight( SafetyCenterIssueActionId safetyCenterIssueActionId, @Nullable SafetySourceIssue safetySourceIssue, @SafetyCenterStatsdLogger.SystemEventResult int result)262     public boolean unmarkSafetyCenterIssueActionInFlight(
263             SafetyCenterIssueActionId safetyCenterIssueActionId,
264             @Nullable SafetySourceIssue safetySourceIssue,
265             @SafetyCenterStatsdLogger.SystemEventResult int result) {
266         boolean dataUpdated =
267                 mSafetyCenterInFlightIssueActionRepository.unmarkSafetyCenterIssueActionInFlight(
268                         safetyCenterIssueActionId, safetySourceIssue, result);
269         if (dataUpdated) {
270             mSafetyCenterIssueRepository.updateIssues(
271                     safetyCenterIssueActionId.getSafetyCenterIssueKey().getUserId());
272         }
273         return dataUpdated;
274     }
275 
276     /** Clears all data related to the given {@code userId}. */
clearForUser(@serIdInt int userId)277     public void clearForUser(@UserIdInt int userId) {
278         mSafetySourceDataRepository.clearForUser(userId);
279         mSafetyCenterInFlightIssueActionRepository.clearForUser(userId);
280         mSafetyCenterIssueDismissalRepository.clearForUser(userId);
281         mSafetyCenterIssueRepository.clearForUser(userId);
282     }
283 
284     /** Clears all stored data. */
clear()285     public void clear() {
286         mSafetySourceDataRepository.clear();
287         mSafetyCenterIssueDismissalRepository.clear();
288         mSafetyCenterInFlightIssueActionRepository.clear();
289         mSafetyCenterIssueRepository.clear();
290     }
291 
292     ///////////////////////////////////////////////////////////////////////////////////////////////
293     //////////////////////   SafetyCenterIssueDismissalRepository /////////////////////////////////
294     ///////////////////////////////////////////////////////////////////////////////////////////////
295 
296     /**
297      * Returns {@code true} if the issue with the given key and severity level is currently
298      * dismissed.
299      *
300      * <p>An issue which is dismissed at one time may become "un-dismissed" later, after the
301      * resurface delay (which depends on severity level) has elapsed.
302      *
303      * <p>If the given issue key is not found in the repository this method returns {@code false}.
304      */
isIssueDismissed( SafetyCenterIssueKey safetyCenterIssueKey, @SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel)305     public boolean isIssueDismissed(
306             SafetyCenterIssueKey safetyCenterIssueKey,
307             @SafetySourceData.SeverityLevel int safetySourceIssueSeverityLevel) {
308         return mSafetyCenterIssueDismissalRepository.isIssueDismissed(
309                 safetyCenterIssueKey, safetySourceIssueSeverityLevel);
310     }
311 
312     /** Returns {@code true} if an issue's notification is dismissed now. */
313     // TODO(b/259084807): Consider extracting notification dismissal logic to separate class
isNotificationDismissedNow( SafetyCenterIssueKey issueKey, @SafetySourceData.SeverityLevel int severityLevel)314     public boolean isNotificationDismissedNow(
315             SafetyCenterIssueKey issueKey, @SafetySourceData.SeverityLevel int severityLevel) {
316         return mSafetyCenterIssueDismissalRepository.isNotificationDismissedNow(
317                 issueKey, severityLevel);
318     }
319 
320     /**
321      * Load available persisted data state into memory.
322      *
323      * <p>Note: only some pieces of the data can be persisted, the rest won't be loaded.
324      */
loadPersistableDataStateFromFile()325     public void loadPersistableDataStateFromFile() {
326         mSafetyCenterIssueDismissalRepository.loadStateFromFile();
327     }
328 
329     /**
330      * Returns the {@link Instant} when the issue with the given key was first reported to Safety
331      * Center.
332      */
333     @Nullable
getIssueFirstSeenAt(SafetyCenterIssueKey safetyCenterIssueKey)334     public Instant getIssueFirstSeenAt(SafetyCenterIssueKey safetyCenterIssueKey) {
335         return mSafetyCenterIssueDismissalRepository.getIssueFirstSeenAt(safetyCenterIssueKey);
336     }
337 
338     ///////////////////////////////////////////////////////////////////////////////////////////////
339     //////////////////////   SafetyCenterIssueRepository  /////////////////////////////////////////
340     ///////////////////////////////////////////////////////////////////////////////////////////////
341 
342     /**
343      * Fetches a list of issues related to the given {@link UserProfileGroup}.
344      *
345      * <p>Issues in the list are sorted in descending order and deduplicated (if applicable, only on
346      * Android U+).
347      *
348      * <p>Only includes issues related to active/running {@code userId}s in the given {@link
349      * UserProfileGroup}.
350      */
getIssuesDedupedSortedDescFor( UserProfileGroup userProfileGroup)351     public List<SafetySourceIssueInfo> getIssuesDedupedSortedDescFor(
352             UserProfileGroup userProfileGroup) {
353         return mSafetyCenterIssueRepository.getIssuesDedupedSortedDescFor(userProfileGroup);
354     }
355 
356     /**
357      * Counts the total number of issues from loggable sources, in the given {@link
358      * UserProfileGroup}.
359      *
360      * <p>Only includes issues related to active/running {@code userId}s in the given {@link
361      * UserProfileGroup}.
362      */
countLoggableIssuesFor(UserProfileGroup userProfileGroup)363     public int countLoggableIssuesFor(UserProfileGroup userProfileGroup) {
364         return mSafetyCenterIssueRepository.countLoggableIssuesFor(userProfileGroup);
365     }
366 
367     /** Gets an unmodifiable list of all issues for the given {@code userId}. */
getIssuesForUser(@serIdInt int userId)368     public List<SafetySourceIssueInfo> getIssuesForUser(@UserIdInt int userId) {
369         return mSafetyCenterIssueRepository.getIssuesForUser(userId);
370     }
371 
372     /**
373      * Returns a set of {@link SafetySourcesGroup} IDs that the given {@link SafetyCenterIssueKey}
374      * is mapped to, or an empty list of no such mapping is configured.
375      *
376      * <p>Issue being mapped to a group means that this issue is relevant to that group.
377      */
getGroupMappingFor(SafetyCenterIssueKey issueKey)378     public Set<String> getGroupMappingFor(SafetyCenterIssueKey issueKey) {
379         return mSafetyCenterIssueRepository.getGroupMappingFor(issueKey);
380     }
381 
382     ///////////////////////////////////////////////////////////////////////////////////////////////
383     //////////////////////   SafetyCenterInFlightIssueActionRepository ////////////////////////////
384     ///////////////////////////////////////////////////////////////////////////////////////////////
385 
386     /** Returns {@code true} if the given issue action is in flight. */
actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId)387     public boolean actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId) {
388         return mSafetyCenterInFlightIssueActionRepository.actionIsInFlight(
389                 safetyCenterIssueActionId);
390     }
391 
392     ///////////////////////////////////////////////////////////////////////////////////////////////
393     //////////////////////   SafetySourceDataRepository ///////////////////////////////////////////
394     ///////////////////////////////////////////////////////////////////////////////////////////////
395 
396     /**
397      * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
398      * the given {@code safetySourceId}, {@code packageName} and {@code userId}.
399      *
400      * <p>Throws if the request is invalid based on the {@link SafetyCenterConfig}: the given {@code
401      * safetySourceId}, {@code packageName} and/or {@code userId} are unexpected.
402      *
403      * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
404      * {@link #setSafetySourceData} with a {@code null} value.
405      */
406     @Nullable
getSafetySourceData( String safetySourceId, String packageName, @UserIdInt int userId)407     public SafetySourceData getSafetySourceData(
408             String safetySourceId, String packageName, @UserIdInt int userId) {
409         if (!mSafetySourceDataValidator.validateRequest(
410                 null, safetySourceId, packageName, userId)) {
411             return null;
412         }
413         return mSafetySourceDataRepository.getSafetySourceData(
414                 SafetySourceKey.of(safetySourceId, userId));
415     }
416 
417     /**
418      * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for
419      * the given {@link SafetySourceKey}.
420      *
421      * <p>This method does not perform any validation, {@link #getSafetySourceData(String, String,
422      * int)} should be called wherever validation is required.
423      *
424      * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using
425      * {@link #setSafetySourceData} with a {@code null} value.
426      */
427     @Nullable
getSafetySourceDataInternal(SafetySourceKey safetySourceKey)428     public SafetySourceData getSafetySourceDataInternal(SafetySourceKey safetySourceKey) {
429         return mSafetySourceDataRepository.getSafetySourceData(safetySourceKey);
430     }
431 
432     /** Returns {@code true} if the given source has an error. */
sourceHasError(SafetySourceKey safetySourceKey)433     public boolean sourceHasError(SafetySourceKey safetySourceKey) {
434         return mSafetySourceDataRepository.sourceHasError(safetySourceKey);
435     }
436 
437     /**
438      * Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueKey}.
439      *
440      * <p>Returns {@code null} if there is no such {@link SafetySourceIssue}.
441      */
442     @Nullable
getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey)443     public SafetySourceIssue getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey) {
444         return mSafetySourceDataRepository.getSafetySourceIssue(safetyCenterIssueKey);
445     }
446 
447     /**
448      * Returns the {@link SafetySourceIssue.Action} associated with the given {@link
449      * SafetyCenterIssueActionId}.
450      *
451      * <p>Returns {@code null} if there is no associated {@link SafetySourceIssue}, or if it's been
452      * dismissed.
453      *
454      * <p>Returns {@code null} if the {@link SafetySourceIssue.Action} is currently in flight.
455      */
456     @Nullable
getSafetySourceIssueAction( SafetyCenterIssueActionId safetyCenterIssueActionId)457     public SafetySourceIssue.Action getSafetySourceIssueAction(
458             SafetyCenterIssueActionId safetyCenterIssueActionId) {
459         return mSafetySourceDataRepository.getSafetySourceIssueAction(safetyCenterIssueActionId);
460     }
461 
462     ///////////////////////////////////////////////////////////////////////////////////////////////
463     ////////////////////////////  Other   /////////////////////////////////////////////////////////
464     ///////////////////////////////////////////////////////////////////////////////////////////////
465 
466     /** Dumps state for debugging purposes. */
dump(FileDescriptor fd, PrintWriter fout)467     public void dump(FileDescriptor fd, PrintWriter fout) {
468         mSafetySourceDataRepository.dump(fout);
469         mSafetyCenterIssueDismissalRepository.dump(fd, fout);
470         mSafetyCenterInFlightIssueActionRepository.dump(fout);
471         mSafetyCenterIssueRepository.dump(fout);
472     }
473 
processSafetyEvent( String safetySourceId, SafetyEvent safetyEvent, @UserIdInt int userId, boolean isError, boolean sourceDataChanged)474     private boolean processSafetyEvent(
475             String safetySourceId,
476             SafetyEvent safetyEvent,
477             @UserIdInt int userId,
478             boolean isError,
479             boolean sourceDataChanged) {
480         int type = safetyEvent.getType();
481         switch (type) {
482             case SafetyEvent.SAFETY_EVENT_TYPE_REFRESH_REQUESTED:
483                 String refreshBroadcastId = safetyEvent.getRefreshBroadcastId();
484                 if (refreshBroadcastId == null) {
485                     Log.w(TAG, "No refresh broadcast id in SafetyEvent of type " + type);
486                     return false;
487                 }
488                 return mSafetyCenterRefreshTracker.reportSourceRefreshCompleted(
489                         refreshBroadcastId, safetySourceId, userId, !isError, sourceDataChanged);
490             case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED:
491             case SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED:
492                 String safetySourceIssueId = safetyEvent.getSafetySourceIssueId();
493                 if (safetySourceIssueId == null) {
494                     Log.w(TAG, "No safety source issue id in SafetyEvent of type " + type);
495                     return false;
496                 }
497                 String safetySourceIssueActionId = safetyEvent.getSafetySourceIssueActionId();
498                 if (safetySourceIssueActionId == null) {
499                     Log.w(TAG, "No safety source issue action id in SafetyEvent of type " + type);
500                     return false;
501                 }
502                 SafetyCenterIssueKey safetyCenterIssueKey =
503                         SafetyCenterIssueKey.newBuilder()
504                                 .setSafetySourceId(safetySourceId)
505                                 .setSafetySourceIssueId(safetySourceIssueId)
506                                 .setUserId(userId)
507                                 .build();
508                 SafetyCenterIssueActionId safetyCenterIssueActionId =
509                         SafetyCenterIssueActionId.newBuilder()
510                                 .setSafetyCenterIssueKey(safetyCenterIssueKey)
511                                 .setSafetySourceIssueActionId(safetySourceIssueActionId)
512                                 .build();
513                 boolean success = type == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED;
514                 int result = toSystemEventResult(success);
515                 return mSafetyCenterInFlightIssueActionRepository
516                         .unmarkSafetyCenterIssueActionInFlight(
517                                 safetyCenterIssueActionId,
518                                 getSafetySourceIssue(safetyCenterIssueKey),
519                                 result);
520             case SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED:
521             case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_LOCALE_CHANGED:
522             case SafetyEvent.SAFETY_EVENT_TYPE_DEVICE_REBOOTED:
523                 return false;
524         }
525         Log.w(TAG, "Unexpected SafetyEvent.Type: " + type);
526         return false;
527     }
528 
529     /**
530      * Writes a SafetySourceStateCollected atom for the given source in response to a stats pull.
531      */
logSafetySourceStateCollectedAutomatic( SafetySourceKey sourceKey, boolean isManagedProfile)532     public void logSafetySourceStateCollectedAutomatic(
533             SafetySourceKey sourceKey, boolean isManagedProfile) {
534         mSafetySourceStateCollectedLogger.writeAutomaticAtom(sourceKey, isManagedProfile);
535     }
536 }
537