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