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

Commit 53d643c9 authored by Daniel Perez's avatar Daniel Perez
Browse files

Add backup metrics for settings that use the extractRelevantValues() method.

Adds metrics logging for settings that are queried from the db. That is: system, global, secure and device_specific_config. 

This is part of an effort to add metrics support to SettingsBackupAgent. For more details, see go/settings-backup-agent-metrics-design

Bug: 379861078 
Change-Id: If8c881da91902df4d630bf8eba854556bbdf7d37
Flag: com.android.server.backup.enable_metrics_settings_backup_agents
Tested: Manually by verifying that the metrics are logged in GMS Core.
parent 9be5c493
Loading
Loading
Loading
Loading
+56 −6
Original line number Diff line number Diff line
@@ -22,6 +22,7 @@ import android.annotation.UserIdInt;
import android.app.backup.BackupAgentHelper;
import android.app.backup.BackupDataInput;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.FullBackupDataOutput;
import android.content.ContentResolver;
import android.content.ContentValues;
@@ -82,6 +83,7 @@ import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.HashMap;
import java.util.zip.CRC32;

/**
@@ -194,6 +196,12 @@ public class SettingsBackupAgent extends BackupAgentHelper {
    private static final String KEY_LOCK_SETTINGS_PIN_ENHANCED_PRIVACY =
            "pin_enhanced_privacy";

    // Error messages for logging metrics.
    private static final String ERROR_COULD_NOT_READ_FROM_CURSOR =
        "could_not_read_from_cursor";
    private static final String ERROR_FAILED_TO_WRITE_ENTITY =
        "failed_to_write_entity";

    // Name of the temporary file we use during full backup/restore.  This is
    // stored in the full-backup tarfile as well, so should not be changed.
    private static final String STAGE_FILE = "flattened-data";
@@ -224,6 +232,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
    // The font_scale default value for this device.
    private float mDefaultFontScale;

    @Nullable private BackupRestoreEventLogger mBackupRestoreEventLogger;
    @VisibleForTesting boolean areAgentMetricsEnabled = false;
    @VisibleForTesting protected Map<String, Integer> numberOfSettingsPerKey;

    @Override
    public void onCreate() {
        if (DEBUG_BACKUP) Log.d(TAG, "onCreate() invoked");
@@ -232,6 +244,11 @@ public class SettingsBackupAgent extends BackupAgentHelper {
                .getStringArray(R.array.entryvalues_font_size);
        mSettingsHelper = new SettingsHelper(this);
        mWifiManager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
        if (com.android.server.backup.Flags.enableMetricsSettingsBackupAgents()) {
            mBackupRestoreEventLogger = this.getBackupRestoreEventLogger();
            numberOfSettingsPerKey = new HashMap<>();
            areAgentMetricsEnabled = true;
        }
        super.onCreate();
    }

@@ -654,23 +671,41 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        if (oldChecksum == newChecksum) {
            return oldChecksum;
        }
        writeDataForKey(key, data, output);
        return newChecksum;
    }

    @VisibleForTesting
    void writeDataForKey(String key, byte[] data, BackupDataOutput output) {
        boolean shouldLogMetrics =
            areAgentMetricsEnabled && numberOfSettingsPerKey.containsKey(key);
        try {
            if (DEBUG_BACKUP) {
                Log.v(TAG, "Writing entity " + key + " of size " + data.length);
            }
            output.writeEntityHeader(key, data.length);
            output.writeEntityData(data, data.length);
            if (shouldLogMetrics) {
                mBackupRestoreEventLogger
                    .logItemsBackedUp(key, numberOfSettingsPerKey.get(key));
            }
        } catch (IOException ioe) {
            // Bail
            if (shouldLogMetrics) {
                mBackupRestoreEventLogger
                    .logItemsBackupFailed(
                        key,
                        numberOfSettingsPerKey.get(key),
                        ERROR_FAILED_TO_WRITE_ENTITY);
            }
        }
        return newChecksum;
    }

    private byte[] getSystemSettings() {
        Cursor cursor = getContentResolver().query(Settings.System.CONTENT_URI, PROJECTION, null,
                null, null);
        try {
            return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP);
            return extractRelevantValues(cursor, SystemSettings.SETTINGS_TO_BACKUP, KEY_SYSTEM);
        } finally {
            cursor.close();
        }
@@ -680,7 +715,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        Cursor cursor = getContentResolver().query(Settings.Secure.CONTENT_URI, PROJECTION, null,
                null, null);
        try {
            return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP);
            return extractRelevantValues(cursor, SecureSettings.SETTINGS_TO_BACKUP, KEY_SECURE);
        } finally {
            cursor.close();
        }
@@ -690,7 +725,7 @@ public class SettingsBackupAgent extends BackupAgentHelper {
        Cursor cursor = getContentResolver().query(Settings.Global.CONTENT_URI, PROJECTION, null,
                null, null);
        try {
            return extractRelevantValues(cursor, getGlobalSettingsToBackup());
            return extractRelevantValues(cursor, getGlobalSettingsToBackup(), KEY_GLOBAL);
        } finally {
            cursor.close();
        }
@@ -1118,11 +1153,20 @@ public class SettingsBackupAgent extends BackupAgentHelper {
     *
     * @param cursor A cursor with settings data.
     * @param settings The settings to extract.
     * @param settingsKey The key of the settings to extract (eg system).
     * @return The byte array of extracted values.
     */
    private byte[] extractRelevantValues(Cursor cursor, String[] settings) {
    private byte[] extractRelevantValues(
        Cursor cursor, String[] settings, String settingsKey) {
        if (!cursor.moveToFirst()) {
            Log.e(TAG, "Couldn't read from the cursor");
            if (areAgentMetricsEnabled) {
                mBackupRestoreEventLogger
                    .logItemsBackupFailed(
                        settingsKey,
                        settings.length,
                        ERROR_COULD_NOT_READ_FROM_CURSOR);
            }
            return new byte[0];
        }

@@ -1181,6 +1225,10 @@ public class SettingsBackupAgent extends BackupAgentHelper {
            }
        }

        if (areAgentMetricsEnabled) {
            numberOfSettingsPerKey.put(settingsKey, backedUpSettingIndex);
        }

        // Aggregate the result.
        byte[] result = new byte[totalSize];
        int pos = 0;
@@ -1364,7 +1412,9 @@ public class SettingsBackupAgent extends BackupAgentHelper {
                     getContentResolver()
                             .query(Settings.Secure.CONTENT_URI, PROJECTION, null, null, null)) {
            return extractRelevantValues(
                    cursor, DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP);
                    cursor,
                    DeviceSpecificSettings.DEVICE_SPECIFIC_SETTINGS_TO_BACKUP,
                    KEY_DEVICE_SPECIFIC_CONFIG);
        }
    }

