436 lines
16 KiB
Java
436 lines
16 KiB
Java
![]() |
/*
|
|||
|
* Copyright (C) 2022 The Android Open Source Project
|
|||
|
*
|
|||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|||
|
* you may not use this file except in compliance with the License.
|
|||
|
* You may obtain a copy of the License at
|
|||
|
*
|
|||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|||
|
*
|
|||
|
* Unless required by applicable law or agreed to in writing, software
|
|||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|||
|
* See the License for the specific language governing permissions and
|
|||
|
* limitations under the License.
|
|||
|
*/
|
|||
|
|
|||
|
package android.app.backup;
|
|||
|
|
|||
|
import android.annotation.NonNull;
|
|||
|
import android.annotation.Nullable;
|
|||
|
import android.annotation.SystemApi;
|
|||
|
import android.app.backup.BackupAnnotations.OperationType;
|
|||
|
import android.os.Bundle;
|
|||
|
import android.os.Parcel;
|
|||
|
import android.os.Parcelable;
|
|||
|
import android.util.ArrayMap;
|
|||
|
import android.util.Slog;
|
|||
|
|
|||
|
import com.android.server.backup.Flags;
|
|||
|
|
|||
|
import java.lang.annotation.Retention;
|
|||
|
import java.lang.annotation.RetentionPolicy;
|
|||
|
import java.nio.charset.StandardCharsets;
|
|||
|
import java.security.MessageDigest;
|
|||
|
import java.security.NoSuchAlgorithmException;
|
|||
|
import java.util.ArrayList;
|
|||
|
import java.util.HashMap;
|
|||
|
import java.util.List;
|
|||
|
import java.util.Map;
|
|||
|
|
|||
|
/**
|
|||
|
* Class to log B&R stats for each data type that is backed up and restored by the calling app.
|
|||
|
*
|
|||
|
* The logger instance is designed to accept a limited number of unique
|
|||
|
* {link @BackupRestoreDataType} values, as determined by the underlying implementation. Apps are
|
|||
|
* expected to have a small pre-defined set of data type values they use. Attempts to log too many
|
|||
|
* unique values will be rejected.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
@SystemApi
|
|||
|
public final class BackupRestoreEventLogger {
|
|||
|
private static final String TAG = "BackupRestoreEventLogger";
|
|||
|
|
|||
|
/**
|
|||
|
* Max number of unique data types for which an instance of this logger can store info. Attempts
|
|||
|
* to use more distinct data type values will be rejected.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public static final int DATA_TYPES_ALLOWED = 150;
|
|||
|
|
|||
|
/**
|
|||
|
* Denotes that the annotated element identifies a data type as required by the logging methods
|
|||
|
* of {@code BackupRestoreEventLogger}
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
@Retention(RetentionPolicy.SOURCE)
|
|||
|
public @interface BackupRestoreDataType {}
|
|||
|
|
|||
|
/**
|
|||
|
* Denotes that the annotated element identifies an error type as required by the logging
|
|||
|
* methods of {@code BackupRestoreEventLogger}
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
@Retention(RetentionPolicy.SOURCE)
|
|||
|
public @interface BackupRestoreError {}
|
|||
|
|
|||
|
private final int mOperationType;
|
|||
|
private final Map<String, DataTypeResult> mResults = new HashMap<>();
|
|||
|
private final MessageDigest mHashDigest;
|
|||
|
|
|||
|
/**
|
|||
|
* @param operationType type of the operation for which logging will be performed. See
|
|||
|
* {@link OperationType}. Attempts to use logging methods that don't match
|
|||
|
* the specified operation type will be rejected (e.g. use backup methods
|
|||
|
* for a restore logger and vice versa).
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public BackupRestoreEventLogger(@OperationType int operationType) {
|
|||
|
mOperationType = operationType;
|
|||
|
|
|||
|
MessageDigest hashDigest = null;
|
|||
|
try {
|
|||
|
hashDigest = MessageDigest.getInstance("SHA-256");
|
|||
|
} catch (NoSuchAlgorithmException e) {
|
|||
|
Slog.w("Couldn't create MessageDigest for hash computation", e);
|
|||
|
}
|
|||
|
mHashDigest = hashDigest;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report progress during a backup operation. Call this method for each distinct data type that
|
|||
|
* your {@code BackupAgent} implementation handles for any items of that type that have been
|
|||
|
* successfully backed up. Repeated calls to this method with the same {@code dataType} will
|
|||
|
* increase the total count of items associated with this data type by {@code count}.
|
|||
|
*
|
|||
|
* This method should be called from a {@link BackupAgent} implementation during an ongoing
|
|||
|
* backup operation.
|
|||
|
*
|
|||
|
* @param dataType the type of data being backed.
|
|||
|
* @param count number of items of the given type that have been successfully backed up.
|
|||
|
*/
|
|||
|
public void logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
|
|||
|
logSuccess(OperationType.BACKUP, dataType, count);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report errors during a backup operation. Call this method whenever items of a certain data
|
|||
|
* type failed to back up. Repeated calls to this method with the same {@code dataType} /
|
|||
|
* {@code error} will increase the total count of items associated with this data type / error
|
|||
|
* by {@code count}.
|
|||
|
*
|
|||
|
* This method should be called from a {@link BackupAgent} implementation during an ongoing
|
|||
|
* backup operation.
|
|||
|
*
|
|||
|
* @param dataType the type of data being backed.
|
|||
|
* @param count number of items of the given type that have failed to back up.
|
|||
|
* @param error optional, the error that has caused the failure.
|
|||
|
*/
|
|||
|
public void logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
|
|||
|
@Nullable @BackupRestoreError String error) {
|
|||
|
logFailure(OperationType.BACKUP, dataType, count, error);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report metadata associated with a data type that is currently being backed up, e.g. name of
|
|||
|
* the selected wallpaper file / package. Repeated calls to this method with the same {@code
|
|||
|
* dataType} will overwrite the previously supplied {@code metaData} value.
|
|||
|
*
|
|||
|
* The logger does not store or transmit the provided metadata value. Instead, it’s replaced
|
|||
|
* with the SHA-256 hash of the provided string.
|
|||
|
*
|
|||
|
* This method should be called from a {@link BackupAgent} implementation during an ongoing
|
|||
|
* backup operation.
|
|||
|
*
|
|||
|
* @param dataType the type of data being backed up.
|
|||
|
* @param metaData the metadata associated with the data type.
|
|||
|
*/
|
|||
|
public void logBackupMetadata(@NonNull @BackupRestoreDataType String dataType,
|
|||
|
@NonNull String metaData) {
|
|||
|
logMetaData(OperationType.BACKUP, dataType, metaData);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report progress during a restore operation. Call this method for each distinct data type that
|
|||
|
* your {@code BackupAgent} implementation handles if any items of that type have been
|
|||
|
* successfully restored. Repeated calls to this method with the same {@code dataType} will
|
|||
|
* increase the total count of items associated with this data type by {@code count}.
|
|||
|
*
|
|||
|
* This method should either be called from a {@link BackupAgent} implementation during an
|
|||
|
* ongoing restore operation or during any delayed restore actions the package had scheduled
|
|||
|
* earlier (e.g. complete the restore once a certain dependency becomes available on the
|
|||
|
* device).
|
|||
|
*
|
|||
|
* @param dataType the type of data being restored.
|
|||
|
* @param count number of items of the given type that have been successfully restored.
|
|||
|
*/
|
|||
|
public void logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
|
|||
|
logSuccess(OperationType.RESTORE, dataType, count);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report errors during a restore operation. Call this method whenever items of a certain data
|
|||
|
* type failed to restore. Repeated calls to this method with the same {@code dataType} /
|
|||
|
* {@code error} will increase the total count of items associated with this data type / error
|
|||
|
* by {@code count}.
|
|||
|
*
|
|||
|
* This method should either be called from a {@link BackupAgent} implementation during an
|
|||
|
* ongoing restore operation or during any delayed restore actions the package had scheduled
|
|||
|
* earlier (e.g. complete the restore once a certain dependency becomes available on the
|
|||
|
* device).
|
|||
|
*
|
|||
|
* @param dataType the type of data being restored.
|
|||
|
* @param count number of items of the given type that have failed to restore.
|
|||
|
* @param error optional, the error that has caused the failure.
|
|||
|
*/
|
|||
|
public void logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
|
|||
|
@Nullable @BackupRestoreError String error) {
|
|||
|
logFailure(OperationType.RESTORE, dataType, count, error);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Report metadata associated with a data type that is currently being restored, e.g. name of
|
|||
|
* the selected wallpaper file / package. Repeated calls to this method with the same
|
|||
|
* {@code dataType} will overwrite the previously supplied {@code metaData} value.
|
|||
|
*
|
|||
|
* The logger does not store or transmit the provided metadata value. Instead, it’s replaced
|
|||
|
* with the SHA-256 hash of the provided string.
|
|||
|
*
|
|||
|
* This method should either be called from a {@link BackupAgent} implementation during an
|
|||
|
* ongoing restore operation or during any delayed restore actions the package had scheduled
|
|||
|
* earlier (e.g. complete the restore once a certain dependency becomes available on the
|
|||
|
* device).
|
|||
|
*
|
|||
|
* @param dataType the type of data being restored.
|
|||
|
* @param metadata the metadata associated with the data type.
|
|||
|
*/
|
|||
|
public void logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
|
|||
|
@NonNull String metadata) {
|
|||
|
logMetaData(OperationType.RESTORE, dataType, metadata);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the contents of this logger. This method should only be used by B&R code in Android
|
|||
|
* Framework.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public List<DataTypeResult> getLoggingResults() {
|
|||
|
return new ArrayList<>(mResults.values());
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the operation type for which this logger was created. This method should only be used
|
|||
|
* by B&R code in Android Framework.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
@OperationType
|
|||
|
public int getOperationType() {
|
|||
|
return mOperationType;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Clears data logged. This method should only be used by B&R code in Android Framework.
|
|||
|
*
|
|||
|
* @hide
|
|||
|
*/
|
|||
|
public void clearData() {
|
|||
|
mResults.clear();
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
private void logSuccess(@OperationType int operationType,
|
|||
|
@BackupRestoreDataType String dataType, int count) {
|
|||
|
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
|
|||
|
if (dataTypeResult == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dataTypeResult.mSuccessCount += count;
|
|||
|
mResults.put(dataType, dataTypeResult);
|
|||
|
}
|
|||
|
|
|||
|
private void logFailure(@OperationType int operationType,
|
|||
|
@NonNull @BackupRestoreDataType String dataType, int count,
|
|||
|
@Nullable @BackupRestoreError String error) {
|
|||
|
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
|
|||
|
if (dataTypeResult == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dataTypeResult.mFailCount += count;
|
|||
|
if (error != null) {
|
|||
|
dataTypeResult.mErrors.merge(error, count, Integer::sum);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
private void logMetaData(@OperationType int operationType,
|
|||
|
@NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
|
|||
|
if (mHashDigest == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
|
|||
|
if (dataTypeResult == null) {
|
|||
|
return;
|
|||
|
}
|
|||
|
|
|||
|
dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Get the result container for the given data type.
|
|||
|
*
|
|||
|
* @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
|
|||
|
* {@code null} if the logger can't accept logs for the given data type.
|
|||
|
*/
|
|||
|
@Nullable
|
|||
|
private DataTypeResult getDataTypeResult(@OperationType int operationType,
|
|||
|
@BackupRestoreDataType String dataType) {
|
|||
|
if (operationType != mOperationType) {
|
|||
|
// Operation type for which we're trying to record logs doesn't match the operation
|
|||
|
// type for which this logger instance was created.
|
|||
|
Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
|
|||
|
+ ", trying to log for " + operationType);
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
if (!mResults.containsKey(dataType)) {
|
|||
|
if (mResults.keySet().size() == getDataTypesAllowed()) {
|
|||
|
// This is a new data type and we're already at capacity.
|
|||
|
Slog.d(TAG, "Logger is full, ignoring new data type");
|
|||
|
return null;
|
|||
|
}
|
|||
|
|
|||
|
mResults.put(dataType, new DataTypeResult(dataType));
|
|||
|
}
|
|||
|
|
|||
|
return mResults.get(dataType);
|
|||
|
}
|
|||
|
|
|||
|
private byte[] getMetaDataHash(String metaData) {
|
|||
|
return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
|
|||
|
}
|
|||
|
|
|||
|
private int getDataTypesAllowed(){
|
|||
|
if (Flags.enableIncreaseDatatypesForAgentLogging()) {
|
|||
|
return DATA_TYPES_ALLOWED;
|
|||
|
} else {
|
|||
|
return 15;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Encapsulate logging results for a single data type.
|
|||
|
*/
|
|||
|
public static final class DataTypeResult implements Parcelable {
|
|||
|
@BackupRestoreDataType
|
|||
|
private final String mDataType;
|
|||
|
private int mSuccessCount;
|
|||
|
private int mFailCount;
|
|||
|
private final Map<String, Integer> mErrors = new HashMap<>();
|
|||
|
private byte[] mMetadataHash;
|
|||
|
|
|||
|
public DataTypeResult(@NonNull String dataType) {
|
|||
|
mDataType = dataType;
|
|||
|
}
|
|||
|
|
|||
|
@NonNull
|
|||
|
@BackupRestoreDataType
|
|||
|
public String getDataType() {
|
|||
|
return mDataType;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return number of items of the given data type that have been successfully backed up or
|
|||
|
* restored.
|
|||
|
*/
|
|||
|
public int getSuccessCount() {
|
|||
|
return mSuccessCount;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return number of items of the given data type that have failed to back up or restore.
|
|||
|
*/
|
|||
|
public int getFailCount() {
|
|||
|
return mFailCount;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return mapping of {@link BackupRestoreError} to the count of items that are affected by
|
|||
|
* the error.
|
|||
|
*/
|
|||
|
@NonNull
|
|||
|
public Map<String, Integer> getErrors() {
|
|||
|
return mErrors;
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* @return SHA-256 hash of the metadata or {@code null} of no metadata has been logged for
|
|||
|
* this data type.
|
|||
|
*/
|
|||
|
@Nullable
|
|||
|
public byte[] getMetadataHash() {
|
|||
|
return mMetadataHash;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public int describeContents() {
|
|||
|
return 0;
|
|||
|
}
|
|||
|
|
|||
|
@Override
|
|||
|
public void writeToParcel(@NonNull Parcel dest, int flags) {
|
|||
|
dest.writeString(mDataType);
|
|||
|
|
|||
|
dest.writeInt(mSuccessCount);
|
|||
|
|
|||
|
dest.writeInt(mFailCount);
|
|||
|
|
|||
|
Bundle errorsBundle = new Bundle();
|
|||
|
for (Map.Entry<String, Integer> e : mErrors.entrySet()) {
|
|||
|
errorsBundle.putInt(e.getKey(), e.getValue());
|
|||
|
}
|
|||
|
dest.writeBundle(errorsBundle);
|
|||
|
|
|||
|
dest.writeByteArray(mMetadataHash);
|
|||
|
}
|
|||
|
|
|||
|
@NonNull
|
|||
|
public static final Parcelable.Creator<DataTypeResult> CREATOR =
|
|||
|
new Parcelable.Creator<>() {
|
|||
|
public DataTypeResult createFromParcel(Parcel in) {
|
|||
|
String dataType = in.readString();
|
|||
|
|
|||
|
int successCount = in.readInt();
|
|||
|
|
|||
|
int failCount = in.readInt();
|
|||
|
|
|||
|
Map<String, Integer> errors = new ArrayMap<>();
|
|||
|
Bundle errorsBundle = in.readBundle(getClass().getClassLoader());
|
|||
|
for (String key : errorsBundle.keySet()) {
|
|||
|
errors.put(key, errorsBundle.getInt(key));
|
|||
|
}
|
|||
|
|
|||
|
byte[] metadataHash = in.createByteArray();
|
|||
|
|
|||
|
DataTypeResult result = new DataTypeResult(dataType);
|
|||
|
result.mSuccessCount = successCount;
|
|||
|
result.mFailCount = failCount;
|
|||
|
result.mErrors.putAll(errors);
|
|||
|
result.mMetadataHash = metadataHash;
|
|||
|
return result;
|
|||
|
}
|
|||
|
|
|||
|
public DataTypeResult[] newArray(int size) {
|
|||
|
return new DataTypeResult[size];
|
|||
|
}
|
|||
|
};
|
|||
|
}
|
|||
|
}
|