• 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.internaldata.SafetyCenterIds.toUserFriendlyString;
22 
23 import android.annotation.Nullable;
24 import android.annotation.UserIdInt;
25 import android.content.Context;
26 import android.os.SystemClock;
27 import android.safetycenter.SafetySourceIssue;
28 import android.util.ArrayMap;
29 import android.util.Log;
30 
31 import androidx.annotation.RequiresApi;
32 
33 import com.android.permission.util.UserUtils;
34 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId;
35 import com.android.safetycenter.internaldata.SafetyCenterIssueKey;
36 import com.android.safetycenter.logging.SafetyCenterStatsdLogger;
37 
38 import java.io.PrintWriter;
39 import java.time.Duration;
40 import java.util.List;
41 
42 import javax.annotation.concurrent.NotThreadSafe;
43 
44 /** Maintains data about in-flight issue actions. */
45 @RequiresApi(TIRAMISU)
46 @NotThreadSafe
47 final class SafetyCenterInFlightIssueActionRepository {
48 
49     private static final String TAG = "SafetyCenterInFlight";
50 
51     private final ArrayMap<SafetyCenterIssueActionId, Long> mSafetyCenterIssueActionsInFlight =
52             new ArrayMap<>();
53 
54     private final Context mContext;
55 
56     /** Constructs a new instance of {@link SafetyCenterInFlightIssueActionRepository}. */
SafetyCenterInFlightIssueActionRepository(Context context)57     SafetyCenterInFlightIssueActionRepository(Context context) {
58         mContext = context;
59     }
60 
61     /** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */
markSafetyCenterIssueActionInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId)62     void markSafetyCenterIssueActionInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId) {
63         mSafetyCenterIssueActionsInFlight.put(
64                 safetyCenterIssueActionId, SystemClock.elapsedRealtime());
65     }
66 
67     /**
68      * Unmarks the given {@link SafetyCenterIssueActionId} as in-flight and returns {@code true} if
69      * the given action was valid and unmarked successfully.
70      *
71      * <p>Also logs an event to statsd with the given {@code result} value.
72      */
unmarkSafetyCenterIssueActionInFlight( SafetyCenterIssueActionId safetyCenterIssueActionId, @Nullable SafetySourceIssue safetySourceIssue, @SafetyCenterStatsdLogger.SystemEventResult int result)73     boolean unmarkSafetyCenterIssueActionInFlight(
74             SafetyCenterIssueActionId safetyCenterIssueActionId,
75             @Nullable SafetySourceIssue safetySourceIssue,
76             @SafetyCenterStatsdLogger.SystemEventResult int result) {
77         Long startElapsedMillis =
78                 mSafetyCenterIssueActionsInFlight.remove(safetyCenterIssueActionId);
79         if (startElapsedMillis == null) {
80             Log.w(
81                     TAG,
82                     "Attempt to unmark unknown in-flight action: "
83                             + toUserFriendlyString(safetyCenterIssueActionId));
84             return false;
85         }
86 
87         SafetyCenterIssueKey issueKey = safetyCenterIssueActionId.getSafetyCenterIssueKey();
88         String issueTypeId = safetySourceIssue == null ? null : safetySourceIssue.getIssueTypeId();
89         Duration duration = Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis);
90 
91         SafetyCenterStatsdLogger.writeInlineActionSystemEvent(
92                 issueKey.getSafetySourceId(),
93                 UserUtils.isManagedProfile(issueKey.getUserId(), mContext),
94                 issueTypeId,
95                 duration,
96                 result);
97 
98         if (safetySourceIssue == null
99                 || getSafetySourceIssueAction(safetyCenterIssueActionId, safetySourceIssue)
100                         == null) {
101             Log.w(
102                     TAG,
103                     "Attempt to unmark in-flight action for a non-existent issue or action: "
104                             + toUserFriendlyString(safetyCenterIssueActionId));
105             return false;
106         }
107 
108         return true;
109     }
110 
111     /** Returns {@code true} if the given issue action is in flight. */
actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId)112     boolean actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId) {
113         return mSafetyCenterIssueActionsInFlight.containsKey(safetyCenterIssueActionId);
114     }
115 
116     /**
117      * Returns {@link SafetySourceIssue.Action} identified by the given {@link
118      * SafetyCenterIssueActionId} and {@link SafetySourceIssue}.
119      */
120     @Nullable
getSafetySourceIssueAction( SafetyCenterIssueActionId safetyCenterIssueActionId, SafetySourceIssue safetySourceIssue)121     SafetySourceIssue.Action getSafetySourceIssueAction(
122             SafetyCenterIssueActionId safetyCenterIssueActionId,
123             SafetySourceIssue safetySourceIssue) {
124         if (actionIsInFlight(safetyCenterIssueActionId)) {
125             return null;
126         }
127 
128         List<SafetySourceIssue.Action> safetySourceIssueActions = safetySourceIssue.getActions();
129         for (int i = 0; i < safetySourceIssueActions.size(); i++) {
130             SafetySourceIssue.Action safetySourceIssueAction = safetySourceIssueActions.get(i);
131 
132             if (safetyCenterIssueActionId
133                     .getSafetySourceIssueActionId()
134                     .equals(safetySourceIssueAction.getId())) {
135                 return safetySourceIssueAction;
136             }
137         }
138 
139         return null;
140     }
141 
142     /** Dumps in-flight action data for debugging purposes. */
dump(PrintWriter fout)143     void dump(PrintWriter fout) {
144         int actionInFlightCount = mSafetyCenterIssueActionsInFlight.size();
145         fout.println("ACTIONS IN FLIGHT (" + actionInFlightCount + ")");
146         for (int i = 0; i < actionInFlightCount; i++) {
147             String printableId = toUserFriendlyString(mSafetyCenterIssueActionsInFlight.keyAt(i));
148             long startElapsedMillis = mSafetyCenterIssueActionsInFlight.valueAt(i);
149             long durationMillis = SystemClock.elapsedRealtime() - startElapsedMillis;
150             fout.println("\t[" + i + "] " + printableId + "(duration=" + durationMillis + "ms)");
151         }
152         fout.println();
153     }
154 
155     /** Clears all in-flight action data. */
clear()156     void clear() {
157         mSafetyCenterIssueActionsInFlight.clear();
158     }
159 
160     /** Clears in-flight action data for given {@code userId}. */
clearForUser(@serIdInt int userId)161     void clearForUser(@UserIdInt int userId) {
162         // Loop in reverse index order to be able to remove entries while iterating.
163         for (int i = mSafetyCenterIssueActionsInFlight.size() - 1; i >= 0; i--) {
164             SafetyCenterIssueActionId issueActionId = mSafetyCenterIssueActionsInFlight.keyAt(i);
165             if (issueActionId.getSafetyCenterIssueKey().getUserId() == userId) {
166                 mSafetyCenterIssueActionsInFlight.removeAt(i);
167             }
168         }
169     }
170 }
171