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