Add last_update_seconds.

Bug: 211458160
Change-Id: I45eed42c0126cfde3b1b632c3ed90af57bdde455
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 5ae2158..9e60b38 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -38,6 +38,11 @@
   // When specified, apex payload should be verified with the public key and root digest.
   bytes public_key = 3;
   bytes root_digest = 4;
+
+  // Required.
+  // The timestamp in seconds when the APEX was last updated. This should match the value in
+  // apex-info-list.xml.
+  uint64 last_update_seconds = 5;
 }
 
 message ApkPayload {
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index aadb71f..cb59e3b 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -332,4 +332,5 @@
     pub name: String,
     pub public_key: Vec<u8>,
     pub root_digest: Vec<u8>,
+    pub last_update_seconds: u64,
 }
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
index b731d33..661af5f 100644
--- a/microdroid_manager/src/payload.rs
+++ b/microdroid_manager/src/payload.rs
@@ -43,7 +43,12 @@
             let name = apex.name.clone();
             let apex_path = format!("/dev/block/by-name/{}", apex.partition_name);
             let result = verify(&apex_path)?;
-            Ok(ApexData { name, public_key: result.public_key, root_digest: result.root_digest })
+            Ok(ApexData {
+                name,
+                public_key: result.public_key,
+                root_digest: result.root_digest,
+                last_update_seconds: apex.last_update_seconds,
+            })
         })
         .collect()
 }
@@ -57,6 +62,7 @@
                 name: data.name.clone(),
                 public_key: data.public_key.clone(),
                 root_digest: data.root_digest.clone(),
+                last_update_seconds: data.last_update_seconds,
                 ..Default::default()
             })
             .collect(),
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 84d3b2f..65adc25 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -31,9 +31,10 @@
 use serde::Deserialize;
 use serde_xml_rs::from_reader;
 use std::collections::HashSet;
-use std::fs::{File, OpenOptions};
+use std::fs::{metadata, File, OpenOptions};
 use std::path::{Path, PathBuf};
 use std::process::Command;
+use std::time::SystemTime;
 use vmconfig::open_parcel_file;
 
 /// The list of APEXes which microdroid requires.
@@ -61,6 +62,10 @@
 
     #[serde(default)]
     has_classpath_jar: bool,
+
+    // The field claims to be milliseconds but is actually seconds.
+    #[serde(rename = "lastUpdateMillis")]
+    last_update_seconds: u64,
 }
 
 impl ApexInfoList {
@@ -92,14 +97,15 @@
         self.list.iter().filter(|info| predicate(info)).map(|info| info.name.clone()).collect()
     }
 
-    fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
-        Ok(self
-            .list
+    fn get(&self, apex_name: &str) -> Result<&ApexInfo> {
+        self.list
             .iter()
             .find(|apex| apex.name == apex_name)
-            .ok_or_else(|| anyhow!("{} not found.", apex_name))?
-            .path
-            .clone())
+            .ok_or_else(|| anyhow!("{} not found.", apex_name))
+    }
+
+    fn get_path_for(&self, apex_name: &str) -> Result<PathBuf> {
+        Ok(self.get(apex_name)?.path.clone())
     }
 }
 
@@ -125,10 +131,12 @@
             let staged = pm.getStagedApexModuleNames()?;
             for apex_info in list.list.iter_mut() {
                 if staged.contains(&apex_info.name) {
-                    let staged_apex_info = pm.getStagedApexInfo(&apex_info.name)?;
-                    if let Some(staged_apex_info) = staged_apex_info {
+                    if let Some(staged_apex_info) = pm.getStagedApexInfo(&apex_info.name)? {
                         apex_info.path = PathBuf::from(staged_apex_info.diskImagePath);
                         apex_info.has_classpath_jar = staged_apex_info.hasClassPathJars;
+                        let metadata = metadata(&apex_info.path)?;
+                        apex_info.last_update_seconds =
+                            metadata.modified()?.duration_since(SystemTime::UNIX_EPOCH)?.as_secs();
                     }
                 }
             }
@@ -141,6 +149,7 @@
     config_path: &str,
     apex_names: &[String],
     temporary_directory: &Path,
+    apex_list: &ApexInfoList,
 ) -> Result<ParcelFileDescriptor> {
     let metadata_path = temporary_directory.join("metadata");
     let metadata = Metadata {
@@ -148,12 +157,15 @@
         apexes: apex_names
             .iter()
             .enumerate()
-            .map(|(i, apex_name)| ApexPayload {
-                name: apex_name.clone(),
-                partition_name: format!("microdroid-apex-{}", i),
-                ..Default::default()
+            .map(|(i, apex_name)| {
+                Ok(ApexPayload {
+                    name: apex_name.clone(),
+                    partition_name: format!("microdroid-apex-{}", i),
+                    last_update_seconds: apex_list.get(apex_name)?.last_update_seconds,
+                    ..Default::default()
+                })
             })
-            .collect(),
+            .collect::<Result<_>>()?,
         apk: Some(ApkPayload {
             name: "apk".to_owned(),
             payload_partition_name: "microdroid-apk".to_owned(),
@@ -212,7 +224,8 @@
     let apexes = collect_apex_names(&apex_list, &vm_payload_config.apexes, app_config.debugLevel);
     info!("Microdroid payload APEXes: {:?}", apexes);
 
-    let metadata_file = make_metadata_file(&app_config.configPath, &apexes, temporary_directory)?;
+    let metadata_file =
+        make_metadata_file(&app_config.configPath, &apexes, temporary_directory, &apex_list)?;
     // put metadata at the first partition
     let mut partitions = vec![Partition {
         label: "payload-metadata".to_owned(),
@@ -397,11 +410,13 @@
                     name: "hasnt_classpath".to_string(),
                     path: PathBuf::from("path0"),
                     has_classpath_jar: false,
+                    last_update_seconds: 12345678,
                 },
                 ApexInfo {
                     name: "has_classpath".to_string(),
                     path: PathBuf::from("path1"),
                     has_classpath_jar: true,
+                    last_update_seconds: 87654321,
                 },
             ],
         };