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

Commit e8d8bf4d authored by beatricemarch's avatar beatricemarch Committed by Beatrice Marchegiani
Browse files

Add a 60 days retention period to BMM Events

Test: manual testing(set a 5 minutes retention period, factory reset the test device, go trough SUW, confirm that the logs show in backup dumpsys, confirm that logs  are deleted after 5 minutes)
atest CtsBackupHostTestCases, GtsBackupHostTestCases
atest BackupManagerMonitorDumpsysUtilsTest, BackupManagerMonitorEventSenderTest, UserBackupManagerServiceTest
Bug: 297159898

Change-Id: If8ce57c68cf9ffaa5472e5e14aee72c6bf2e5153
parent 05d5d5c2
Loading
Loading
Loading
Loading
+16 −0
Original line number Diff line number Diff line
@@ -654,6 +654,13 @@ public class UserBackupManagerService {
        // the pending backup set
        mBackupHandler.postDelayed(this::parseLeftoverJournals, INITIALIZATION_DELAY_MILLIS);

        // check if we are past the retention period for BMM Events,
        // if so delete expired events and do not print them to dumpsys
        BackupManagerMonitorDumpsysUtils backupManagerMonitorDumpsysUtils =
                new BackupManagerMonitorDumpsysUtils();
        mBackupHandler.postDelayed(backupManagerMonitorDumpsysUtils::deleteExpiredBMMEvents,
                INITIALIZATION_DELAY_MILLIS);

        mBackupPreferences = new UserBackupPreferences(mContext, mBaseStateDir);

        // Power management
@@ -4181,7 +4188,16 @@ public class UserBackupManagerService {
    private void dumpBMMEvents(PrintWriter pw) {
        BackupManagerMonitorDumpsysUtils bm =
                new BackupManagerMonitorDumpsysUtils();
        if (bm.deleteExpiredBMMEvents()) {
            pw.println("BACKUP MANAGER MONITOR EVENTS HAVE EXPIRED");
            return;
        }
        File events = bm.getBMMEventsFile();
        if (events.length() == 0){
            // We have not recorded BMMEvents yet.
            pw.println("NO BACKUP MANAGER MONITOR EVENTS");
            return;
        }
        pw.println("START OF BACKUP MANAGER MONITOR EVENTS");
        try (BufferedReader reader = new BufferedReader(new FileReader(events))) {
            String line;
+156 −10
Original line number Diff line number Diff line
@@ -23,15 +23,22 @@ import android.os.Bundle;
import android.os.Environment;
import android.util.Slog;

import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastPrintWriter;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.TimeUnit;


/*
@@ -46,12 +53,21 @@ public class BackupManagerMonitorDumpsysUtils {
    // Name of the subdirectory where the text file containing the BMM events will be stored.
    // Same as {@link UserBackupManagerFiles}
    private static final String BACKUP_PERSISTENT_DIR = "backup";
    private static final String INITIAL_SETUP_TIMESTAMP_KEY = "initialSetupTimestamp";
    // Retention period of 60 days (in millisec) for the BMM Events.
    // After tha time has passed the text file containing the BMM events will be emptied
    private static final long LOGS_RETENTION_PERIOD_MILLISEC = 60 * TimeUnit.DAYS.toMillis(1);
    // We cache the value of IsAfterRetentionPeriod() to avoid unnecessary disk I/O
    // mIsAfterRetentionPeriodCached tracks if we have cached the value of IsAfterRetentionPeriod()
    private boolean mIsAfterRetentionPeriodCached = false;
    // The cahched value of IsAfterRetentionPeriod()
    private boolean mIsAfterRetentionPeriod;

    /**
     * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that
     * will be persisted in a text file and printed in the dumpsys.
     *
     * If the evenntBundle passed is not a RESTORE event, return early
     * If the eventBundle passed is not a RESTORE event, return early
     *
     * Key information related to the event:
     * - Timestamp (HAS TO ALWAYS BE THE FIRST LINE OF EACH EVENT)
@@ -73,6 +89,11 @@ public class BackupManagerMonitorDumpsysUtils {
     * Agent Error - Category: no_wallpaper, Count: 1
     */
    public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) {
        if (isAfterRetentionPeriod()) {
            // We only log data for the first 60 days since setup
            return;
        }

        if (eventBundle == null) {
            return;
        }
@@ -89,6 +110,12 @@ public class BackupManagerMonitorDumpsysUtils {
        }
        File bmmEvents = getBMMEventsFile();

        if (bmmEvents.length() == 0) {
            // We are parsing the first restore event.
            // Time to also record the setup timestamp of the device
            recordSetUpTimestamp();
        }

        try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true);
             PrintWriter pw = new FastPrintWriter(out);) {

@@ -257,4 +284,123 @@ public class BackupManagerMonitorDumpsysUtils {
            default -> false;
        };
    }

    /**
     * Store the timestamp when the device was set up (date when the first BMM event is parsed)
     * in a text file.
     */
    @VisibleForTesting
    void recordSetUpTimestamp() {
        File setupDateFile = getSetUpDateFile();
        // record setup timestamp only once
        if (setupDateFile.length() == 0) {
            try (FileOutputStream out = new FileOutputStream(setupDateFile, /*append*/ true);
                 PrintWriter pw = new FastPrintWriter(out);) {
                long currentDate = System.currentTimeMillis();
                pw.println(currentDate);
            } catch (IOException e) {
                Slog.w(TAG, "An error occurred while recording the setup date: "
                        + e.getMessage());
            }
        }

    }

    @VisibleForTesting
    String getSetUpDate() {
        File fname = getSetUpDateFile();
        try (FileInputStream inputStream = new FileInputStream(fname);
             InputStreamReader reader = new InputStreamReader(inputStream);
             BufferedReader bufferedReader = new BufferedReader(reader);) {
            return bufferedReader.readLine();
        } catch (Exception e) {
            Slog.w(TAG, "An error occurred while reading the date: " + e.getMessage());
            return "Could not retrieve setup date";
        }
    }

    @VisibleForTesting
    static boolean isDateAfterNMillisec(long startTimeStamp, long endTimeStamp, long millisec) {
        if (startTimeStamp > endTimeStamp) {
            // Something has gone wrong, timeStamp1 should always precede timeStamp2.
            // Out of caution return true: we would delete the logs rather than
            // risking them being kept for longer than the retention period
            return true;
        }
        long timeDifferenceMillis = endTimeStamp - startTimeStamp;
        return (timeDifferenceMillis >= millisec);
    }

    /**
     * Check if current date is after retention period
     */
    @VisibleForTesting
    boolean isAfterRetentionPeriod() {
        if (mIsAfterRetentionPeriodCached) {
            return mIsAfterRetentionPeriod;
        } else {
            File setUpDateFile = getSetUpDateFile();
            if (setUpDateFile.length() == 0) {
                // We are yet to record a setup date. This means we haven't parsed the first event.
                mIsAfterRetentionPeriod = false;
                mIsAfterRetentionPeriodCached = true;
                return false;
            }
            try {
                long setupTimestamp = Long.parseLong(getSetUpDate());
                long currentTimestamp = System.currentTimeMillis();
                mIsAfterRetentionPeriod = isDateAfterNMillisec(setupTimestamp, currentTimestamp,
                        getRetentionPeriodInMillisec());
                mIsAfterRetentionPeriodCached = true;
                return mIsAfterRetentionPeriod;
            } catch (NumberFormatException e) {
                // An error occurred when parsing the setup timestamp.
                // Out of caution return true: we would delete the logs rather than
                // risking them being kept for longer than the retention period
                mIsAfterRetentionPeriod = true;
                mIsAfterRetentionPeriodCached = true;
                return true;
            }
        }
    }

    @VisibleForTesting
    File getSetUpDateFile() {
        File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR);
        File setupDateFile = new File(dataDir, INITIAL_SETUP_TIMESTAMP_KEY + ".txt");
        return setupDateFile;
    }

    @VisibleForTesting
    long getRetentionPeriodInMillisec() {
        return LOGS_RETENTION_PERIOD_MILLISEC;
    }

    /**
     * Delete the BMM Events file after the retention period has passed.
     *
     * @return true if the retention period has passed false otherwise.
     * we want to return true even if we were unable to delete the file, as this will prevent
     * expired BMM events from being printed to the dumpsys
     */
    public boolean deleteExpiredBMMEvents() {
        try {
            if (isAfterRetentionPeriod()) {
                File bmmEvents = getBMMEventsFile();
                if (bmmEvents.exists()) {
                    if (bmmEvents.delete()) {
                        Slog.i(TAG, "Deleted expired BMM Events");
                    } else {
                        Slog.e(TAG, "Unable to delete expired BMM Events");
                    }
                }
                return true;
            }
            return false;
        } catch (Exception e) {
            // Handle any unexpected exceptions
            // To be safe we return true as we want to avoid exposing expired BMMEvents
            return true;
        }
    }
}
+192 −9
Original line number Diff line number Diff line
@@ -17,26 +17,29 @@
package com.android.server.backup.utils;

