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