Add implementation for BackupRestoreEventLogger

Bug: 252762060
Test: atest BackupRestoreEventLoggerTest
Change-Id: If5516b873b5a0562c819d45270c27187280da3e6
This commit is contained in:
Ruslan Tkhakokhov
2022-10-24 18:47:06 +01:00
parent a06729ee1f
commit 8264c8dbce
2 changed files with 392 additions and 16 deletions

View File

@@ -19,10 +19,15 @@ package android.app.backup;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.util.Slog;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
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;
@@ -38,6 +43,8 @@ import java.util.Map;
* @hide
*/
public 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.
@@ -72,6 +79,8 @@ public class BackupRestoreEventLogger {
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
@@ -81,6 +90,14 @@ public class BackupRestoreEventLogger {
*/
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;
}
/**
@@ -98,7 +115,7 @@ public class BackupRestoreEventLogger {
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
return true;
return logSuccess(OperationType.BACKUP, dataType, count);
}
/**
@@ -118,7 +135,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
return true;
return logFailure(OperationType.BACKUP, dataType, count, error);
}
/**
@@ -139,7 +156,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metaData) {
return true;
return logMetaData(OperationType.BACKUP, dataType, metaData);
}
/**
@@ -159,7 +176,7 @@ public class BackupRestoreEventLogger {
* @return boolean, indicating whether the log has been accepted.
*/
public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
return true;
return logSuccess(OperationType.RESTORE, dataType, count);
}
/**
@@ -181,7 +198,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
return true;
return logFailure(OperationType.RESTORE, dataType, count, error);
}
/**
@@ -204,7 +221,7 @@ public class BackupRestoreEventLogger {
*/
public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
@NonNull String metadata) {
return true;
return logMetaData(OperationType.RESTORE, dataType, metadata);
}
/**
@@ -214,7 +231,7 @@ public class BackupRestoreEventLogger {
* @hide
*/
public List<DataTypeResult> getLoggingResults() {
return Collections.emptyList();
return new ArrayList<>(mResults.values());
}
/**
@@ -227,22 +244,97 @@ public class BackupRestoreEventLogger {
return mOperationType;
}
private boolean logSuccess(@OperationType int operationType,
@BackupRestoreDataType String dataType, int count) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
return false;
}
dataTypeResult.mSuccessCount += count;
mResults.put(dataType, dataTypeResult);
return true;
}
private boolean logFailure(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, int count,
@Nullable @BackupRestoreError String error) {
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
return false;
}
dataTypeResult.mFailCount += count;
if (error != null) {
dataTypeResult.mErrors.merge(error, count, Integer::sum);
}
return true;
}
private boolean logMetaData(@OperationType int operationType,
@NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
if (mHashDigest == null) {
return false;
}
DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
if (dataTypeResult == null) {
return false;
}
dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
return true;
}
/**
* 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() == DATA_TYPES_ALLOWED) {
// 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));
}
/**
* Encapsulate logging results for a single data type.
*/
public static class DataTypeResult {
@BackupRestoreDataType
private final String mDataType;
private final int mSuccessCount;
private final Map<String, Integer> mErrors;
private final byte[] mMetadataHash;
private int mSuccessCount;
private int mFailCount;
private final Map<String, Integer> mErrors = new HashMap<>();
private byte[] mMetadataHash;
public DataTypeResult(String dataType, int successCount,
Map<String, Integer> errors, byte[] metadataHash) {
public DataTypeResult(String dataType) {
mDataType = dataType;
mSuccessCount = successCount;
mErrors = errors;
mMetadataHash = metadataHash;
}
@NonNull
@@ -259,6 +351,13 @@ public class BackupRestoreEventLogger {
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.

View File

@@ -0,0 +1,277 @@
/*
* 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 static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
import static com.google.common.truth.Truth.assertThat;
import static junit.framework.Assert.fail;
import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.platform.test.annotations.Presubmit;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupRestoreEventLoggerTest {
private static final int DATA_TYPES_ALLOWED = 15;
private static final String DATA_TYPE_1 = "data_type_1";
private static final String DATA_TYPE_2 = "data_type_2";
private static final String ERROR_1 = "error_1";
private static final String ERROR_2 = "error_2";
private static final String METADATA_1 = "metadata_1";
private static final String METADATA_2 = "metadata_2";
private BackupRestoreEventLogger mLogger;
private MessageDigest mHashDigest;
@Before
public void setUp() throws Exception {
mHashDigest = MessageDigest.getInstance("SHA-256");
}
@Test
public void testBackupLogger_rejectsRestoreLogs() {
mLogger = new BackupRestoreEventLogger(BACKUP);
assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
}
@Test
public void testRestoreLogger_rejectsBackupLogs() {
mLogger = new BackupRestoreEventLogger(RESTORE);
assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
}
@Test
public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
mLogger = new BackupRestoreEventLogger(BACKUP);
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
.isTrue();
assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
}
assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
.isFalse();
assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
@Test
public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
mLogger = new BackupRestoreEventLogger(RESTORE);
for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
String dataType = DATA_TYPE_1 + i;
assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
.isTrue();
assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
}
assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
.isFalse();
assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
}
@Test
public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
mLogger = new BackupRestoreEventLogger(BACKUP);
mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
byte[] expectedHash = getMetaDataHash(METADATA_2);
assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
}
@Test
public void testLogRestoreMetadata_repeatedCalls_recordsLatestMetadataHash() {
mLogger = new BackupRestoreEventLogger(RESTORE);
mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_2);
byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
byte[] expectedHash = getMetaDataHash(METADATA_2);
assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
}
@Test
public void testLogItemsBackedUp_repeatedCalls_recordsTotalItems() {
mLogger = new BackupRestoreEventLogger(BACKUP);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
mLogger.logItemsBackedUp(DATA_TYPE_1, secondCount);
int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
}
@Test
public void testLogItemsRestored_repeatedCalls_recordsTotalItems() {
mLogger = new BackupRestoreEventLogger(RESTORE);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
mLogger.logItemsRestored(DATA_TYPE_1, secondCount);
int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
}
@Test
public void testLogItemsBackedUp_multipleDataTypes_recordsEachDataType() {
mLogger = new BackupRestoreEventLogger(BACKUP);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
mLogger.logItemsBackedUp(DATA_TYPE_2, secondCount);
int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
assertThat(firstDataTypeCount).isEqualTo(firstCount);
assertThat(secondDataTypeCount).isEqualTo(secondCount);
}
@Test
public void testLogItemsRestored_multipleDataTypes_recordsEachDataType() {
mLogger = new BackupRestoreEventLogger(RESTORE);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
mLogger.logItemsRestored(DATA_TYPE_2, secondCount);
int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
assertThat(firstDataTypeCount).isEqualTo(firstCount);
assertThat(secondDataTypeCount).isEqualTo(secondCount);
}
@Test
public void testLogItemsBackupFailed_repeatedCalls_recordsTotalItems() {
mLogger = new BackupRestoreEventLogger(BACKUP);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, /* error */ null);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, "error");
int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
}
@Test
public void testLogItemsRestoreFailed_repeatedCalls_recordsTotalItems() {
mLogger = new BackupRestoreEventLogger(RESTORE);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, /* error */ null);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, "error");
int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
}
@Test
public void testLogItemsBackupFailed_multipleErrors_recordsEachError() {
mLogger = new BackupRestoreEventLogger(BACKUP);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_1);
int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
@Test
public void testLogItemsRestoreFailed_multipleErrors_recordsEachError() {
mLogger = new BackupRestoreEventLogger(RESTORE);
int firstCount = 10;
int secondCount = 5;
mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_1);
int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
.getErrors().get(ERROR_2);
assertThat(firstErrorTypeCount).isEqualTo(firstCount);
assertThat(secondErrorTypeCount).isEqualTo(secondCount);
}
private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
@BackupRestoreDataType String dataType) {
Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
if (result.isEmpty()) {
fail("Failed to find result for data type: " + dataType);
}
return result.get();
}
private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
List<DataTypeResult> resultList = logger.getLoggingResults();
return resultList.stream().filter(
dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
}
private byte[] getMetaDataHash(String metaData) {
return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
}
}