import static org.junit.Assert.assertTrue;

import static org.testng.AssertJUnit.assertFalse;
import android.app.backup.BackupAnnotations;
import android.app.backup.BackupManagerMonitor;
import android.os.Bundle;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;

import java.io.File;

public class BackupManagerMonitorDumpsysUtilsTest {
    private File mTempFile;
    private long mRetentionPeriod;
    private File mTempBMMEventsFile;
    private File mTempSetUpDateFile;
    private TestBackupManagerMonitorDumpsysUtils mBackupManagerMonitorDumpsysUtils;
    @Rule
    public TemporaryFolder tmp = new TemporaryFolder();

    @Before
    public void setUp() throws Exception {
        mTempFile = tmp.newFile("testbmmevents.txt");
        mRetentionPeriod = 30 * 60 * 1000;
        mTempBMMEventsFile = tmp.newFile("testbmmevents.txt");
        mTempSetUpDateFile = tmp.newFile("testSetUpDate.txt");
        mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils();
    }

@@ -46,7 +49,7 @@ public class BackupManagerMonitorDumpsysUtilsTest {
            throws Exception {
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(null);

        assertTrue(mTempFile.length() == 0);
        assertTrue(mTempBMMEventsFile.length() == 0);

    }

@@ -57,7 +60,7 @@ public class BackupManagerMonitorDumpsysUtilsTest {
        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1);
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);

