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.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__DATA_PROVIDED; 22 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__NO_DATA_PROVIDED; 23 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__REFRESH_TIMEOUT; 24 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED; 25 import static com.android.permission.PermissionStatsLog.SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR; 26 27 import android.annotation.Nullable; 28 import android.annotation.UptimeMillisLong; 29 import android.annotation.UserIdInt; 30 import android.content.Context; 31 import android.os.SystemClock; 32 import android.safetycenter.SafetyCenterData; 33 import android.safetycenter.SafetyEvent; 34 import android.safetycenter.SafetySourceData; 35 import android.safetycenter.SafetySourceErrorDetails; 36 import android.safetycenter.SafetySourceIssue; 37 import android.util.ArrayMap; 38 import android.util.ArraySet; 39 import android.util.Log; 40 41 import androidx.annotation.RequiresApi; 42 43 import com.android.safetycenter.SafetySourceKey; 44 import com.android.safetycenter.internaldata.SafetyCenterIssueActionId; 45 import com.android.safetycenter.internaldata.SafetyCenterIssueKey; 46 import com.android.safetycenter.logging.SafetyCenterStatsdLogger; 47 48 import java.io.PrintWriter; 49 import java.util.List; 50 import java.util.Objects; 51 52 import javax.annotation.concurrent.NotThreadSafe; 53 54 /** 55 * Repository for {@link SafetySourceData} and other data managed by Safety Center including {@link 56 * SafetySourceErrorDetails}. 57 * 58 * <p>This class isn't thread safe. Thread safety must be handled by the caller. 59 */ 60 @RequiresApi(TIRAMISU) 61 @NotThreadSafe 62 final class SafetySourceDataRepository { 63 64 private static final String TAG = "SafetySourceDataRepo"; 65 66 private final ArrayMap<SafetySourceKey, SafetySourceData> mSafetySourceData = new ArrayMap<>(); 67 private final ArraySet<SafetySourceKey> mSafetySourceErrors = new ArraySet<>(); 68 private final ArrayMap<SafetySourceKey, Long> mSafetySourceLastUpdated = new ArrayMap<>(); 69 private final ArrayMap<SafetySourceKey, Integer> mSourceStates = new ArrayMap<>(); 70 71 private final Context mContext; 72 private final SafetyCenterInFlightIssueActionRepository 73 mSafetyCenterInFlightIssueActionRepository; 74 private final SafetyCenterIssueDismissalRepository mSafetyCenterIssueDismissalRepository; 75 SafetySourceDataRepository( Context context, SafetyCenterInFlightIssueActionRepository safetyCenterInFlightIssueActionRepository, SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository)76 SafetySourceDataRepository( 77 Context context, 78 SafetyCenterInFlightIssueActionRepository safetyCenterInFlightIssueActionRepository, 79 SafetyCenterIssueDismissalRepository safetyCenterIssueDismissalRepository) { 80 mContext = context; 81 mSafetyCenterInFlightIssueActionRepository = safetyCenterInFlightIssueActionRepository; 82 mSafetyCenterIssueDismissalRepository = safetyCenterIssueDismissalRepository; 83 } 84 85 /** 86 * Sets the latest {@link SafetySourceData} for the given {@code safetySourceId}, {@link 87 * SafetyEvent}, {@code packageName} and {@code userId}, and returns {@code true} if this caused 88 * any changes which would alter {@link SafetyCenterData}. 89 * 90 * <p>This method does not perform any validation, {@link 91 * SafetyCenterDataManager#setSafetySourceData(SafetySourceData, String, SafetyEvent, String, 92 * int)} should be called wherever validation is required. 93 * 94 * <p>Setting a {@code null} {@link SafetySourceData} evicts the current {@link 95 * SafetySourceData} entry and clears the {@link SafetyCenterIssueDismissalRepository} for the 96 * source. 97 * 98 * <p>This method may modify the {@link SafetyCenterIssueDismissalRepository}. 99 */ setSafetySourceData( @ullable SafetySourceData safetySourceData, String safetySourceId, @UserIdInt int userId)100 boolean setSafetySourceData( 101 @Nullable SafetySourceData safetySourceData, 102 String safetySourceId, 103 @UserIdInt int userId) { 104 SafetySourceKey key = SafetySourceKey.of(safetySourceId, userId); 105 safetySourceData = 106 AndroidLockScreenFix.maybeOverrideSafetySourceData( 107 mContext, safetySourceId, safetySourceData); 108 109 boolean sourceDataDiffers = !Objects.equals(safetySourceData, mSafetySourceData.get(key)); 110 boolean removedSourceError = mSafetySourceErrors.remove(key); 111 112 if (sourceDataDiffers) { 113 setSafetySourceDataInternal(key, safetySourceData); 114 } 115 116 setLastUpdatedNow(key); 117 return sourceDataDiffers || removedSourceError; 118 } 119 setSafetySourceDataInternal(SafetySourceKey key, @Nullable SafetySourceData data)120 private void setSafetySourceDataInternal(SafetySourceKey key, @Nullable SafetySourceData data) { 121 ArraySet<String> issueIds = new ArraySet<>(); 122 if (data == null) { 123 mSafetySourceData.remove(key); 124 mSourceStates.put(key, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_CLEARED); 125 } else { 126 mSafetySourceData.put(key, data); 127 for (int i = 0; i < data.getIssues().size(); i++) { 128 issueIds.add(data.getIssues().get(i).getId()); 129 } 130 mSourceStates.put(key, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__DATA_PROVIDED); 131 } 132 mSafetyCenterIssueDismissalRepository.updateIssuesForSource( 133 issueIds, key.getSourceId(), key.getUserId()); 134 } 135 136 /** 137 * Returns the latest {@link SafetySourceData} that was set by {@link #setSafetySourceData} for 138 * the given {@link SafetySourceKey}. 139 * 140 * <p>This method does not perform any validation, {@link 141 * SafetyCenterDataManager#getSafetySourceData(String, String, int)} should be called wherever 142 * validation is required. 143 * 144 * <p>Returns {@code null} if it was never set since boot, or if the entry was evicted using 145 * {@link #setSafetySourceData} with a {@code null} value. 146 */ 147 @Nullable getSafetySourceData(SafetySourceKey safetySourceKey)148 SafetySourceData getSafetySourceData(SafetySourceKey safetySourceKey) { 149 return mSafetySourceData.get(safetySourceKey); 150 } 151 152 /** Returns {@code true} if the given source has an error. */ sourceHasError(SafetySourceKey safetySourceKey)153 boolean sourceHasError(SafetySourceKey safetySourceKey) { 154 return mSafetySourceErrors.contains(safetySourceKey); 155 } 156 157 /** 158 * Reports the given {@link SafetySourceErrorDetails} for the given {@code safetySourceId} and 159 * {@code userId}, and returns {@code true} if this changed the repository's data. 160 * 161 * <p>This method does not perform any validation, {@link 162 * SafetyCenterDataManager#reportSafetySourceError(SafetySourceErrorDetails, String, String, 163 * int)} should be called wherever validation is required. 164 */ reportSafetySourceError( SafetySourceErrorDetails safetySourceErrorDetails, String safetySourceId, @UserIdInt int userId)165 boolean reportSafetySourceError( 166 SafetySourceErrorDetails safetySourceErrorDetails, 167 String safetySourceId, 168 @UserIdInt int userId) { 169 SafetyEvent safetyEvent = safetySourceErrorDetails.getSafetyEvent(); 170 Log.w(TAG, "Error reported from source: " + safetySourceId + ", for event: " + safetyEvent); 171 172 int safetyEventType = safetyEvent.getType(); 173 if (safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_FAILED 174 || safetyEventType == SafetyEvent.SAFETY_EVENT_TYPE_RESOLVING_ACTION_SUCCEEDED) { 175 return false; 176 } 177 178 SafetySourceKey sourceKey = SafetySourceKey.of(safetySourceId, userId); 179 mSourceStates.put(sourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__SOURCE_ERROR); 180 return setSafetySourceError(sourceKey); 181 } 182 183 /** 184 * Marks the given {@link SafetySourceKey} as having timed out during a refresh, and returns 185 * {@code true} if it caused a change to the stored data. 186 * 187 * @param setError whether we should clear the data associated with the source and set an error 188 */ markSafetySourceRefreshTimedOut(SafetySourceKey sourceKey, boolean setError)189 boolean markSafetySourceRefreshTimedOut(SafetySourceKey sourceKey, boolean setError) { 190 mSourceStates.put(sourceKey, SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__REFRESH_TIMEOUT); 191 if (!setError) { 192 return false; 193 } 194 return setSafetySourceError(sourceKey); 195 } 196 197 /** 198 * Marks the given {@link SafetySourceKey} as being in an error state and returns {@code true} 199 * if this changed the repository's data. 200 */ setSafetySourceError(SafetySourceKey safetySourceKey)201 private boolean setSafetySourceError(SafetySourceKey safetySourceKey) { 202 setLastUpdatedNow(safetySourceKey); 203 boolean removingSafetySourceDataChangedSafetyCenterData = 204 mSafetySourceData.remove(safetySourceKey) != null; 205 boolean addingSafetySourceErrorChangedSafetyCenterData = 206 mSafetySourceErrors.add(safetySourceKey); 207 return removingSafetySourceDataChangedSafetyCenterData 208 || addingSafetySourceErrorChangedSafetyCenterData; 209 } 210 211 /** 212 * Returns the {@link SafetySourceIssue} associated with the given {@link SafetyCenterIssueKey}. 213 * 214 * <p>Returns {@code null} if there is no such {@link SafetySourceIssue}. 215 */ 216 @Nullable getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey)217 SafetySourceIssue getSafetySourceIssue(SafetyCenterIssueKey safetyCenterIssueKey) { 218 SafetySourceKey key = 219 SafetySourceKey.of( 220 safetyCenterIssueKey.getSafetySourceId(), safetyCenterIssueKey.getUserId()); 221 SafetySourceData safetySourceData = mSafetySourceData.get(key); 222 if (safetySourceData == null) { 223 return null; 224 } 225 List<SafetySourceIssue> safetySourceIssues = safetySourceData.getIssues(); 226 227 SafetySourceIssue targetIssue = null; 228 for (int i = 0; i < safetySourceIssues.size(); i++) { 229 SafetySourceIssue safetySourceIssue = safetySourceIssues.get(i); 230 231 if (safetyCenterIssueKey.getSafetySourceIssueId().equals(safetySourceIssue.getId())) { 232 targetIssue = safetySourceIssue; 233 break; 234 } 235 } 236 237 return targetIssue; 238 } 239 240 /** 241 * Returns the {@link SafetySourceIssue.Action} associated with the given {@link 242 * SafetyCenterIssueActionId}. 243 * 244 * <p>Returns {@code null} if there is no associated {@link SafetySourceIssue}. 245 * 246 * <p>Returns {@code null} if the {@link SafetySourceIssue.Action} is currently in flight. 247 */ 248 @Nullable getSafetySourceIssueAction( SafetyCenterIssueActionId safetyCenterIssueActionId)249 SafetySourceIssue.Action getSafetySourceIssueAction( 250 SafetyCenterIssueActionId safetyCenterIssueActionId) { 251 SafetySourceIssue safetySourceIssue = 252 getSafetySourceIssue(safetyCenterIssueActionId.getSafetyCenterIssueKey()); 253 254 if (safetySourceIssue == null) { 255 return null; 256 } 257 258 return mSafetyCenterInFlightIssueActionRepository.getSafetySourceIssueAction( 259 safetyCenterIssueActionId, safetySourceIssue); 260 } 261 262 /** 263 * Returns the elapsed realtime millis of when the data of the given {@link SafetySourceKey} was 264 * last updated, or {@code 0L} if no update has occurred. 265 * 266 * @see SystemClock#elapsedRealtime() 267 */ 268 @UptimeMillisLong getSafetySourceLastUpdated(SafetySourceKey sourceKey)269 long getSafetySourceLastUpdated(SafetySourceKey sourceKey) { 270 Long lastUpdated = mSafetySourceLastUpdated.get(sourceKey); 271 if (lastUpdated != null) { 272 return lastUpdated; 273 } else { 274 return 0L; 275 } 276 } 277 setLastUpdatedNow(SafetySourceKey sourceKey)278 private void setLastUpdatedNow(SafetySourceKey sourceKey) { 279 mSafetySourceLastUpdated.put(sourceKey, SystemClock.elapsedRealtime()); 280 } 281 282 /** 283 * Returns the current {@link SafetyCenterStatsdLogger.SourceState} of the given {@link 284 * SafetySourceKey}. 285 */ 286 @SafetyCenterStatsdLogger.SourceState getSourceState(SafetySourceKey sourceKey)287 int getSourceState(SafetySourceKey sourceKey) { 288 Integer sourceState = mSourceStates.get(sourceKey); 289 if (sourceState != null) { 290 return sourceState; 291 } else { 292 return SAFETY_SOURCE_STATE_COLLECTED__SOURCE_STATE__NO_DATA_PROVIDED; 293 } 294 } 295 296 /** Clears all data for all users. */ clear()297 void clear() { 298 mSafetySourceData.clear(); 299 mSafetySourceErrors.clear(); 300 mSafetySourceLastUpdated.clear(); 301 mSourceStates.clear(); 302 } 303 304 /** Clears all data for the given user. */ clearForUser(@serIdInt int userId)305 void clearForUser(@UserIdInt int userId) { 306 // Loop in reverse index order to be able to remove entries while iterating. 307 for (int i = mSafetySourceData.size() - 1; i >= 0; i--) { 308 SafetySourceKey sourceKey = mSafetySourceData.keyAt(i); 309 if (sourceKey.getUserId() == userId) { 310 mSafetySourceData.removeAt(i); 311 } 312 } 313 for (int i = mSafetySourceErrors.size() - 1; i >= 0; i--) { 314 SafetySourceKey sourceKey = mSafetySourceErrors.valueAt(i); 315 if (sourceKey.getUserId() == userId) { 316 mSafetySourceErrors.removeAt(i); 317 } 318 } 319 for (int i = mSafetySourceLastUpdated.size() - 1; i >= 0; i--) { 320 SafetySourceKey sourceKey = mSafetySourceLastUpdated.keyAt(i); 321 if (sourceKey.getUserId() == userId) { 322 mSafetySourceLastUpdated.removeAt(i); 323 } 324 } 325 for (int i = mSourceStates.size() - 1; i >= 0; i--) { 326 SafetySourceKey sourceKey = mSourceStates.keyAt(i); 327 if (sourceKey.getUserId() == userId) { 328 mSourceStates.removeAt(i); 329 } 330 } 331 } 332 333 /** Dumps state for debugging purposes. */ dump(PrintWriter fout)334 void dump(PrintWriter fout) { 335 dumpArrayMap(fout, mSafetySourceData, "SOURCE DATA"); 336 int errorCount = mSafetySourceErrors.size(); 337 fout.println("SOURCE ERRORS (" + errorCount + ")"); 338 for (int i = 0; i < errorCount; i++) { 339 SafetySourceKey key = mSafetySourceErrors.valueAt(i); 340 fout.println("\t[" + i + "] " + key); 341 } 342 dumpArrayMap(fout, mSafetySourceLastUpdated, "LAST UPDATED"); 343 dumpArrayMap(fout, mSourceStates, "SOURCE STATES"); 344 fout.println(); 345 } 346 dumpArrayMap(PrintWriter fout, ArrayMap<K, V> map, String label)347 private static <K, V> void dumpArrayMap(PrintWriter fout, ArrayMap<K, V> map, String label) { 348 int count = map.size(); 349 fout.println(label + " (" + count + ")"); 350 for (int i = 0; i < count; i++) { 351 fout.println("\t[" + i + "] " + map.keyAt(i) + " -> " + map.valueAt(i)); 352 } 353 } 354 } 355