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 com.android.safetycenter.internaldata.SafetyCenterIds.toUserFriendlyString; 20 21 import android.annotation.UserIdInt; 22 import android.content.Context; 23 import android.os.SystemClock; 24 import android.safetycenter.SafetySourceIssue; 25 import android.util.ArrayMap; 26 import android.util.ArraySet; 27 import android.util.Log; 28 29 import androidx.annotation.Nullable; 30 31 import com.android.safetycenter.SafetySourceIssues; 32 import com.android.safetycenter.UserProfileGroup; 33 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; 34 import com.android.safetycenter.internaldata.SafetyCenterIssueKey; 35 import com.android.safetycenter.logging.SafetyCenterStatsdLogger; 36 37 import java.io.PrintWriter; 38 import java.time.Duration; 39 40 import javax.annotation.concurrent.NotThreadSafe; 41 42 /** Maintains data about in-flight issue actions. */ 43 @NotThreadSafe 44 final class SafetyCenterInFlightIssueActionRepository { 45 46 private static final String TAG = "SafetyCenterInFlight"; 47 48 private final ArrayMap<SafetyCenterIssueActionId, Long> mSafetyCenterIssueActionsInFlight = 49 new ArrayMap<>(); 50 51 private final Context mContext; 52 53 /** Constructs a new instance of {@link SafetyCenterInFlightIssueActionRepository}. */ SafetyCenterInFlightIssueActionRepository(Context context)54 SafetyCenterInFlightIssueActionRepository(Context context) { 55 mContext = context; 56 } 57 58 /** Marks the given {@link SafetyCenterIssueActionId} as in-flight. */ markSafetyCenterIssueActionInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId)59 void markSafetyCenterIssueActionInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId) { 60 mSafetyCenterIssueActionsInFlight.put( 61 safetyCenterIssueActionId, SystemClock.elapsedRealtime()); 62 } 63 64 /** 65 * Unmarks the given {@link SafetyCenterIssueActionId} as in-flight and returns {@code true} if 66 * the given action was valid and unmarked successfully. 67 * 68 * <p>Also logs an event to statsd with the given {@code result} value. 69 */ unmarkSafetyCenterIssueActionInFlight( SafetyCenterIssueActionId safetyCenterIssueActionId, @Nullable SafetySourceIssue safetySourceIssue, @SafetyCenterStatsdLogger.SystemEventResult int result)70 boolean unmarkSafetyCenterIssueActionInFlight( 71 SafetyCenterIssueActionId safetyCenterIssueActionId, 72 @Nullable SafetySourceIssue safetySourceIssue, 73 @SafetyCenterStatsdLogger.SystemEventResult int result) { 74 Long startElapsedMillis = 75 mSafetyCenterIssueActionsInFlight.remove(safetyCenterIssueActionId); 76 if (startElapsedMillis == null) { 77 Log.w( 78 TAG, 79 "Attempt to unmark unknown in-flight action: " 80 + toUserFriendlyString(safetyCenterIssueActionId)); 81 return false; 82 } 83 84 SafetyCenterIssueKey issueKey = safetyCenterIssueActionId.getSafetyCenterIssueKey(); 85 String issueTypeId = safetySourceIssue == null ? null : safetySourceIssue.getIssueTypeId(); 86 Duration duration = Duration.ofMillis(SystemClock.elapsedRealtime() - startElapsedMillis); 87 88 SafetyCenterStatsdLogger.writeInlineActionSystemEvent( 89 issueKey.getSafetySourceId(), 90 UserProfileGroup.getProfileTypeOfUser(issueKey.getUserId(), mContext), 91 issueTypeId, 92 duration, 93 result); 94 95 if (safetySourceIssue == null 96 || getSafetySourceIssueAction(safetyCenterIssueActionId, safetySourceIssue) 97 == null) { 98 Log.w( 99 TAG, 100 "Attempt to unmark in-flight action for a non-existent issue or action: " 101 + toUserFriendlyString(safetyCenterIssueActionId)); 102 return false; 103 } 104 105 return true; 106 } 107 108 /** Returns {@code true} if the given issue action is in flight. */ actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId)109 boolean actionIsInFlight(SafetyCenterIssueActionId safetyCenterIssueActionId) { 110 return mSafetyCenterIssueActionsInFlight.containsKey(safetyCenterIssueActionId); 111 } 112 113 /** Returns a list of IDs of in-flight actions for the given source and user */ getInFlightActions(String sourceId, @UserIdInt int userId)114 ArraySet<SafetyCenterIssueActionId> getInFlightActions(String sourceId, @UserIdInt int userId) { 115 ArraySet<SafetyCenterIssueActionId> result = new ArraySet<>(); 116 for (int i = 0; i < mSafetyCenterIssueActionsInFlight.size(); i++) { 117 SafetyCenterIssueActionId actionId = mSafetyCenterIssueActionsInFlight.keyAt(i); 118 SafetyCenterIssueKey issueKey = actionId.getSafetyCenterIssueKey(); 119 if (sourceId.equals(issueKey.getSafetySourceId()) && issueKey.getUserId() == userId) { 120 result.add(actionId); 121 } 122 } 123 return result; 124 } 125 126 /** 127 * Returns {@link SafetySourceIssue.Action} identified by the given {@link 128 * SafetyCenterIssueActionId} and {@link SafetySourceIssue}. 129 */ 130 @Nullable getSafetySourceIssueAction( SafetyCenterIssueActionId safetyCenterIssueActionId, SafetySourceIssue safetySourceIssue)131 SafetySourceIssue.Action getSafetySourceIssueAction( 132 SafetyCenterIssueActionId safetyCenterIssueActionId, 133 SafetySourceIssue safetySourceIssue) { 134 if (actionIsInFlight(safetyCenterIssueActionId)) { 135 return null; 136 } 137 138 return SafetySourceIssues.findAction( 139 safetySourceIssue, safetyCenterIssueActionId.getSafetySourceIssueActionId()); 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