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.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED; 20 import static android.safetycenter.SafetyEvent.SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED; 21 22 import android.annotation.UserIdInt; 23 import android.safetycenter.SafetyEvent; 24 import android.safetycenter.SafetySourceData; 25 import android.safetycenter.SafetySourceIssue; 26 import android.util.ArraySet; 27 import android.util.Log; 28 29 import androidx.annotation.Nullable; 30 31 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; 32 33 import java.util.List; 34 35 /** 36 * Works around sources sending unexpected {@link SafetyEvent}s by optionally replacing them using 37 * heuristics based on the incoming {@link SafetySourceData} and Safety Center's current state. 38 * 39 * @hide 40 */ 41 public final class SafetyEventFix { 42 43 private static final String TAG = "SafetyEventFix"; 44 SafetyEventFix()45 private SafetyEventFix() {} 46 47 /** 48 * Optionally returns a new {@link SafetyEvent} if heuristics indicate that the one provided by 49 * the source is inappropriate, otherwise returns the source-provided event unchanged. 50 * 51 * <p>If the incoming event has type {@link SafetyEvent#SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED} 52 * but the {@link SafetySourceData} no longer includes an issue, for which Safety Center has a 53 * record of an in-flight, resolving action, then the event will be exchanged for a new one of 54 * type {@link SafetyEvent#SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED}. 55 */ maybeOverrideSafetyEvent( SafetyCenterDataManager dataManager, String safetySourceId, @Nullable SafetySourceData safetySourceData, SafetyEvent safetyEvent, @UserIdInt int userId)56 public static SafetyEvent maybeOverrideSafetyEvent( 57 SafetyCenterDataManager dataManager, 58 String safetySourceId, 59 @Nullable SafetySourceData safetySourceData, 60 SafetyEvent safetyEvent, 61 @UserIdInt int userId) { 62 if (safetySourceData == null 63 || safetyEvent.getType() != SAFETY_EVENT_TYPE_SOURCE_STATE_CHANGED) { 64 return safetyEvent; 65 } 66 67 ArraySet<SafetyCenterIssueActionId> possiblySuccessfulActions = 68 dataManager.getInFlightActions(safetySourceId, userId); 69 70 if (possiblySuccessfulActions.isEmpty()) { 71 return safetyEvent; 72 } 73 74 // Discard any actions for which the issue is still present in the latest source data, they 75 // cannot have been resolved successfully! 76 ArraySet<String> presentSourceIssueIds = getSourceIssueIds(safetySourceData); 77 for (int i = possiblySuccessfulActions.size() - 1; i >= 0; i--) { 78 String sourceIssueId = 79 possiblySuccessfulActions 80 .valueAt(i) 81 .getSafetyCenterIssueKey() 82 .getSafetySourceIssueId(); 83 if (presentSourceIssueIds.contains(sourceIssueId)) { 84 possiblySuccessfulActions.removeAt(i); 85 } 86 } 87 88 if (possiblySuccessfulActions.isEmpty()) { 89 return safetyEvent; 90 } 91 92 if (possiblySuccessfulActions.size() > 1) { 93 Log.i(TAG, "Multiple actions resolved, not overriding " + safetyEvent); 94 return safetyEvent; 95 } 96 97 SafetyCenterIssueActionId successfulAction = possiblySuccessfulActions.valueAt(0); 98 SafetyEvent replacement = newActionSucceededEvent(successfulAction); 99 Log.i(TAG, "Replacing incoming " + safetyEvent + " with " + replacement); 100 return replacement; 101 } 102 getSourceIssueIds(SafetySourceData safetySourceData)103 private static ArraySet<String> getSourceIssueIds(SafetySourceData safetySourceData) { 104 List<SafetySourceIssue> issues = safetySourceData.getIssues(); 105 ArraySet<String> issueIds = new ArraySet<>(issues.size()); 106 for (int i = 0; i < issues.size(); i++) { 107 issueIds.add(issues.get(i).getId()); 108 } 109 return issueIds; 110 } 111 newActionSucceededEvent(SafetyCenterIssueActionId actionId)112 private static SafetyEvent newActionSucceededEvent(SafetyCenterIssueActionId actionId) { 113 return new SafetyEvent.Builder(SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) 114 .setSafetySourceIssueId(actionId.getSafetyCenterIssueKey().getSafetySourceIssueId()) 115 .setSafetySourceIssueActionId(actionId.getSafetySourceIssueActionId()) 116 .build(); 117 } 118 } 119