Loading core/java/android/app/backup/BackupRestoreEventLogger.java +115 −16 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading @@ -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; } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -214,7 +231,7 @@ public class BackupRestoreEventLogger { * @hide */ public List<DataTypeResult> getLoggingResults() { return Collections.emptyList(); return new ArrayList<>(mResults.values()); } /** Loading @@ -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 Loading @@ -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. Loading core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java 0 → 100644 +277 −0 Original line number Diff line number Diff line /* * 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)); } } Loading
core/java/android/app/backup/BackupRestoreEventLogger.java +115 −16 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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. Loading Loading @@ -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 Loading @@ -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; } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -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); } /** Loading @@ -214,7 +231,7 @@ public class BackupRestoreEventLogger { * @hide */ public List<DataTypeResult> getLoggingResults() { return Collections.emptyList(); return new ArrayList<>(mResults.values()); } /** Loading @@ -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 Loading @@ -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. Loading
core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java 0 → 100644 +277 −0 Original line number Diff line number Diff line /* * 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)); } }