Fix odsign_e2e_tests to accurately get file modified time in ms.

Before this change, we were using "stat -c '%.3Y'" to get file modified
time in ms. However, the Toybox's `stat` implementation truncates to
timestamp to seconds, making the timestamp not accurate enough, and
causes the test to be flaky. Flakes observed when odrefresh compiles
artifacts right after a boot in a test, where the compilation time and
the boot time only differ in milliseconds.

This change fixes the problem by using "stat -c '%y'" and converting the
formatted time into timestamp in Java.

Bug: 193616266
Test: atest odsign_e2e_tests
Change-Id: I36c571e9cafc2b4e99ae20f196a138834d7f57e0
diff --git a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
index bc9712b..02c2787 100644
--- a/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
+++ b/test/odsign/test-src/com/android/tests/odsign/OnDeviceSigningHostTest.java
@@ -37,6 +37,8 @@
 import org.junit.runners.MethodSorters;
 
 import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.Arrays;
 import java.util.HashSet;
 import java.util.Optional;
@@ -89,7 +91,7 @@
             mZygoteArtifacts.addAll(getZygoteLoadedArtifacts(zygoteName).orElse(new HashSet<>()));
         }
         mSystemServerArtifacts = getSystemServerLoadedArtifacts();
-        mBootTimeMs = getDevice().getDeviceDate();
+        mBootTimeMs = getCurrentTimeMs();
     }
 
     @After
@@ -339,24 +341,56 @@
         getDevice().pushString(apexInfo, CACHE_INFO_FILE);
     }
 
-    long getModifiedTimeMs(String filename) throws Exception {
-        String timeStr = getDevice()
-                .executeShellCommand(String.format("stat -c '%%.3Y' '%s'", filename))
-                .trim();
-        return (long)(Double.parseDouble(timeStr) * 1000);
+    long parseFormattedDateTime(String dateTimeStr) throws Exception {
+        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(
+                "yyyy-MM-dd HH:mm:ss.nnnnnnnnn Z");
+        ZonedDateTime zonedDateTime = ZonedDateTime.parse(dateTimeStr, formatter);
+        return zonedDateTime.toInstant().toEpochMilli();
     }
 
-    void assertArtifactsModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+    long getModifiedTimeMs(String filename) throws Exception {
+        // We can't use the "-c '%.3Y'" flag when to get the timestamp because the Toybox's `stat`
+        // implementation truncates the timestamp to seconds, which is not accurate enough, so we
+        // use "-c '%%y'" and parse the time ourselves.
+        String dateTimeStr = getDevice()
+                .executeShellCommand(String.format("stat -c '%%y' '%s'", filename))
+                .trim();
+        return parseFormattedDateTime(dateTimeStr);
+    }
+
+    long getCurrentTimeMs() throws Exception {
+        // We can't use getDevice().getDeviceDate() because it truncates the timestamp to seconds,
+        // which is not accurate enough.
+        String dateTimeStr = getDevice()
+                .executeShellCommand("date +'%Y-%m-%d %H:%M:%S.%N %z'")
+                .trim();
+        return parseFormattedDateTime(dateTimeStr);
+    }
+
+    void assertArtifactsModifiedAfterBoot(Set<String> artifacts) throws Exception {
         for (String artifact : artifacts) {
-            assertTrue("Artifact " + artifact + " is not re-compiled",
-                    getModifiedTimeMs(artifact) > timeMs);
+            long modifiedTime = getModifiedTimeMs(artifact);
+            assertTrue(
+                    String.format(
+                            "Artifact %s is not re-compiled. Modified time: %d, Boot time: %d",
+                            artifact,
+                            modifiedTime,
+                            mBootTimeMs),
+                    modifiedTime > mBootTimeMs);
         }
     }
 
-    void assertArtifactsNotModifiedAfter(Set<String> artifacts, long timeMs) throws Exception {
+    void assertArtifactsNotModifiedAfterBoot(Set<String> artifacts) throws Exception {
         for (String artifact : artifacts) {
-            assertTrue("Artifact " + artifact + " is unexpectedly re-compiled",
-                    getModifiedTimeMs(artifact) < timeMs);
+            long modifiedTime = getModifiedTimeMs(artifact);
+            assertTrue(
+                    String.format(
+                            "Artifact %s is unexpectedly re-compiled. " +
+                                    "Modified time: %d, Boot time: %d",
+                            artifact,
+                            modifiedTime,
+                            mBootTimeMs),
+                    modifiedTime < mBootTimeMs);
         }
     }
 
@@ -366,8 +400,8 @@
         removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command("odrefresh --compile");
 
-        assertArtifactsModifiedAfter(mZygoteArtifacts, mBootTimeMs);
-        assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+        assertArtifactsModifiedAfterBoot(mZygoteArtifacts);
+        assertArtifactsModifiedAfterBoot(mSystemServerArtifacts);
     }
 
     @Test
@@ -376,8 +410,8 @@
         removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command("odrefresh --compile");
 
-        assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
-        assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+        assertArtifactsNotModifiedAfterBoot(mZygoteArtifacts);
+        assertArtifactsModifiedAfterBoot(mSystemServerArtifacts);
     }
 
     @Test
@@ -386,8 +420,8 @@
         removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command("odrefresh --compile");
 
-        assertArtifactsModifiedAfter(mZygoteArtifacts, mBootTimeMs);
-        assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+        assertArtifactsModifiedAfterBoot(mZygoteArtifacts);
+        assertArtifactsModifiedAfterBoot(mSystemServerArtifacts);
     }
 
     @Test
@@ -396,8 +430,8 @@
         removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command("odrefresh --compile");
 
-        assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
-        assertArtifactsModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+        assertArtifactsNotModifiedAfterBoot(mZygoteArtifacts);
+        assertArtifactsModifiedAfterBoot(mSystemServerArtifacts);
     }
 
     @Test
@@ -405,8 +439,8 @@
         removeCompilationLogToAvoidBackoff();
         getDevice().executeShellV2Command("odrefresh --compile");
 
-        assertArtifactsNotModifiedAfter(mZygoteArtifacts, mBootTimeMs);
-        assertArtifactsNotModifiedAfter(mSystemServerArtifacts, mBootTimeMs);
+        assertArtifactsNotModifiedAfterBoot(mZygoteArtifacts);
+        assertArtifactsNotModifiedAfterBoot(mSystemServerArtifacts);
     }
 
     private boolean haveCompilationLog() throws Exception {