        assertTrue(mTempFile.length() == 0);
        assertTrue(mTempBMMEventsFile.length() == 0);
    }

    @Test
@@ -67,18 +70,198 @@ public class BackupManagerMonitorDumpsysUtilsTest {
        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1);
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);

        assertTrue(mTempFile.length() == 0);
        assertTrue(mTempBMMEventsFile.length() == 0);
    }

    @Test
    public void parseBackupManagerMonitorEventForDumpsys_eventWithCategoryAndId_eventIsWrittenToFile()
            throws Exception {
        Bundle event = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);

        assertTrue(mTempBMMEventsFile.length() != 0);
    }

    @Test
    public void parseBackupManagerMonitorEventForDumpsys_firstEvent_recordSetUpTimestamp()
            throws Exception {
        assertTrue(mTempBMMEventsFile.length()==0);
        assertTrue(mTempSetUpDateFile.length()==0);

        Bundle event = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);

        assertTrue(mTempBMMEventsFile.length() != 0);
        assertTrue(mTempSetUpDateFile.length()!=0);
    }

    @Test
    public void parseBackupManagerMonitorEventForDumpsys_notFirstEvent_doNotChangeSetUpTimestamp()
            throws Exception {
        Bundle event1 = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event1);
        String setUpTimestampBefore = mBackupManagerMonitorDumpsysUtils.getSetUpDate();

        Bundle event2 = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event2);
        String setUpTimestampAfter = mBackupManagerMonitorDumpsysUtils.getSetUpDate();

        assertTrue(setUpTimestampBefore.equals(setUpTimestampAfter));
    }


    @Test
    public void deleteExpiredBackupManagerMonitorEvent_eventsAreExpired_deleteEventsAndReturnTrue()
            throws Exception {
        Bundle event = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);
        assertTrue(mTempBMMEventsFile.length() != 0);
        // Re-initialise the test BackupManagerMonitorDumpsysUtils to
        // clear the cached value of isAfterRetentionPeriod
        mBackupManagerMonitorDumpsysUtils = new TestBackupManagerMonitorDumpsysUtils();

        // set a retention period of 0 second
        mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(0);

        assertTrue(mBackupManagerMonitorDumpsysUtils.deleteExpiredBMMEvents());
        assertFalse(mTempBMMEventsFile.exists());
    }

    @Test
    public void deleteExpiredBackupManagerMonitorEvent_eventsAreNotExpired_returnFalse() throws
            Exception {
        Bundle event = createRestoreBMMEvent();
        mBackupManagerMonitorDumpsysUtils.parseBackupManagerMonitorRestoreEventForDumpsys(event);
        assertTrue(mTempBMMEventsFile.length() != 0);

        // set a retention period of 30 minutes
        mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(30 * 60 * 1000);

        assertFalse(mBackupManagerMonitorDumpsysUtils.deleteExpiredBMMEvents());
        assertTrue(mTempBMMEventsFile.length() != 0);
    }

    @Test
    public void isAfterRetentionPeriod_afterRetentionPeriod_returnTrue() throws
            Exception {
        mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp();

        // set a retention period of 0 second
        mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(0);

        assertTrue(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod());
    }

    @Test
    public void isAfterRetentionPeriod_beforeRetentionPeriod_returnFalse() throws
            Exception {
        mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp();

        // set a retention period of 30 minutes
        mBackupManagerMonitorDumpsysUtils.setTestRetentionPeriod(30 * 60 * 1000);

        assertFalse(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod());
    }

    @Test
    public void isAfterRetentionPeriod_noSetupDate_returnFalse() throws
            Exception {
        assertTrue(mTempSetUpDateFile.length() == 0);

        assertFalse(mBackupManagerMonitorDumpsysUtils.isAfterRetentionPeriod());
    }

    @Test
    public void isDateAfterNMillisec_date1IsAfterThanDate2_returnTrue() throws
            Exception {
        long timestamp1 = System.currentTimeMillis();
        long timestamp2 = timestamp1 - 1;

        assertTrue(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2,
                0));
    }

    @Test
    public void isDateAfterNMillisec_date1IsAfterNMillisecFromDate2_returnTrue() throws
            Exception {
        long timestamp1 = System.currentTimeMillis();
        long timestamp2 = timestamp1 + 10;

        assertTrue(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2,
                10));
    }

    @Test
    public void isDateAfterNMillisec_date1IsLessThanNMillisecFromDate2_returnFalse() throws
            Exception {
        long timestamp1 = System.currentTimeMillis();
        long timestamp2 = timestamp1 + 10;

        assertFalse(mBackupManagerMonitorDumpsysUtils.isDateAfterNMillisec(timestamp1, timestamp2,
                11));
    }

    @Test
    public void recordSetUpTimestamp_timestampNotSetBefore_setTimestamp() throws
            Exception {
        assertTrue(mTempSetUpDateFile.length() == 0);

        mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp();

        assertTrue(mTempSetUpDateFile.length() != 0);
    }

    @Test
    public void recordSetUpTimestamp_timestampSetBefore_doNothing() throws
            Exception {
        mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp();
        assertTrue(mTempSetUpDateFile.length() != 0);
        String timestampBefore = mBackupManagerMonitorDumpsysUtils.getSetUpDate();

        mBackupManagerMonitorDumpsysUtils.recordSetUpTimestamp();

        assertTrue(mTempSetUpDateFile.length() != 0);
        String timestampAfter = mBackupManagerMonitorDumpsysUtils.getSetUpDate();
        assertTrue(timestampAfter.equals(timestampBefore));
    }

    private Bundle createRestoreBMMEvent() {
        Bundle event = new Bundle();
        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID, 1);
        event.putInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY, 1);
        event.putInt(BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE,
                BackupAnnotations.OperationType.RESTORE);
        return event;
    }

    private class TestBackupManagerMonitorDumpsysUtils
            extends BackupManagerMonitorDumpsysUtils {

        private long testRetentionPeriod;

        TestBackupManagerMonitorDumpsysUtils() {
            super();
            this.testRetentionPeriod = mRetentionPeriod;
        }

        public void setTestRetentionPeriod(long testRetentionPeriod) {
            this.testRetentionPeriod = testRetentionPeriod;
        }

        @Override
        public File getBMMEventsFile() {
            return mTempFile;
            return mTempBMMEventsFile;
        }

        @Override
        File getSetUpDateFile() {
            return mTempSetUpDateFile;
        }

        @Override
        long getRetentionPeriodInMillisec() {
            return testRetentionPeriod;
        }

    }
}