Merge "Skip 25Q1 (ab/BP1A.250305.020) in aosp-main-future" into aosp-main-future
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index d47afc4..1c078ce 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -19,6 +19,3 @@
 clang_format = --commit ${PREUPLOAD_COMMIT} --style file --extensions c,h,cc,cpp
 rustfmt = --config-path=rustfmt.toml
 ktfmt = --kotlinlang-style
-
-[Hook Scripts]
-aosp_hook = ${REPO_ROOT}/frameworks/base/tools/aosp/aosp_sha.sh ${PREUPLOAD_COMMIT} "."
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index 4aac37a..a9b4abe 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -38,6 +38,7 @@
 import android.system.virtualmachine.VirtualMachineException
 import android.util.Log
 import android.widget.Toast
+import com.android.internal.annotations.GuardedBy
 import com.android.system.virtualmachine.flags.Flags.terminalGuiSupport
 import com.android.virtualization.terminal.MainActivity.Companion.TAG
 import com.android.virtualization.terminal.Runner.Companion.create
@@ -55,6 +56,7 @@
 import java.io.FileOutputStream
 import java.io.IOException
 import java.lang.RuntimeException
+import java.lang.Math.min
 import java.net.InetSocketAddress
 import java.net.SocketAddress
 import java.nio.file.Files
@@ -75,6 +77,40 @@
     private var server: Server? = null
     private var debianService: DebianServiceImpl? = null
     private var portNotifier: PortNotifier? = null
