CSD: fix issue with timestamp after reboot

The CSD record timestamps use CLOCK_MONOTONIC which is counting from
boot time. In order to have a consistent timestamp between reboots we
need to convert between global system time to boot time.

Test: audio and media.audio_flinger dumpsys after reboot
Bug: 266915998
Change-Id: I49111f002d95c27c039e1bd719c39ab5ae5e29d3
diff --git a/services/core/java/com/android/server/audio/SoundDoseHelper.java b/services/core/java/com/android/server/audio/SoundDoseHelper.java
index 748c4ce5..4e8e704 100644
--- a/services/core/java/com/android/server/audio/SoundDoseHelper.java
+++ b/services/core/java/com/android/server/audio/SoundDoseHelper.java
@@ -108,6 +108,8 @@
     private static final String PERSIST_CSD_RECORD_SEPARATOR_CHAR = "|";
     private static final String PERSIST_CSD_RECORD_SEPARATOR = "\\|";
 
+    private static final long GLOBAL_TIME_OFFSET_UNINITIALIZED = -1;
+
     private final EventLogger mLogger = new EventLogger(AudioService.LOG_NB_EVENTS_SOUND_DOSE,
             "CSD updates");
 
@@ -168,6 +170,11 @@
     @GuardedBy("mCsdStateLock")
     private final List<SoundDoseRecord> mDoseRecords = new ArrayList<>();
 
+    // time in seconds reported by System.currentTimeInMillis used as an offset to convert between
+    // boot time and global time
+    @GuardedBy("mCsdStateLock")
+    private long mGlobalTimeOffsetInSecs = GLOBAL_TIME_OFFSET_UNINITIALIZED;
+
     private final Context mContext;
 
     private final ISoundDoseCallback.Stub mSoundDoseCallback = new ISoundDoseCallback.Stub() {
@@ -590,16 +597,24 @@
             Log.v(TAG, "Initializing sound dose");
 
             synchronized (mCsdStateLock) {
+                if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
+                    mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
+                }
+
+                float prevCsd = mCurrentCsd;
                 // Restore persisted values
                 mCurrentCsd = parseGlobalSettingFloat(
                         Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE, /* defaultValue= */0.f);
-                mNextCsdWarning = parseGlobalSettingFloat(
-                        Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
-                final List<SoundDoseRecord> records = persistedStringToRecordList(
-                        mSettings.getGlobalString(mAudioService.getContentResolver(),
-                                Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS));
-                if (records != null) {
-                    mDoseRecords.addAll(records);
+                if (mCurrentCsd != prevCsd) {
+                    mNextCsdWarning = parseGlobalSettingFloat(
+                            Settings.Global.AUDIO_SAFE_CSD_NEXT_WARNING, /* defaultValue= */1.f);
+                    final List<SoundDoseRecord> records = persistedStringToRecordList(
+                            mSettings.getGlobalString(mAudioService.getContentResolver(),
+                                    Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS),
+                            mGlobalTimeOffsetInSecs);
+                    if (records != null) {
+                        mDoseRecords.addAll(records);
+                    }
                 }
             }
 
@@ -774,8 +789,13 @@
         mLogger.enqueue(SoundDoseEvent.getDoseUpdateEvent(currentCsd, totalDuration));
     }
 
+    @SuppressWarnings("GuardedBy")  // avoid limitation with intra-procedural analysis of lambdas
     private void onPersistSoundDoseRecords() {
         synchronized (mCsdStateLock) {
+            if (mGlobalTimeOffsetInSecs == GLOBAL_TIME_OFFSET_UNINITIALIZED) {
+                mGlobalTimeOffsetInSecs = System.currentTimeMillis() / 1000L;
+            }
+
             mSettings.putGlobalString(mAudioService.getContentResolver(),
                     Settings.Global.AUDIO_SAFE_CSD_CURRENT_VALUE,
                     Float.toString(mCurrentCsd));
@@ -785,28 +805,41 @@
             mSettings.putGlobalString(mAudioService.getContentResolver(),
                     Settings.Global.AUDIO_SAFE_CSD_DOSE_RECORDS,
                     mDoseRecords.stream().map(
-                            SoundDoseHelper::recordToPersistedString).collect(
+                            record -> SoundDoseHelper.recordToPersistedString(record,
+                                    mGlobalTimeOffsetInSecs)).collect(
                             Collectors.joining(PERSIST_CSD_RECORD_SEPARATOR_CHAR)));
         }
     }
 
-    private static String recordToPersistedString(SoundDoseRecord record) {
-        return record.timestamp
+    private static String recordToPersistedString(SoundDoseRecord record,
+            long globalTimeOffsetInSecs) {
+        return convertToGlobalTime(record.timestamp, globalTimeOffsetInSecs)
                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.duration
                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.value
                 + PERSIST_CSD_RECORD_FIELD_SEPARATOR + record.averageMel;
     }
 
-    private static List<SoundDoseRecord> persistedStringToRecordList(String records) {
+    private static long convertToGlobalTime(long bootTimeInSecs, long globalTimeOffsetInSecs) {
+        return bootTimeInSecs + globalTimeOffsetInSecs;
+    }
+
+    private static long convertToBootTime(long globalTimeInSecs, long globalTimeOffsetInSecs) {
+        return globalTimeInSecs - globalTimeOffsetInSecs;
+    }
+
+    private static List<SoundDoseRecord> persistedStringToRecordList(String records,
+            long globalTimeOffsetInSecs) {
         if (records == null || records.isEmpty()) {
             return null;
         }
         return Arrays.stream(TextUtils.split(records, PERSIST_CSD_RECORD_SEPARATOR)).map(
-                SoundDoseHelper::persistedStringToRecord).filter(Objects::nonNull).collect(
+                record -> SoundDoseHelper.persistedStringToRecord(record,
+                        globalTimeOffsetInSecs)).filter(Objects::nonNull).collect(
                 Collectors.toList());
     }
 
-    private static SoundDoseRecord persistedStringToRecord(String record) {
+    private static SoundDoseRecord persistedStringToRecord(String record,
+            long globalTimeOffsetInSecs) {
         if (record == null || record.isEmpty()) {
             return null;
         }
@@ -818,7 +851,8 @@
 
         final SoundDoseRecord sdRecord = new SoundDoseRecord();
         try {
-            sdRecord.timestamp = Long.parseLong(fields[0]);
+            sdRecord.timestamp = convertToBootTime(Long.parseLong(fields[0]),
+                    globalTimeOffsetInSecs);
             sdRecord.duration = Integer.parseInt(fields[1]);
             sdRecord.value = Float.parseFloat(fields[2]);
             sdRecord.averageMel = Float.parseFloat(fields[3]);