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