+    private var mLock = Object()
+    @GuardedBy("mLock")
+    private var currentMemBalloonPercent = 0;
+
+    @GuardedBy("mLock")
+    private val inflateMemBalloonHandler = Handler(Looper.getMainLooper())
+    private val inflateMemBalloonTask: Runnable = object : Runnable {
+        override fun run() {
+            synchronized(mLock) {
+                if (currentMemBalloonPercent < INITIAL_MEM_BALLOON_PERCENT
+                    || currentMemBalloonPercent > MAX_MEM_BALLOON_PERCENT
+                ) {
+                    Log.e(
+                        TAG, "currentBalloonPercent=$currentMemBalloonPercent is invalid," +
+                                " should be in range: " +
+                                "$INITIAL_MEM_BALLOON_PERCENT~$MAX_MEM_BALLOON_PERCENT"
+                    )
+                    return
+                }
+                // Increases the balloon size by MEM_BALLOON_PERCENT_STEP% every time
+                if (currentMemBalloonPercent < MAX_MEM_BALLOON_PERCENT) {
+                    currentMemBalloonPercent =
+                        min(
+                            MAX_MEM_BALLOON_PERCENT,
+                            currentMemBalloonPercent + MEM_BALLOON_PERCENT_STEP
+                        )
+                    virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
+                    inflateMemBalloonHandler.postDelayed(this,
+                        MEM_BALLOON_INFLATE_INTERVAL_MILLIS)
+                }
+            }
+        }
+    }
+
 
     interface VmLauncherServiceCallback {
         fun onVmStart()
@@ -99,13 +135,26 @@
             // When the app starts, reset the memory balloon to 0%.
             // This gives the app maximum available memory.
             ApplicationLifeCycleEvent.APP_ON_START -> {
-                virtualMachine?.setMemoryBalloonByPercent(0)
+                synchronized(mLock) {
+                    inflateMemBalloonHandler.removeCallbacks(inflateMemBalloonTask);
+                    currentMemBalloonPercent = 0;
+                    virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
+                }
             }
             ApplicationLifeCycleEvent.APP_ON_STOP -> {
-                // When the app stops, inflate the memory balloon to 10%.
-                // This allows the system to reclaim memory while the app is in the background.
-                // TODO(b/400590341) Inflate the balloon while the application remains Stop status.
-                virtualMachine?.setMemoryBalloonByPercent(10)
+                // When the app stops, inflate the memory balloon to INITIAL_MEM_BALLOON_PERCENT.
+                // Inflate the balloon by MEM_BALLOON_PERCENT_STEP every
+                // MEM_BALLOON_INFLATE_INTERVAL_MILLIS milliseconds until reaching
+                // MAX_MEM_BALLOON_PERCENT of total memory. This allows the system to reclaim
+                // memory while the app is in the background.
+                synchronized(mLock) {
+                    currentMemBalloonPercent = INITIAL_MEM_BALLOON_PERCENT;
+                    virtualMachine?.setMemoryBalloonByPercent(currentMemBalloonPercent)
+                    inflateMemBalloonHandler.postDelayed(
+                        inflateMemBalloonTask,
+                        MEM_BALLOON_INFLATE_INTERVAL_MILLIS
+                    );
+                }
             }
             else -> {
                 Log.e(TAG, "unrecognized lifecycle event: $event")
@@ -388,6 +437,11 @@
         private const val RESULT_STOP = 1
         private const val RESULT_ERROR = 2
 
+        private const val INITIAL_MEM_BALLOON_PERCENT = 10
+        private const val MAX_MEM_BALLOON_PERCENT = 50
+        private const val MEM_BALLOON_INFLATE_INTERVAL_MILLIS = 60000L
+        private const val MEM_BALLOON_PERCENT_STEP = 5;
+
         private fun getMyIntent(context: Context): Intent {
             return Intent(context.getApplicationContext(), VmLauncherService::class.java)
         }
diff --git a/android/vm/vm_shell.sh b/android/vm/vm_shell.sh
index 60d9329..cac5781 100755
--- a/android/vm/vm_shell.sh
+++ b/android/vm/vm_shell.sh
@@ -50,15 +50,13 @@
 }
 
 function list_cids() {
-    local selected_cid=$1
-    local available_cids=$(adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }')
-    echo "${available_cids}"
+    adb shell /apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }'
 }
 
 function handle_connect_cmd() {
     selected_cid=$1
 
-    available_cids=$(list_cids)
+    available_cids=($(list_cids))
 
     if [ -z "${available_cids}" ]; then
         echo No VM is available
@@ -66,11 +64,11 @@
     fi
 
     if [ ! -n "${selected_cid}" ]; then
-        if [ ${#selected_cid[@]} -eq 1 ]; then
+        if [ ${#available_cids[@]} -eq 1 ]; then
             selected_cid=${available_cids[0]}
         else
             PS3="Select CID of VM to adb-shell into: "
-            select cid in ${available_cids}
+            select cid in ${available_cids[@]}
             do
                 selected_cid=${cid}
                 break
diff --git a/tests/backcompat_test/Android.bp b/tests/backcompat_test/Android.bp
index aa1e089..d47487a 100644
--- a/tests/backcompat_test/Android.bp
+++ b/tests/backcompat_test/Android.bp
@@ -14,6 +14,7 @@
         "libanyhow",
         "liblibc",
         "libnix",
+        "librustutils",
         "libvmclient",
         "liblog_rust",
     ],
diff --git a/tests/backcompat_test/src/main.rs b/tests/backcompat_test/src/main.rs
index b92049d..eaf3365 100644
--- a/tests/backcompat_test/src/main.rs
+++ b/tests/backcompat_test/src/main.rs
@@ -25,6 +25,7 @@
 use anyhow::anyhow;
 use anyhow::Context;
 use anyhow::Error;
+use anyhow::Result;
 use log::info;
 use std::fs::read_to_string;
 use std::fs::File;
@@ -46,11 +47,11 @@
 
 /// Runs a protected VM and validates it against a golden device tree.
 #[test]
-fn test_device_tree_protected_compat() -> Result<(), Error> {
+fn test_device_tree_protected_compat() -> Result<()> {
     run_test(true, GOLDEN_DEVICE_TREE_PROTECTED)
 }
 
-fn run_test(protected: bool, golden_dt: &str) -> Result<(), Error> {
+fn run_test(protected: bool, golden_dt: &str) -> Result<()> {
     let kernel = Some(open_payload(VMBASE_EXAMPLE_KERNEL_PATH)?);
     android_logger::init_once(
         android_logger::Config::default()
@@ -142,7 +143,8 @@
     {
         return Err(anyhow!("failed to execute dtc"));
     }
-    let dtcompare_res = Command::new("./dtcompare")
+    let mut dtcompare_cmd = Command::new("./dtcompare");
+    dtcompare_cmd
         .arg("--dt1")
         .arg("dump_dt_golden.dtb")
         .arg("--dt2")
@@ -162,12 +164,23 @@
         .arg("/chosen/linux,initrd-start")
         .arg("--ignore-path-value")
         .arg("/chosen/linux,initrd-end")
-        .arg("--ignore-path-value")
-        .arg("/avf/secretkeeper_public_key")
         .arg("--ignore-path")
-        .arg("/avf/name")
-        .output()
-        .context("failed to execute dtcompare")?;
+        .arg("/avf/name");
+    // Check if Secretkeeper is advertised. If not, check the vendor API level. Secretkeeper is
+    // required as of 202504, and if missing, the test should fail.
+    // Otherwise, ignore the fields, as they are not required.
+    if service.isUpdatableVmSupported()? {
+        dtcompare_cmd.arg("--ignore-path-value").arg("/avf/secretkeeper_public_key");
+    } else if vsr_api_level()? >= 202504 {
+        return Err(anyhow!("Secretkeeper support missing on vendor API >= 202504. Secretkeeper needs to be implemented."));
+    } else {
+        dtcompare_cmd
+            .arg("--ignore-path")
+            .arg("/avf/secretkeeper_public_key")
+            .arg("--ignore-path")
+            .arg("/avf/untrusted/defer-rollback-protection");
+    }
+    let dtcompare_res = dtcompare_cmd.output().context("failed to execute dtcompare")?;
     if !dtcompare_res.status.success() {
         if !Command::new("./dtc_static")
             .arg("-I")
@@ -202,7 +215,17 @@
     Ok(())
 }
 
-fn open_payload(path: &str) -> Result<ParcelFileDescriptor, Error> {
+fn open_payload(path: &str) -> Result<ParcelFileDescriptor> {
     let file = File::open(path).with_context(|| format!("Failed to open VM image {path}"))?;
     Ok(ParcelFileDescriptor::new(file))
 }
+
+fn vsr_api_level() -> Result<i32> {
+    get_sysprop_i32("ro.vendor.api_level")
+}
+
+fn get_sysprop_i32(prop: &str) -> Result<i32> {
+    let res = rustutils::system_properties::read(prop)?;
+    res.map(|val| val.parse::<i32>().with_context(|| format!("Failed to read {prop}")))
+        .unwrap_or(Ok(-1))
+}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 59a57f1..5513af6 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -1463,7 +1463,7 @@
     }
 
     private void ensureUpdatableVmSupported() throws DeviceNotAvailableException {
-        if (PropertyUtil.isVendorApiLevelAtLeast(getAndroidDevice(), 202504)) {
+        if (PropertyUtil.getVsrApiLevel(getAndroidDevice()) >= 202504) {
             assertTrue(
                     "Missing Updatable VM support, have you declared Secretkeeper interface?",
                     isUpdatableVmSupported());