1 /*
2  * Copyright (C) 2023 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.backup.utils;
18 
19 import android.app.backup.BackupAnnotations;
20 import android.app.backup.BackupManagerMonitor;
21 import android.app.backup.BackupRestoreEventLogger;
22 import android.os.Bundle;
23 import android.os.Environment;
24 import android.util.Slog;
25 
26 import com.android.internal.annotations.VisibleForTesting;
27 import com.android.internal.util.FastPrintWriter;
28 
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.FileWriter;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.io.PrintWriter;
37 import java.text.SimpleDateFormat;
38 import java.util.ArrayList;
39 import java.util.Date;
40 import java.util.Map;
41 import java.util.concurrent.TimeUnit;
42 
43 
44 /*
45  * Util class to parse a BMM event and write it to a text file, to be the printed in
46  * the backup dumpsys
47  *
48  * Note: this class is note thread safe
49  */
50 public class BackupManagerMonitorDumpsysUtils {
51 
52     private static final String TAG = "BackupManagerMonitorDumpsysUtils";
53     // Name of the subdirectory where the text file containing the BMM events will be stored.
54     // Same as {@link UserBackupManagerFiles}
55     private static final String BACKUP_PERSISTENT_DIR = "backup";
56     private static final String INITIAL_SETUP_TIMESTAMP_KEY = "initialSetupTimestamp";
57     // Retention period of 60 days (in millisec) for the BMM Events.
58     // After tha time has passed the text file containing the BMM events will be emptied
59     private static final long LOGS_RETENTION_PERIOD_MILLISEC = 60 * TimeUnit.DAYS.toMillis(1);
60     // Size limit for the text file containing the BMM events
61     private static final long BMM_FILE_SIZE_LIMIT_BYTES = 25 * 1024 * 1000; // 2.5 MB
62 
63     // We cache the value of IsAfterRetentionPeriod() to avoid unnecessary disk I/O
64     // mIsAfterRetentionPeriodCached tracks if we have cached the value of IsAfterRetentionPeriod()
65     private boolean mIsAfterRetentionPeriodCached = false;
66     // The cached value of IsAfterRetentionPeriod()
67     private boolean mIsAfterRetentionPeriod;
68     // If isFileLargerThanSizeLimit(bmmEvents)  returns true we cache the value to avoid
69     // unnecessary disk I/O
70    private boolean mIsFileLargerThanSizeLimit = false;
71 
72     /**
73      * Parses the BackupManagerMonitor bundle for a RESTORE event in a series of strings that
74      * will be persisted in a text file and printed in the dumpsys.
75      *
76      * If the eventBundle passed is not a RESTORE event, return early
77      *
78      * Key information related to the event:
79      * - Timestamp (HAS TO ALWAYS BE THE FIRST LINE OF EACH EVENT)
80      * - Event ID
81      * - Event Category
82      * - Operation type
83      * - Package name (can be null)
84      * - Agent logs (if available)
85      *
86      * Example of formatting:
87      * [2023-09-21 14:43:33.824] - Agent logging results
88      *   Package: com.android.wallpaperbackup
89      *   Agent Logs:
90      *           Data Type: wlp_img_system
91      *                   Item restored: 0/1
92      *                   Agent Error - Category: no_wallpaper, Count: 1
93      *           Data Type: wlp_img_lock
94      *                   Item restored: 0/1
95      *                   Agent Error - Category: no_wallpaper, Count: 1
96      */
parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle)97     public void parseBackupManagerMonitorRestoreEventForDumpsys(Bundle eventBundle) {
98         if (isAfterRetentionPeriod()) {
99             // We only log data for the first 60 days since setup
100             return;
101         }
102 
103         if (eventBundle == null) {
104             return;
105         }
106 
107         if (!isOpTypeRestore(eventBundle)) {
108             //We only log Restore events
109             return;
110         }
111 
112         if (!eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
113                 || !eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY)) {
114             Slog.w(TAG, "Event id and category are not optional fields.");
115             return;
116         }
117         File bmmEvents = getBMMEventsFile();
118 
119         if (bmmEvents.length() == 0) {
120             // We are parsing the first restore event.
121             // Time to also record the setup timestamp of the device
122             recordSetUpTimestamp();
123         }
124 
125         if(isFileLargerThanSizeLimit(bmmEvents)){
126             // Do not write more events if the file is over size limit
127             return;
128         }
129 
130         try (FileOutputStream out = new FileOutputStream(bmmEvents, /*append*/ true);
131              PrintWriter pw = new FastPrintWriter(out);) {
132 
133             int eventCategory = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_CATEGORY);
134             int eventId = eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID);
135 
136             if (eventId == BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS &&
137                     !hasAgentLogging(eventBundle)) {
138                 // Do not record an empty agent logging event
139                 return;
140             }
141 
142             pw.println("[" + timestamp() + "] - " + getId(eventId));
143 
144             if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME)) {
145                 pw.println("\tPackage: "
146                         + eventBundle.getString(BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_NAME));
147             }
148 
149             addAgentLogsIfAvailable(eventBundle, pw);
150             addExtrasIfAvailable(eventBundle, pw);
151         } catch (java.io.IOException e) {
152             Slog.e(TAG, "IO Exception when writing BMM events to file: " + e);
153         }
154 
155     }
156 
hasAgentLogging(Bundle eventBundle)157     private boolean hasAgentLogging(Bundle eventBundle) {
158         if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS)) {
159             ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs =
160                     eventBundle.getParcelableArrayList(
161                             BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
162 
163             return !agentLogs.isEmpty();
164         }
165         return false;
166     }
167 
168     /**
169      * Extracts agent logs from the BackupManagerMonitor event. These logs detail:
170      * - the data type for the agent
171      * - the count of successfully restored items
172      * - the count of items that failed to restore
173      * - the metadata associated with this datatype
174      * - any errors
175      */
addAgentLogsIfAvailable(Bundle eventBundle, PrintWriter pw)176     private void addAgentLogsIfAvailable(Bundle eventBundle, PrintWriter pw) {
177         if (hasAgentLogging(eventBundle)) {
178             pw.println("\tAgent Logs:");
179             ArrayList<BackupRestoreEventLogger.DataTypeResult> agentLogs =
180                     eventBundle.getParcelableArrayList(
181                             BackupManagerMonitor.EXTRA_LOG_AGENT_LOGGING_RESULTS);
182             for (BackupRestoreEventLogger.DataTypeResult result : agentLogs) {
183                 int totalItems = result.getFailCount() + result.getSuccessCount();
184                 pw.println("\t\tData Type: " + result.getDataType());
185                 pw.println("\t\t\tItem restored: " + result.getSuccessCount() + "/" +
186                         totalItems);
187                 for (Map.Entry<String, Integer> entry : result.getErrors().entrySet()) {
188                     pw.println("\t\t\tAgent Error - Category: " +
189                             entry.getKey() + ", Count: " + entry.getValue());
190                 }
191             }
192         }
193     }
194 
195     /**
196      * Extracts some extras (defined in BackupManagerMonitor as EXTRA_LOG_<description>)
197      * from the BackupManagerMonitor event. Not all extras have the same importance. For now only
198      * focus on extras relating to version mismatches between packages on the source and target.
199      *
200      * When an event with ID LOG_EVENT_ID_RESTORE_VERSION_HIGHER (trying to restore from higher to
201      * lower version of a package) parse:
202      * EXTRA_LOG_RESTORE_VERSION [int]: the version of the package on the source
203      * EXTRA_LOG_RESTORE_ANYWAY [bool]: if the package allows restore any version
204      * EXTRA_LOG_RESTORE_VERSION_TARGET [int]: an extra to record the package version on the target
205      *
206      * When we are performing a V to U downgrade (event with id V_TO_U_RESTORE_SET_LIST) we record
207      * the value of the V to U allowlist and denylist:
208      * EXTRA_LOG_V_TO_U_ALLOWLIST[string]
209      * EXTRA_LOG_V_TO_U_DENYLIST[string]
210      */
addExtrasIfAvailable(Bundle eventBundle, PrintWriter pw)211     private void addExtrasIfAvailable(Bundle eventBundle, PrintWriter pw) {
212         if (eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID) ==
213                 BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER) {
214             if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY)) {
215                 pw.println("\t\tPackage supports RestoreAnyVersion: "
216                         + eventBundle.getBoolean(BackupManagerMonitor.EXTRA_LOG_RESTORE_ANYWAY));
217             }
218             if (eventBundle.containsKey(BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION)) {
219                 pw.println("\t\tPackage version on source: "
220                         + eventBundle.getLong(BackupManagerMonitor.EXTRA_LOG_RESTORE_VERSION));
221             }
222             if (eventBundle.containsKey(
223                     BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION)) {
224                 pw.println("\t\tPackage version on target: "
225                         + eventBundle.getLong(
226                         BackupManagerMonitor.EXTRA_LOG_EVENT_PACKAGE_LONG_VERSION));
227             }
228         }
229 
230         if (eventBundle.getInt(BackupManagerMonitor.EXTRA_LOG_EVENT_ID)
231                 == BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST) {
232             if (eventBundle.containsKey(
233                     BackupManagerMonitor.EXTRA_LOG_V_TO_U_DENYLIST)) {
234                 pw.println("\t\tV to U Denylist : "
235                         + eventBundle.getString(
236                         BackupManagerMonitor.EXTRA_LOG_V_TO_U_DENYLIST));
237             }
238 
239             if (eventBundle.containsKey(
240                     BackupManagerMonitor.EXTRA_LOG_V_TO_U_ALLOWLIST)) {
241                 pw.println("\t\tV to U Allowllist : "
242                         + eventBundle.getString(
243                         BackupManagerMonitor.EXTRA_LOG_V_TO_U_ALLOWLIST));
244             }
245         }
246     }
247 
248     /*
249      * Get the path of the text files which stores the BMM events
250      */
getBMMEventsFile()251     public File getBMMEventsFile() {
252         File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR);
253         File fname = new File(dataDir, "bmmevents.txt");
254         return fname;
255     }
256 
isFileLargerThanSizeLimit(File events)257     public boolean isFileLargerThanSizeLimit(File events){
258         if (!mIsFileLargerThanSizeLimit) {
259             mIsFileLargerThanSizeLimit = events.length() > getBMMEventsFileSizeLimit();
260         }
261         return mIsFileLargerThanSizeLimit;
262     }
263 
timestamp()264     private String timestamp() {
265         long currentTime = System.currentTimeMillis();
266         Date date = new Date(currentTime);
267         SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
268         return dateFormat.format(date);
269     }
270 
getCategory(int code)271     private String getCategory(int code) {
272         String category = switch (code) {
273             case BackupManagerMonitor.LOG_EVENT_CATEGORY_TRANSPORT -> "Transport";
274             case BackupManagerMonitor.LOG_EVENT_CATEGORY_AGENT -> "Agent";
275             case BackupManagerMonitor.LOG_EVENT_CATEGORY_BACKUP_MANAGER_POLICY ->
276                     "Backup Manager Policy";
277             default -> "Unknown category code: " + code;
278         };
279         return category;
280     }
281 
getId(int code)282     private String getId(int code) {
283         String id = switch (code) {
284             case BackupManagerMonitor.LOG_EVENT_ID_FULL_BACKUP_CANCEL -> "Full backup cancel";
285             case BackupManagerMonitor.LOG_EVENT_ID_ILLEGAL_KEY -> "Illegal key";
286             case BackupManagerMonitor.LOG_EVENT_ID_NO_DATA_TO_SEND -> "No data to send";
287             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_INELIGIBLE -> "Package ineligible";
288             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_KEY_VALUE_PARTICIPANT ->
289                     "Package key-value participant";
290             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_STOPPED -> "Package stopped";
291             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_FOUND -> "Package not found";
292             case BackupManagerMonitor.LOG_EVENT_ID_BACKUP_DISABLED -> "Backup disabled";
293             case BackupManagerMonitor.LOG_EVENT_ID_DEVICE_NOT_PROVISIONED ->
294                     "Device not provisioned";
295             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_TRANSPORT_NOT_PRESENT ->
296                     "Package transport not present";
297             case BackupManagerMonitor.LOG_EVENT_ID_ERROR_PREFLIGHT -> "Error preflight";
298             case BackupManagerMonitor.LOG_EVENT_ID_QUOTA_HIT_PREFLIGHT -> "Quota hit preflight";
299             case BackupManagerMonitor.LOG_EVENT_ID_EXCEPTION_FULL_BACKUP -> "Exception full backup";
300             case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_BACKUP_CANCEL ->
301                     "Key-value backup cancel";
302             case BackupManagerMonitor.LOG_EVENT_ID_NO_RESTORE_METADATA_AVAILABLE ->
303                     "No restore metadata available";
304             case BackupManagerMonitor.LOG_EVENT_ID_NO_PM_METADATA_RECEIVED ->
305                     "No PM metadata received";
306             case BackupManagerMonitor.LOG_EVENT_ID_PM_AGENT_HAS_NO_METADATA ->
307                     "PM agent has no metadata";
308             case BackupManagerMonitor.LOG_EVENT_ID_LOST_TRANSPORT -> "Lost transport";
309             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_NOT_PRESENT -> "Package not present";
310             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_VERSION_HIGHER ->
311                     "Restore version higher";
312             case BackupManagerMonitor.LOG_EVENT_ID_APP_HAS_NO_AGENT -> "App has no agent";
313             case BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH -> "Signature mismatch";
314             case BackupManagerMonitor.LOG_EVENT_ID_CANT_FIND_AGENT -> "Can't find agent";
315             case BackupManagerMonitor.LOG_EVENT_ID_KEY_VALUE_RESTORE_TIMEOUT ->
316                     "Key-value restore timeout";
317             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_ANY_VERSION -> "Restore any version";
318             case BackupManagerMonitor.LOG_EVENT_ID_VERSIONS_MATCH -> "Versions match";
319             case BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BACKUP_OLDER ->
320                     "Version of backup older";
321             case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_SIGNATURE_MISMATCH ->
322                     "Full restore signature mismatch";
323             case BackupManagerMonitor.LOG_EVENT_ID_SYSTEM_APP_NO_AGENT -> "System app no agent";
324             case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_ALLOW_BACKUP_FALSE ->
325                     "Full restore allow backup false";
326             case BackupManagerMonitor.LOG_EVENT_ID_APK_NOT_INSTALLED -> "APK not installed";
327             case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_RESTORE_WITHOUT_APK ->
328                     "Cannot restore without APK";
329             case BackupManagerMonitor.LOG_EVENT_ID_MISSING_SIGNATURE -> "Missing signature";
330             case BackupManagerMonitor.LOG_EVENT_ID_EXPECTED_DIFFERENT_PACKAGE ->
331                     "Expected different package";
332             case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_VERSION -> "Unknown version";
333             case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE_TIMEOUT -> "Full restore timeout";
334             case BackupManagerMonitor.LOG_EVENT_ID_CORRUPT_MANIFEST -> "Corrupt manifest";
335             case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_METADATA_MISMATCH ->
336                     "Widget metadata mismatch";
337             case BackupManagerMonitor.LOG_EVENT_ID_WIDGET_UNKNOWN_VERSION ->
338                     "Widget unknown version";
339             case BackupManagerMonitor.LOG_EVENT_ID_NO_PACKAGES -> "No packages";
340             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_IS_NULL -> "Transport is null";
341             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_NON_INCREMENTAL_BACKUP_REQUIRED ->
342                     "Transport non-incremental backup required";
343             case BackupManagerMonitor.LOG_EVENT_ID_AGENT_LOGGING_RESULTS -> "Agent logging results";
344             case BackupManagerMonitor.LOG_EVENT_ID_START_SYSTEM_RESTORE -> "Start system restore";
345             case BackupManagerMonitor.LOG_EVENT_ID_START_RESTORE_AT_INSTALL ->
346                     "Start restore at install";
347             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_DURING_START_RESTORE ->
348                     "Transport error during start restore";
349             case BackupManagerMonitor.LOG_EVENT_ID_CANNOT_GET_NEXT_PKG_NAME ->
350                     "Cannot get next package name";
351             case BackupManagerMonitor.LOG_EVENT_ID_UNKNOWN_RESTORE_TYPE -> "Unknown restore type";
352             case BackupManagerMonitor.LOG_EVENT_ID_KV_RESTORE -> "KV restore";
353             case BackupManagerMonitor.LOG_EVENT_ID_FULL_RESTORE -> "Full restore";
354             case BackupManagerMonitor.LOG_EVENT_ID_NO_NEXT_RESTORE_TARGET ->
355                     "No next restore target";
356             case BackupManagerMonitor.LOG_EVENT_ID_KV_AGENT_ERROR -> "KV agent error";
357             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_RESTORE_FINISHED ->
358                     "Package restore finished";
359             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_KV_RESTORE ->
360                     "Transport error KV restore";
361             case BackupManagerMonitor.LOG_EVENT_ID_NO_FEEDER_THREAD -> "No feeder thread";
362             case BackupManagerMonitor.LOG_EVENT_ID_FULL_AGENT_ERROR -> "Full agent error";
363             case BackupManagerMonitor.LOG_EVENT_ID_TRANSPORT_ERROR_FULL_RESTORE ->
364                     "Transport error full restore";
365             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_COMPLETE -> "Restore complete";
366             case BackupManagerMonitor.LOG_EVENT_ID_START_PACKAGE_RESTORE -> "Start package restore";
367             case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE -> "Agent failure";
368             case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_ELIGIBLE ->
369                     "V to U restore pkg eligible";
370             case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_PKG_NOT_ELIGIBLE ->
371                     "V to U restore pkg not eligible";
372             case BackupManagerMonitor.LOG_EVENT_ID_V_TO_U_RESTORE_SET_LIST ->
373                     "V to U restore lists";
374             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_AT_INSTALL_INVOKED ->
375                     "Invoked restore at install";
376             case BackupManagerMonitor.LOG_EVENT_ID_SKIP_RESTORE_AT_INSTALL ->
377                     "Skip restore at install";
378             case BackupManagerMonitor.LOG_EVENT_ID_PACKAGE_ACCEPTED_FOR_RESTORE ->
379                     "Pkg accepted for restore";
380             case BackupManagerMonitor.LOG_EVENT_ID_RESTORE_DATA_DOES_NOT_BELONG_TO_PACKAGE ->
381                     "Restore data does not belong to package";
382             case BackupManagerMonitor.LOG_EVENT_ID_UNABLE_TO_CREATE_AGENT_FOR_RESTORE ->
383                     "Unable to create Agent";
384             case BackupManagerMonitor.LOG_EVENT_ID_AGENT_CRASHED_BEFORE_RESTORE_DATA_IS_SENT ->
385                     "Agent crashed before restore data is streamed";
386             case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_SEND_DATA_TO_AGENT_DURING_RESTORE ->
387                     "Failed to send data to agent";
388             case BackupManagerMonitor.LOG_EVENT_ID_AGENT_FAILURE_DURING_RESTORE ->
389                     "Agent failure during restore";
390             case BackupManagerMonitor.LOG_EVENT_ID_FAILED_TO_READ_DATA_FROM_TRANSPORT ->
391                     "Failed to read data from Transport";
392             default -> "Unknown log event ID: " + code;
393         };
394         return id;
395     }
396 
isOpTypeRestore(Bundle eventBundle)397     private boolean isOpTypeRestore(Bundle eventBundle) {
398         return switch (eventBundle.getInt(
399                 BackupManagerMonitor.EXTRA_LOG_OPERATION_TYPE, -1)) {
400             case BackupAnnotations.OperationType.RESTORE -> true;
401             default -> false;
402         };
403     }
404 
405     /**
406      * Store the timestamp when the device was set up (date when the first BMM event is parsed)
407      * in a text file.
408      */
409     @VisibleForTesting
recordSetUpTimestamp()410     void recordSetUpTimestamp() {
411         File setupDateFile = getSetUpDateFile();
412         // record setup timestamp only once
413         if (setupDateFile.length() == 0) {
414             try (FileOutputStream out = new FileOutputStream(setupDateFile, /*append*/ true);
415                  PrintWriter pw = new FastPrintWriter(out);) {
416                 long currentDate = System.currentTimeMillis();
417                 pw.println(currentDate);
418             } catch (IOException e) {
419                 Slog.w(TAG, "An error occurred while recording the setup date: "
420                         + e.getMessage());
421             }
422         }
423 
424     }
425 
426     @VisibleForTesting
getSetUpDate()427     String getSetUpDate() {
428         File fname = getSetUpDateFile();
429         try (FileInputStream inputStream = new FileInputStream(fname);
430              InputStreamReader reader = new InputStreamReader(inputStream);
431              BufferedReader bufferedReader = new BufferedReader(reader);) {
432             return bufferedReader.readLine();
433         } catch (Exception e) {
434             Slog.w(TAG, "An error occurred while reading the date: " + e.getMessage());
435             return "Could not retrieve setup date";
436         }
437     }
438 
439     @VisibleForTesting
isDateAfterNMillisec(long startTimeStamp, long endTimeStamp, long millisec)440     static boolean isDateAfterNMillisec(long startTimeStamp, long endTimeStamp, long millisec) {
441         if (startTimeStamp > endTimeStamp) {
442             // Something has gone wrong, timeStamp1 should always precede timeStamp2.
443             // Out of caution return true: we would delete the logs rather than
444             // risking them being kept for longer than the retention period
445             return true;
446         }
447         long timeDifferenceMillis = endTimeStamp - startTimeStamp;
448         return (timeDifferenceMillis >= millisec);
449     }
450 
451     /**
452      * Check if current date is after retention period
453      */
454     @VisibleForTesting
isAfterRetentionPeriod()455     boolean isAfterRetentionPeriod() {
456         if (mIsAfterRetentionPeriodCached) {
457             return mIsAfterRetentionPeriod;
458         } else {
459             File setUpDateFile = getSetUpDateFile();
460             if (setUpDateFile.length() == 0) {
461                 // We are yet to record a setup date. This means we haven't parsed the first event.
462                 mIsAfterRetentionPeriod = false;
463                 mIsAfterRetentionPeriodCached = true;
464                 return false;
465             }
466             try {
467                 long setupTimestamp = Long.parseLong(getSetUpDate());
468                 long currentTimestamp = System.currentTimeMillis();
469                 mIsAfterRetentionPeriod = isDateAfterNMillisec(setupTimestamp, currentTimestamp,
470                         getRetentionPeriodInMillisec());
471                 mIsAfterRetentionPeriodCached = true;
472                 return mIsAfterRetentionPeriod;
473             } catch (NumberFormatException e) {
474                 // An error occurred when parsing the setup timestamp.
475                 // Out of caution return true: we would delete the logs rather than
476                 // risking them being kept for longer than the retention period
477                 mIsAfterRetentionPeriod = true;
478                 mIsAfterRetentionPeriodCached = true;
479                 return true;
480             }
481         }
482     }
483 
484     @VisibleForTesting
getSetUpDateFile()485     File getSetUpDateFile() {
486         File dataDir = new File(Environment.getDataDirectory(), BACKUP_PERSISTENT_DIR);
487         File setupDateFile = new File(dataDir, INITIAL_SETUP_TIMESTAMP_KEY + ".txt");
488         return setupDateFile;
489     }
490 
491     @VisibleForTesting
getRetentionPeriodInMillisec()492     long getRetentionPeriodInMillisec() {
493         return LOGS_RETENTION_PERIOD_MILLISEC;
494     }
495 
496     @VisibleForTesting
getBMMEventsFileSizeLimit()497     long getBMMEventsFileSizeLimit(){
498         return BMM_FILE_SIZE_LIMIT_BYTES;
499     }
500 
501     /**
502      * Delete the BMM Events file after the retention period has passed.
503      *
504      * @return true if the retention period has passed false otherwise.
505      * we want to return true even if we were unable to delete the file, as this will prevent
506      * expired BMM events from being printed to the dumpsys
507      */
deleteExpiredBMMEvents()508     public boolean deleteExpiredBMMEvents() {
509         try {
510             if (isAfterRetentionPeriod()) {
511                 File bmmEvents = getBMMEventsFile();
512                 if (bmmEvents.exists()) {
513                     if (bmmEvents.delete()) {
514                         Slog.i(TAG, "Deleted expired BMM Events");
515                     } else {
516                         Slog.e(TAG, "Unable to delete expired BMM Events");
517                     }
518                 }
519                 return true;
520             }
521             return false;
522         } catch (Exception e) {
523             // Handle any unexpected exceptions
524             // To be safe we return true as we want to avoid exposing expired BMMEvents
525             return true;
526         }
527     }
528 }
529