Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 8264c8db authored by Ruslan Tkhakokhov's avatar Ruslan Tkhakokhov
Browse files

Add implementation for BackupRestoreEventLogger

Bug: 252762060
Test: atest BackupRestoreEventLoggerTest
Change-Id: If5516b873b5a0562c819d45270c27187280da3e6
parent a06729ee
Loading
Loading
Loading
Loading
+115 −16
Original line number Diff line number Diff line
@@ -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.
+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));
    }
}