+155 −1
Original line number Diff line number Diff line
@@ -17,11 +17,22 @@
package com.android.providers.settings;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
import static junit.framework.Assert.assertFalse;
import static junit.framework.Assert.assertTrue;

import static org.junit.Assert.assertArrayEquals;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.when;

import android.app.backup.BackupAnnotations.BackupDestination;
import android.app.backup.BackupAnnotations.OperationType;
import android.app.backup.BackupDataOutput;
import android.app.backup.BackupRestoreEventLogger;
import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
@@ -32,7 +43,10 @@ import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;
import android.provider.Settings;
import android.provider.settings.validators.SettingsValidators;
import android.provider.settings.validators.Validator;
@@ -44,8 +58,12 @@ import androidx.test.runner.AndroidJUnit4;
import com.android.window.flags.Flags;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -54,12 +72,14 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;


/**
 * Tests for the SettingsHelperTest
 * Usage: atest SettingsProviderTest:SettingsBackupAgentTest
@@ -73,6 +93,8 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
    private static final Map<String, String> DEVICE_SPECIFIC_TEST_VALUES = new HashMap<>();
    private static final Map<String, String> TEST_VALUES = new HashMap<>();
    private static final Map<String, Validator> TEST_VALUES_VALIDATORS = new HashMap<>();
    private static final String TEST_KEY = "test_key";
    private static final String TEST_VALUE = "test_value";

    static {
        DEVICE_SPECIFIC_TEST_VALUES.put(Settings.Secure.DISPLAY_DENSITY_FORCED,
@@ -86,6 +108,13 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
        TEST_VALUES_VALIDATORS.put(PRESERVED_TEST_SETTING, SettingsValidators.ANY_STRING_VALIDATOR);
    }

    @Rule
    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
    @Rule
    public final MockitoRule mockito = MockitoJUnit.rule();

    @Mock private BackupDataOutput mBackupDataOutput;

    private TestFriendlySettingsBackupAgent mAgentUnderTest;
    private Context mContext;

@@ -262,6 +291,110 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
        assertEquals("1.5", testedMethod.apply("1.8"));
    }

    @Test
    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void onCreate_metricsFlagIsDisabled_areAgentMetricsEnabledIsFalse() {
        mAgentUnderTest.onCreate();

        assertFalse(mAgentUnderTest.areAgentMetricsEnabled);
    }

    @Test
    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void onCreate_flagIsEnabled_areAgentMetricsEnabledIsTrue() {
        mAgentUnderTest.onCreate();

        assertTrue(mAgentUnderTest.areAgentMetricsEnabled);
    }

    @Test
    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_dataWriteSucceeds_logsSuccessMetrics()
        throws IOException {
        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
        mAgentUnderTest.onCreate(
            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);

        mAgentUnderTest.writeDataForKey(
            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);

        DataTypeResult loggingResult =
            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
        assertNotNull(loggingResult);
        assertEquals(loggingResult.getSuccessCount(), 1);
    }

    @Test
    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityHeaderFails_logsFailureMetrics()
        throws IOException {
        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenThrow(new IOException());
        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
        mAgentUnderTest.onCreate(
            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);

        mAgentUnderTest.writeDataForKey(
            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);

        DataTypeResult loggingResult =
            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
        assertNotNull(loggingResult);
        assertEquals(loggingResult.getFailCount(), 1);
    }

    @Test
    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyContainsKey_writeEntityDataFails_logsFailureMetrics()
        throws IOException {
        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenThrow(new IOException());
        mAgentUnderTest.onCreate(
            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);

        mAgentUnderTest.writeDataForKey(
            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);

        DataTypeResult loggingResult =
            getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest);
        assertNotNull(loggingResult);
        assertEquals(loggingResult.getFailCount(), 1);
    }

    @Test
    @DisableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void writeDataForKey_metricsFlagIsDisabled_doesNotLogMetrics()
        throws IOException {
        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
        mAgentUnderTest.onCreate(
            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);
        mAgentUnderTest.setNumberOfSettingsPerKey(TEST_KEY, 1);

        mAgentUnderTest.writeDataForKey(
            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);

        assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
    }

    @Test
    @EnableFlags(com.android.server.backup.Flags.FLAG_ENABLE_METRICS_SETTINGS_BACKUP_AGENTS)
    public void writeDataForKey_metricsFlagIsEnabled_numberOfSettingsPerKeyDoesNotContainKey_doesNotLogMetrics()
        throws IOException {
        when(mBackupDataOutput.writeEntityHeader(anyString(), anyInt())).thenReturn(0);
        when(mBackupDataOutput.writeEntityData(any(byte[].class), anyInt())).thenReturn(0);
        mAgentUnderTest.onCreate(
            UserHandle.SYSTEM, BackupDestination.CLOUD, OperationType.BACKUP);

        mAgentUnderTest.writeDataForKey(
            TEST_KEY, TEST_VALUE.getBytes(), mBackupDataOutput);

        assertNull(getLoggingResultForDatatype(TEST_KEY, mAgentUnderTest));
    }

    private byte[] generateBackupData(Map<String, String> keyValueData) {
        int totalBytes = 0;
        for (String key : keyValueData.keySet()) {
@@ -329,6 +462,21 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {
        }
    }

    private DataTypeResult getLoggingResultForDatatype(
        String dataType, SettingsBackupAgent agent) {
        if (agent.getBackupRestoreEventLogger() == null) {
            return null;
        }
        List<DataTypeResult> loggingResults =
            agent.getBackupRestoreEventLogger().getLoggingResults();
        for (DataTypeResult result : loggingResults) {
            if (result.getDataType().equals(dataType)) {
                return result;
            }
        }
        return null;
    }

    private byte[] generateSingleKeyTestBackupData(String key, String value) throws IOException {
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            os.write(SettingsBackupAgent.toByteArray(key));
@@ -376,6 +524,12 @@ public class SettingsBackupAgentTest extends BaseSettingsProviderTest {

            return mSettingsWhitelist;
        }

        void setNumberOfSettingsPerKey(String key, int numberOfSettings) {
            if (numberOfSettingsPerKey != null) {
                this.numberOfSettingsPerKey.put(key, numberOfSettings);
            }
        }
    }

    /** The TestSettingsHelper tracks which values have been backed up and/or restored. */