Implement wear history tracking

Bug: 32512551
Test: runtest -x packages/services/Car/tests/carservice_unit_test/src/com/android/car/storagemonitoring/CarStorageMonitoringTest.java
      runtest -x packages/services/Car/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
Change-Id: Id024f9378e0a762cbdfeead9267deeebfa86fd40
diff --git a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
index cd104f8..c45e5e3 100644
--- a/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
+++ b/tests/carservice_test/src/com/android/car/CarStorageMonitoringTest.java
@@ -16,11 +16,25 @@
 
 package com.android.car;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.car.Car;
 import android.car.storagemonitoring.CarStorageMonitoringManager;
 import android.car.storagemonitoring.WearEstimate;
+import android.car.storagemonitoring.WearEstimateChange;
 import android.test.suitebuilder.annotation.MediumTest;
+import android.util.JsonWriter;
+import android.util.Log;
+import com.android.car.storagemonitoring.WearEstimateRecord;
+import com.android.car.storagemonitoring.WearHistory;
 import com.android.car.storagemonitoring.WearInformation;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 
 /** Test the public entry points for the CarStorageMonitoringManager */
 @MediumTest
@@ -28,13 +42,120 @@
     private static final String TAG = CarStorageMonitoringTest.class.getSimpleName();
 
     private static final WearInformation DEFAULT_WEAR_INFORMATION =
-        new WearInformation(10, 0, WearInformation.PRE_EOL_INFO_NORMAL);
+        new WearInformation(30, 0, WearInformation.PRE_EOL_INFO_NORMAL);
+
+    private static final class WearData {
+        static final WearData DEFAULT = new WearData(0, DEFAULT_WEAR_INFORMATION, null);
+
+        final long uptime;
+        @NonNull
+        final WearInformation wearInformation;
+        @Nullable
+        final WearHistory wearHistory;
+
+        WearData(long uptime, @Nullable WearInformation wearInformation, @Nullable WearHistory wearHistory) {
+            if (wearInformation == null) wearInformation = DEFAULT_WEAR_INFORMATION;
+            this.uptime = uptime;
+            this.wearInformation = wearInformation;
+            this.wearHistory = wearHistory;
+        }
+    }
+
+    private static final Map<String, WearData> PER_TEST_WEAR_DATA =
+            new HashMap<String, WearData>() {{
+                put("testReadWearHistory",
+                    new WearData(6500, DEFAULT_WEAR_INFORMATION,
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build())));
+
+                put("testNotAcceptableWearEvent",
+                    new WearData(2520006499L,
+                        new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build())));
+
+                put("testAcceptableWearEvent",
+                    new WearData(2520006501L,
+                        new WearInformation(40, 0, WearInformation.PRE_EOL_INFO_NORMAL),
+                        WearHistory.fromRecords(
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(WearEstimate.UNKNOWN_ESTIMATE)
+                                .toWearEstimate(new WearEstimate(10, 0))
+                                .atUptime(1000)
+                                .atTimestamp(Instant.ofEpochMilli(5000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(10, 0))
+                                .toWearEstimate(new WearEstimate(20, 0))
+                                .atUptime(4000)
+                                .atTimestamp(Instant.ofEpochMilli(12000)).build(),
+                            WearEstimateRecord.Builder.newBuilder()
+                                .fromWearEstimate(new WearEstimate(20, 0))
+                                .toWearEstimate(new WearEstimate(30, 0))
+                                .atUptime(6500)
+                                .atTimestamp(Instant.ofEpochMilli(17000)).build())));
+            }};
 
     private CarStorageMonitoringManager mCarStorageMonitoringManager;
 
     @Override
     protected synchronized void configureFakeSystemInterface() {
-        setFlashWearInformation(DEFAULT_WEAR_INFORMATION);
+        try {
+            final String testName = getName();
+            final WearData wearData = PER_TEST_WEAR_DATA.getOrDefault(testName, WearData.DEFAULT);
+            final WearHistory wearHistory = wearData.wearHistory;
+
+            setFlashWearInformation(wearData.wearInformation);
+            setUptimeProvider( (boolean b) -> 0 );
+
+            if (wearHistory != null) {
+                File wearHistoryFile = new File(getFakeSystemInterface().getFilesDir(),
+                    CarStorageMonitoringService.WEAR_INFO_FILENAME);
+                try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(wearHistoryFile))) {
+                    wearHistory.writeToJson(jsonWriter);
+                }
+            }
+
+            if (wearData.uptime > 0) {
+                File uptimeFile = new File(getFakeSystemInterface().getFilesDir(),
+                        CarStorageMonitoringService.UPTIME_TRACKER_FILENAME);
+                try (JsonWriter jsonWriter = new JsonWriter(new FileWriter(uptimeFile))) {
+                    jsonWriter.beginObject();
+                    jsonWriter.name("uptime").value(wearData.uptime);
+                    jsonWriter.endObject();
+                }
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "failed to configure fake system interface", e);
+            fail("failed to configure fake system interface instance");
+        }
     }
 
     @Override
@@ -57,4 +178,50 @@
         assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateA, wearEstimate.typeA);
         assertEquals(DEFAULT_WEAR_INFORMATION.lifetimeEstimateB, wearEstimate.typeB);
     }
+
+    public void testReadWearHistory() throws Exception {
+        final List<WearEstimateChange> wearEstimateChanges =
+                mCarStorageMonitoringManager.getWearEstimateHistory();
+
+        assertNotNull(wearEstimateChanges);
+        assertFalse(wearEstimateChanges.isEmpty());
+
+        final WearHistory expectedWearHistory = PER_TEST_WEAR_DATA.get(getName()).wearHistory;
+
+        assertEquals(expectedWearHistory.size(), wearEstimateChanges.size());
+        for (int i = 0; i < wearEstimateChanges.size(); ++i) {
+            final WearEstimateRecord expected = expectedWearHistory.get(i);
+            final WearEstimateChange actual = wearEstimateChanges.get(i);
+
+            assertTrue(expected.isSameAs(actual));
+        }
+    }
+
+    private void checkLastWearEvent(boolean isAcceptable) throws Exception {
+        final List<WearEstimateChange> wearEstimateChanges =
+            mCarStorageMonitoringManager.getWearEstimateHistory();
+
+        assertNotNull(wearEstimateChanges);
+        assertFalse(wearEstimateChanges.isEmpty());
+
+        final WearData wearData = PER_TEST_WEAR_DATA.get(getName());
+
+        final WearInformation expectedCurrentWear = wearData.wearInformation;
+        final WearEstimate expectedPreviousWear = wearData.wearHistory.getLast().getNewWearEstimate();
+
+        final WearEstimateChange actualCurrentWear =
+                wearEstimateChanges.get(wearEstimateChanges.size() - 1);
+
+        assertEquals(isAcceptable, actualCurrentWear.isAcceptableDegradation);
+        assertEquals(expectedCurrentWear.toWearEstimate(), actualCurrentWear.newEstimate);
+        assertEquals(expectedPreviousWear, actualCurrentWear.oldEstimate);
+    }
+
+    public void testNotAcceptableWearEvent() throws Exception {
+        checkLastWearEvent(false);
+    }
+
+    public void testAcceptableWearEvent() throws Exception {
+        checkLastWearEvent(true);
+    }
 }