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

Commit 55c9b221 authored by Ruslan Tkhakokhov's avatar Ruslan Tkhakokhov Committed by Android (Google) Code Review
Browse files

Merge "Add implementation for BackupRestoreEventLogger"

parents 8a173b6a 8264c8db
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));
    }
}