diff options
3 files changed, 364 insertions, 19 deletions
diff --git a/services/backup/java/com/android/server/backup/UserBackupManagerService.java b/services/backup/java/com/android/server/backup/UserBackupManagerService.java index 4c137bce4f7b..2d80af92eea5 100644 --- a/services/backup/java/com/android/server/backup/UserBackupManagerService.java +++ b/services/backup/java/com/android/server/backup/UserBackupManagerService.java @@ -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; diff --git a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java index 0b55ca21371b..bc2326d8241d 100644 --- a/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java +++ b/services/backup/java/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtils.java @@ -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) @@ -63,16 +79,21 @@ public class BackupManagerMonitorDumpsysUtils { * * Example of formatting: * RESTORE Event: [2023-08-18 17:16:00.735] Agent - Agent logging results - * Package name: com.android.wallpaperbackup - * Agent Logs: - * Data Type: wlp_img_system - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 - * Data Type: wlp_img_lock - * Item restored: 0/1 - * Agent Error - Category: no_wallpaper, Count: 1 + * Package name: com.android.wallpaperbackup + * Agent Logs: + * Data Type: wlp_img_system + * Item restored: 0/1 + * Agent Error - Category: no_wallpaper, Count: 1 + * Data Type: wlp_img_lock + * Item restored: 0/1 + * 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,8 +110,14 @@ 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);) { + PrintWriter pw = new FastPrintWriter(out);) { int eventCategory = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY); int eventId = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID); @@ -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; + } + } } diff --git a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java index 8e17b3a58769..a45b17e2e3c5 100644 --- a/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java +++ b/services/tests/mockingservicestests/src/com/android/server/backup/utils/BackupManagerMonitorDumpsysUtilsTest.java @@ -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; + } + } } |