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