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