1 /* 2 * Copyright (C) 2022 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 android.app.backup; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.app.backup.BackupAnnotations.OperationType; 23 import android.os.Bundle; 24 import android.os.Parcel; 25 import android.os.Parcelable; 26 import android.util.ArrayMap; 27 import android.util.Slog; 28 29 import com.android.server.backup.Flags; 30 31 import java.lang.annotation.Retention; 32 import java.lang.annotation.RetentionPolicy; 33 import java.nio.charset.StandardCharsets; 34 import java.security.MessageDigest; 35 import java.security.NoSuchAlgorithmException; 36 import java.util.ArrayList; 37 import java.util.Arrays; 38 import java.util.HashMap; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Objects; 42 43 /** 44 * Class to log B&R stats for each data type that is backed up and restored by the calling app. 45 * 46 * The logger instance is designed to accept a limited number of unique 47 * {link @BackupRestoreDataType} values, as determined by the underlying implementation. Apps are 48 * expected to have a small pre-defined set of data type values they use. Attempts to log too many 49 * unique values will be rejected. 50 * 51 * @hide 52 */ 53 @SystemApi 54 public final class BackupRestoreEventLogger { 55 private static final String TAG = "BackupRestoreEventLogger"; 56 57 /** 58 * Max number of unique data types for which an instance of this logger can store info. Attempts 59 * to use more distinct data type values will be rejected. 60 * 61 * @hide 62 */ 63 public static final int DATA_TYPES_ALLOWED = 150; 64 65 /** 66 * Denotes that the annotated element identifies a data type as required by the logging methods 67 * of {@code BackupRestoreEventLogger} 68 * 69 * @hide 70 */ 71 @Retention(RetentionPolicy.SOURCE) 72 public @interface BackupRestoreDataType {} 73 74 /** 75 * Denotes that the annotated element identifies an error type as required by the logging 76 * methods of {@code BackupRestoreEventLogger} 77 * 78 * @hide 79 */ 80 @Retention(RetentionPolicy.SOURCE) 81 public @interface BackupRestoreError {} 82 83 private final int mOperationType; 84 private final Map<String, DataTypeResult> mResults = new HashMap<>(); 85 private final MessageDigest mHashDigest; 86 87 /** 88 * @param operationType type of the operation for which logging will be performed. See 89 * {@link OperationType}. Attempts to use logging methods that don't match 90 * the specified operation type will be rejected (e.g. use backup methods 91 * for a restore logger and vice versa). 92 * 93 * @hide 94 */ BackupRestoreEventLogger(@perationType int operationType)95 public BackupRestoreEventLogger(@OperationType int operationType) { 96 mOperationType = operationType; 97 98 MessageDigest hashDigest = null; 99 try { 100 hashDigest = MessageDigest.getInstance("SHA-256"); 101 } catch (NoSuchAlgorithmException e) { 102 Slog.w("Couldn't create MessageDigest for hash computation", e); 103 } 104 mHashDigest = hashDigest; 105 } 106 107 /** 108 * Report progress during a backup operation. Call this method for each distinct data type that 109 * your {@code BackupAgent} implementation handles for any items of that type that have been 110 * successfully backed up. Repeated calls to this method with the same {@code dataType} will 111 * increase the total count of items associated with this data type by {@code count}. 112 * 113 * This method should be called from a {@link BackupAgent} implementation during an ongoing 114 * backup operation. 115 * 116 * @param dataType the type of data being backed. 117 * @param count number of items of the given type that have been successfully backed up. 118 */ logItemsBackedUp(@onNull @ackupRestoreDataType String dataType, int count)119 public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) { 120 logSuccess(OperationType.BACKUP, dataType, count); 121 } 122 123 /** 124 * Report errors during a backup operation. Call this method whenever items of a certain data 125 * type failed to back up. Repeated calls to this method with the same {@code dataType} / 126 * {@code error} will increase the total count of items associated with this data type / error 127 * by {@code count}. 128 * 129 * This method should be called from a {@link BackupAgent} implementation during an ongoing 130 * backup operation. 131 * 132 * @param dataType the type of data being backed. 133 * @param count number of items of the given type that have failed to back up. 134 * @param error optional, the error that has caused the failure. 135 */ logItemsBackupFailed(@onNull @ackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)136 public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count, 137 @Nullable @BackupRestoreError String error) { 138 logFailure(OperationType.BACKUP, dataType, count, error); 139 } 140 141 /** 142 * Report metadata associated with a data type that is currently being backed up, e.g. name of 143 * the selected wallpaper file / package. Repeated calls to this method with the same {@code 144 * dataType} will overwrite the previously supplied {@code metaData} value. 145 * 146 * The logger does not store or transmit the provided metadata value. Instead, it’s replaced 147 * with the SHA-256 hash of the provided string. 148 * 149 * This method should be called from a {@link BackupAgent} implementation during an ongoing 150 * backup operation. 151 * 152 * @param dataType the type of data being backed up. 153 * @param metaData the metadata associated with the data type. 154 */ logBackupMetadata(@onNull @ackupRestoreDataType String dataType, @NonNull String metaData)155 public void logBackupMetadata(@NonNull @BackupRestoreDataType String dataType, 156 @NonNull String metaData) { 157 logMetaData(OperationType.BACKUP, dataType, metaData); 158 } 159 160 /** 161 * Report progress during a restore operation. Call this method for each distinct data type that 162 * your {@code BackupAgent} implementation handles if any items of that type have been 163 * successfully restored. Repeated calls to this method with the same {@code dataType} will 164 * increase the total count of items associated with this data type by {@code count}. 165 * 166 * This method should either be called from a {@link BackupAgent} implementation during an 167 * ongoing restore operation or during any delayed restore actions the package had scheduled 168 * earlier (e.g. complete the restore once a certain dependency becomes available on the 169 * device). 170 * 171 * @param dataType the type of data being restored. 172 * @param count number of items of the given type that have been successfully restored. 173 */ logItemsRestored(@onNull @ackupRestoreDataType String dataType, int count)174 public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) { 175 logSuccess(OperationType.RESTORE, dataType, count); 176 } 177 178 /** 179 * Report errors during a restore operation. Call this method whenever items of a certain data 180 * type failed to restore. Repeated calls to this method with the same {@code dataType} / 181 * {@code error} will increase the total count of items associated with this data type / error 182 * by {@code count}. 183 * 184 * This method should either be called from a {@link BackupAgent} implementation during an 185 * ongoing restore operation or during any delayed restore actions the package had scheduled 186 * earlier (e.g. complete the restore once a certain dependency becomes available on the 187 * device). 188 * 189 * @param dataType the type of data being restored. 190 * @param count number of items of the given type that have failed to restore. 191 * @param error optional, the error that has caused the failure. 192 */ logItemsRestoreFailed(@onNull @ackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)193 public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count, 194 @Nullable @BackupRestoreError String error) { 195 logFailure(OperationType.RESTORE, dataType, count, error); 196 } 197 198 /** 199 * Report metadata associated with a data type that is currently being restored, e.g. name of 200 * the selected wallpaper file / package. Repeated calls to this method with the same 201 * {@code dataType} will overwrite the previously supplied {@code metaData} value. 202 * 203 * The logger does not store or transmit the provided metadata value. Instead, it’s replaced 204 * with the SHA-256 hash of the provided string. 205 * 206 * This method should either be called from a {@link BackupAgent} implementation during an 207 * ongoing restore operation or during any delayed restore actions the package had scheduled 208 * earlier (e.g. complete the restore once a certain dependency becomes available on the 209 * device). 210 * 211 * @param dataType the type of data being restored. 212 * @param metadata the metadata associated with the data type. 213 */ logRestoreMetadata(@onNull @ackupRestoreDataType String dataType, @NonNull String metadata)214 public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType, 215 @NonNull String metadata) { 216 logMetaData(OperationType.RESTORE, dataType, metadata); 217 } 218 219 /** 220 * Get the contents of this logger. This method should only be used by B&R code in Android 221 * Framework. 222 * 223 * @hide 224 */ getLoggingResults()225 public List<DataTypeResult> getLoggingResults() { 226 return new ArrayList<>(mResults.values()); 227 } 228 229 /** 230 * Get the operation type for which this logger was created. This method should only be used 231 * by B&R code in Android Framework. 232 * 233 * @hide 234 */ 235 @OperationType getOperationType()236 public int getOperationType() { 237 return mOperationType; 238 } 239 240 /** 241 * Clears data logged. This method should only be used by B&R code in Android Framework. 242 * 243 * @hide 244 */ clearData()245 public void clearData() { 246 mResults.clear(); 247 248 } 249 logSuccess(@perationType int operationType, @BackupRestoreDataType String dataType, int count)250 private void logSuccess(@OperationType int operationType, 251 @BackupRestoreDataType String dataType, int count) { 252 DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType); 253 if (dataTypeResult == null) { 254 return; 255 } 256 257 dataTypeResult.mSuccessCount += count; 258 mResults.put(dataType, dataTypeResult); 259 } 260 logFailure(@perationType int operationType, @NonNull @BackupRestoreDataType String dataType, int count, @Nullable @BackupRestoreError String error)261 private void logFailure(@OperationType int operationType, 262 @NonNull @BackupRestoreDataType String dataType, int count, 263 @Nullable @BackupRestoreError String error) { 264 DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType); 265 if (dataTypeResult == null) { 266 return; 267 } 268 269 dataTypeResult.mFailCount += count; 270 if (error != null) { 271 dataTypeResult.mErrors.merge(error, count, Integer::sum); 272 } 273 } 274 logMetaData(@perationType int operationType, @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData)275 private void logMetaData(@OperationType int operationType, 276 @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) { 277 if (mHashDigest == null) { 278 return; 279 } 280 DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType); 281 if (dataTypeResult == null) { 282 return; 283 } 284 285 dataTypeResult.mMetadataHash = getMetaDataHash(metaData); 286 } 287 288 /** 289 * Get the result container for the given data type. 290 * 291 * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or 292 * {@code null} if the logger can't accept logs for the given data type. 293 */ 294 @Nullable getDataTypeResult(@perationType int operationType, @BackupRestoreDataType String dataType)295 private DataTypeResult getDataTypeResult(@OperationType int operationType, 296 @BackupRestoreDataType String dataType) { 297 if (operationType != mOperationType) { 298 // Operation type for which we're trying to record logs doesn't match the operation 299 // type for which this logger instance was created. 300 Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType 301 + ", trying to log for " + operationType); 302 return null; 303 } 304 305 if (!mResults.containsKey(dataType)) { 306 if (mResults.keySet().size() == getDataTypesAllowed()) { 307 // This is a new data type and we're already at capacity. 308 Slog.d(TAG, "Logger is full, ignoring new data type"); 309 return null; 310 } 311 312 mResults.put(dataType, new DataTypeResult(dataType)); 313 } 314 315 return mResults.get(dataType); 316 } 317 getMetaDataHash(String metaData)318 private byte[] getMetaDataHash(String metaData) { 319 return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8)); 320 } 321 getDataTypesAllowed()322 private int getDataTypesAllowed(){ 323 if (Flags.enableIncreaseDatatypesForAgentLogging()) { 324 return DATA_TYPES_ALLOWED; 325 } else { 326 return 15; 327 } 328 } 329 330 /** @hide */ toString(DataTypeResult result)331 public static String toString(DataTypeResult result) { 332 Objects.requireNonNull(result, "result cannot be null"); 333 StringBuilder string = new StringBuilder("type=").append(result.mDataType) 334 .append(", successCount=").append(result.mSuccessCount) 335 .append(", failCount=").append(result.mFailCount); 336 if (!result.mErrors.isEmpty()) { 337 string.append(", errors=").append(result.mErrors); 338 } 339 if (result.mMetadataHash != null) { 340 string.append(", metadataHash=").append(Arrays.toString(result.mMetadataHash)); 341 } 342 return string.toString(); 343 } 344 345 /** 346 * Encapsulate logging results for a single data type. 347 */ 348 public static final class DataTypeResult implements Parcelable { 349 @BackupRestoreDataType 350 private final String mDataType; 351 private int mSuccessCount; 352 private int mFailCount; 353 private final Map<String, Integer> mErrors = new HashMap<>(); 354 private byte[] mMetadataHash; 355 DataTypeResult(@onNull String dataType)356 public DataTypeResult(@NonNull String dataType) { 357 mDataType = dataType; 358 } 359 360 @NonNull 361 @BackupRestoreDataType getDataType()362 public String getDataType() { 363 return mDataType; 364 } 365 366 /** 367 * @return number of items of the given data type that have been successfully backed up or 368 * restored. 369 */ getSuccessCount()370 public int getSuccessCount() { 371 return mSuccessCount; 372 } 373 374 /** 375 * @return number of items of the given data type that have failed to back up or restore. 376 */ getFailCount()377 public int getFailCount() { 378 return mFailCount; 379 } 380 381 /** 382 * @return mapping of {@link BackupRestoreError} to the count of items that are affected by 383 * the error. 384 */ 385 @NonNull getErrors()386 public Map<String, Integer> getErrors() { 387 return mErrors; 388 } 389 390 /** 391 * @return SHA-256 hash of the metadata or {@code null} of no metadata has been logged for 392 * this data type. 393 */ 394 @Nullable getMetadataHash()395 public byte[] getMetadataHash() { 396 return mMetadataHash; 397 } 398 399 @Override describeContents()400 public int describeContents() { 401 return 0; 402 } 403 404 @Override writeToParcel(@onNull Parcel dest, int flags)405 public void writeToParcel(@NonNull Parcel dest, int flags) { 406 dest.writeString(mDataType); 407 408 dest.writeInt(mSuccessCount); 409 410 dest.writeInt(mFailCount); 411 412 Bundle errorsBundle = new Bundle(); 413 for (Map.Entry<String, Integer> e : mErrors.entrySet()) { 414 errorsBundle.putInt(e.getKey(), e.getValue()); 415 } 416 dest.writeBundle(errorsBundle); 417 418 dest.writeByteArray(mMetadataHash); 419 } 420 421 @NonNull 422 public static final Parcelable.Creator<DataTypeResult> CREATOR = 423 new Parcelable.Creator<>() { 424 public DataTypeResult createFromParcel(Parcel in) { 425 String dataType = in.readString(); 426 427 int successCount = in.readInt(); 428 429 int failCount = in.readInt(); 430 431 Map<String, Integer> errors = new ArrayMap<>(); 432 Bundle errorsBundle = in.readBundle(getClass().getClassLoader()); 433 for (String key : errorsBundle.keySet()) { 434 errors.put(key, errorsBundle.getInt(key)); 435 } 436 437 byte[] metadataHash = in.createByteArray(); 438 439 DataTypeResult result = new DataTypeResult(dataType); 440 result.mSuccessCount = successCount; 441 result.mFailCount = failCount; 442 result.mErrors.putAll(errors); 443 result.mMetadataHash = metadataHash; 444 return result; 445 } 446 447 public DataTypeResult[] newArray(int size) { 448 return new DataTypeResult[size]; 449 } 450 }; 451 } 452 } 453