Merge "AVF: promote FD->IBinder to SystemApi." into main
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/DebianServiceImpl.kt b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
index 887ae02..e035ad4 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/DebianServiceImpl.kt
@@ -28,6 +28,7 @@
 import com.android.virtualization.terminal.proto.ReportVmActivePortsResponse
 import com.android.virtualization.terminal.proto.ShutdownQueueOpeningRequest
 import com.android.virtualization.terminal.proto.ShutdownRequestItem
+import io.grpc.stub.ServerCallStreamObserver
 import io.grpc.stub.StreamObserver
 
 internal class DebianServiceImpl(context: Context) : DebianServiceImplBase() {
@@ -79,8 +80,15 @@
         request: ShutdownQueueOpeningRequest?,
         responseObserver: StreamObserver<ShutdownRequestItem?>,
     ) {
+        val serverCallStreamObserver = responseObserver as ServerCallStreamObserver<ShutdownRequestItem?>
+        serverCallStreamObserver.setOnCancelHandler {
+            shutdownRunnable = null
+        }
         Log.d(TAG, "openShutdownRequestQueue")
         shutdownRunnable = Runnable {
+            if (serverCallStreamObserver.isCancelled()) {
+                return@Runnable
+            }
             responseObserver.onNext(ShutdownRequestItem.newBuilder().build())
             responseObserver.onCompleted()
             shutdownRunnable = null
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
index 017ff89..54754ff 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ImageArchive.kt
@@ -65,6 +65,14 @@
         }
     }
 
+    /** Returns path to the archive. */
+    fun getPath(): String {
+        return when (source) {
+            is UrlSource -> source.value.toString()
+            is PathSource -> source.value.toString()
+        }
+    }
+
     /** Returns size of the archive in bytes */
     @Throws(IOException::class)
     fun getSize(): Long {
@@ -138,8 +146,8 @@
     companion object {
         private const val DIR_IN_SDCARD = "linux"
         private const val ARCHIVE_NAME = "images.tar.gz"
-        private const val BUILD_TAG = "latest" // TODO: use actual tag name
-        private const val HOST_URL = "https://dl.google.com/android/ferrochrome/$BUILD_TAG"
+        private val BUILD_TAG = Integer.toString(Build.VERSION.SDK_INT_FULL)
+        private val HOST_URL = "https://dl.google.com/android/ferrochrome/$BUILD_TAG"
 
         fun getSdcardPathForTesting(): Path {
             return Environment.getExternalStoragePublicDirectory(DIR_IN_SDCARD).toPath()
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
index 7180e87..01c3880 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/InstallerService.kt
@@ -150,21 +150,26 @@
 
     private fun downloadFromSdcard(): Boolean {
         val archive = fromSdCard()
+        val archive_path = archive.getPath()
 
         // Installing from sdcard is preferred, but only supported only in debuggable build.
-        if (Build.isDebuggable() && archive.exists()) {
-            Log.i(TAG, "trying to install /sdcard/linux/images.tar.gz")
+        if (!Build.isDebuggable()) {
+            Log.i(TAG, "Non-debuggable build doesn't support installation from $archive_path")
+            return false
+        }
+        if (!archive.exists()) {
+            return false
+        }
 
-            val dest = getDefault(this).installDir
-            try {
-                archive.installTo(dest, null)
-                Log.i(TAG, "image is installed from /sdcard/linux/images.tar.gz")
-                return true
-            } catch (e: IOException) {
-                Log.i(TAG, "Failed to install /sdcard/linux/images.tar.gz", e)
-            }
-        } else {
-            Log.i(TAG, "Non-debuggable build doesn't support installation from /sdcard/linux")
+        Log.i(TAG, "trying to install $archive_path")
+
+        val dest = getDefault(this).installDir
+        try {
+            archive.installTo(dest, null)
+            Log.i(TAG, "image is installed from $archive_path")
+            return true
+        } catch (e: IOException) {
+            Log.i(TAG, "Failed to install $archive_path", e)
         }
         return false
     }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
index 547f1a7..4162247 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/Logger.kt
@@ -30,6 +30,7 @@
 import java.nio.file.Files
 import java.nio.file.Path
 import java.nio.file.StandardOpenOption
+import java.time.LocalDateTime
 import java.util.concurrent.ExecutorService
 import libcore.io.Streams
 
@@ -37,14 +38,20 @@
  * Forwards VM's console output to a file on the Android side, and VM's log output to Android logd.
  */
 internal object Logger {
-    fun setup(vm: VirtualMachine, path: Path, executor: ExecutorService) {
+    fun setup(vm: VirtualMachine, dir: Path, executor: ExecutorService) {
+        val tag = vm.name
+
         if (vm.config.debugLevel != VirtualMachineConfig.DEBUG_LEVEL_FULL) {
+            Log.i(tag, "Logs are not captured. Non-debuggable VM.")
             return
         }
 
         try {
+            Files.createDirectories(dir)
+            deleteOldLogs(dir, 10)
+            val logPath = dir.resolve(LocalDateTime.now().toString() + ".txt")
             val console = vm.getConsoleOutput()
-            val file = Files.newOutputStream(path, StandardOpenOption.CREATE)
+            val file = Files.newOutputStream(logPath, StandardOpenOption.CREATE)
             executor.submit<Int?> {
                 console.use { console ->
                     LineBufferedOutputStream(file).use { fileOutput ->
@@ -54,7 +61,7 @@
             }
 
             val log = vm.getLogOutput()
-            executor.submit<Unit> { log.use { writeToLogd(it, vm.name) } }
+            executor.submit<Unit> { log.use { writeToLogd(it, tag) } }
         } catch (e: VirtualMachineException) {
             throw RuntimeException(e)
         } catch (e: IOException) {
@@ -62,12 +69,32 @@
         }
     }
 
+    fun deleteOldLogs(dir: Path, numLogsToKeep: Long) {
+        Files.list(dir)
+            .filter { Files.isRegularFile(it) }
+            .sorted(
+                Comparator.comparingLong { f: Path ->
+                        // for some reason, type inference didn't work here!
+                        Files.getLastModifiedTime(f).toMillis()
+                    }
+                    .reversed()
+            )
+            .skip(numLogsToKeep)
+            .forEach {
+                try {
+                    Files.delete(it)
+                } catch (e: IOException) {
+                    // don't bother
+                }
+            }
+    }
+
     @Throws(IOException::class)
-    private fun writeToLogd(input: InputStream?, vmName: String?) {
+    private fun writeToLogd(input: InputStream?, tag: String?) {
         val reader = BufferedReader(InputStreamReader(input))
         reader
             .useLines { lines -> lines.takeWhile { !Thread.interrupted() } }
-            .forEach { Log.d(vmName, it) }
+            .forEach { Log.d(tag, it) }
     }
 
     private class LineBufferedOutputStream(out: OutputStream?) : BufferedOutputStream(out) {
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
index f6eeff9..35c5570 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -24,14 +24,11 @@
 import android.graphics.drawable.Icon
 import android.graphics.fonts.FontStyle
 import android.net.Uri
-import android.net.nsd.NsdManager
-import android.net.nsd.NsdServiceInfo
 import android.os.Build
 import android.os.Bundle
 import android.os.ConditionVariable
 import android.os.Environment
 import android.os.SystemProperties
-import android.os.Trace
 import android.provider.Settings
 import android.util.DisplayMetrics
 import android.util.Log
@@ -62,6 +59,7 @@
 import java.io.IOException
 import java.net.MalformedURLException
 import java.net.URL
+import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
 
@@ -78,12 +76,11 @@
     private lateinit var image: InstalledImage
     private lateinit var accessibilityManager: AccessibilityManager
     private lateinit var manageExternalStorageActivityResultLauncher: ActivityResultLauncher<Intent>
-    private var ipAddress: String? = null
-    private var port: Int? = null
     private lateinit var terminalViewModel: TerminalViewModel
     private lateinit var viewPager: ViewPager2
     private lateinit var tabLayout: TabLayout
     private lateinit var terminalTabAdapter: TerminalTabAdapter
+    private val terminalInfo = CompletableFuture<TerminalInfo>()
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
@@ -243,40 +240,12 @@
     }
 
     fun connectToTerminalService(terminalView: TerminalView) {
-        if (ipAddress != null && port != null) {
-            val url = getTerminalServiceUrl(ipAddress, port!!)
-            terminalView.loadUrl(url.toString())
-            return
-        }
-        // TODO: refactor this block as a method
-        val nsdManager = getSystemService<NsdManager>(NsdManager::class.java)
-        val info = NsdServiceInfo()
-        info.serviceType = "_http._tcp"
-        info.serviceName = "ttyd"
-        nsdManager.registerServiceInfoCallback(
-            info,
-            executorService,
-            object : NsdManager.ServiceInfoCallback {
-                var loaded: Boolean = false
-
-                override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {}
-
-                override fun onServiceInfoCallbackUnregistered() {}
-
-                override fun onServiceLost() {}
-
-                override fun onServiceUpdated(info: NsdServiceInfo) {
-                    Log.i(TAG, "Service found: $info")
-                    if (!loaded) {
-                        ipAddress = info.hostAddresses[0].hostAddress
-                        port = info.port
-                        val url = getTerminalServiceUrl(ipAddress, port!!)
-                        loaded = true
-                        nsdManager.unregisterServiceInfoCallback(this)
-                        runOnUiThread(Runnable { terminalView.loadUrl(url.toString()) })
-                    }
-                }
+        terminalInfo.thenAcceptAsync(
+            { info ->
+                val url = getTerminalServiceUrl(info.ipAddress, info.port)
+                runOnUiThread({ terminalView.loadUrl(url.toString()) })
             },
+            executorService,
         )
     }
 
@@ -292,6 +261,10 @@
         Log.i(TAG, "onVmStart()")
     }
 
+    override fun onTerminalAvailable(info: TerminalInfo) {
+        terminalInfo.complete(info)
+    }
+
     override fun onVmStop() {
         Log.i(TAG, "onVmStop()")
         finish()
@@ -354,7 +327,7 @@
 
         val stopIntent = Intent()
         stopIntent.setClass(this, VmLauncherService::class.java)
-        stopIntent.setAction(VmLauncherService.ACTION_STOP_VM_LAUNCHER_SERVICE)
+        stopIntent.setAction(VmLauncherService.ACTION_SHUTDOWN_VM)
         val stopPendingIntent =
             PendingIntent.getService(
                 this,
@@ -389,7 +362,6 @@
                 )
                 .build()
 
-        Trace.beginAsyncSection("executeTerminal", 0)
         run(this, this, notification, getDisplayInfo())
     }
 
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt
new file mode 100644
index 0000000..7647d9b
--- /dev/null
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MemBalloonController.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2025 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.virtualization.terminal
+
+import android.content.Context
+import android.os.Handler
+import android.os.Looper
+import android.system.virtualmachine.VirtualMachine
+import android.util.Log
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.ProcessLifecycleOwner
+import com.android.virtualization.terminal.MainActivity.Companion.TAG
+import java.util.concurrent.Executors
+import java.util.concurrent.ScheduledFuture
+import java.util.concurrent.TimeUnit
+
+/**
+ * MemBalloonController is responsible for adjusting the memory ballon size of a VM depending on
+ * whether the app is visible or running in the background
+ */
+class MemBalloonController(val context: Context, val vm: VirtualMachine) {
+    companion object {
+        private const val INITIAL_PERCENT = 10
+        private const val MAX_PERCENT = 50
+        private const val INFLATION_STEP_PERCENT = 5
+        private const val INFLATION_PERIOD_SEC = 60L
+
+        private val mainHandler = Handler(Looper.getMainLooper())
+
+        private fun runOnMainThread(runnable: Runnable) {
+            mainHandler.post(runnable)
+        }
+    }
+
+    private val executor =
+        Executors.newSingleThreadScheduledExecutor(
+            TerminalThreadFactory(context.getApplicationContext())
+        )
+
+    private val observer =
+        object : DefaultLifecycleObserver {
+
+            // If the app is started or resumed, give deflate the balloon to 0 to give maximum
+            // available memory to the virtual machine
+            override fun onResume(owner: LifecycleOwner) {
+                ongoingInflation?.cancel(false)
+                executor.submit({
+                    Log.v(TAG, "app resumed. deflating mem balloon to the minimum")
+                    vm.setMemoryBalloonByPercent(0)
+                })
+            }
+
+            // If the app goes into background, progressively inflate the balloon from
+            // INITIAL_PERCENT until it reaches MAX_PERCENT
+            override fun onStop(owner: LifecycleOwner) {
+                ongoingInflation?.cancel(false)
+                balloonPercent = INITIAL_PERCENT
+                ongoingInflation =
+                    executor.scheduleAtFixedRate(
+                        {
+                            if (balloonPercent <= MAX_PERCENT) {
+                                Log.v(TAG, "inflating mem balloon to ${balloonPercent} %")
+                                vm.setMemoryBalloonByPercent(balloonPercent)
+                                balloonPercent += INFLATION_STEP_PERCENT
+                            } else {
+                                Log.v(TAG, "mem balloon is inflated to its max (${MAX_PERCENT} %)")
+                                ongoingInflation!!.cancel(false)
+                            }
+                        },
+                        0 /* initialDelay */,
+                        INFLATION_PERIOD_SEC,
+                        TimeUnit.SECONDS,
+                    )
+            }
+        }
+
+    private var balloonPercent = 0
+    private var ongoingInflation: ScheduledFuture<*>? = null
+
+    fun start() {
+        // addObserver is @MainThread
+        runOnMainThread({ ProcessLifecycleOwner.get().lifecycle.addObserver(observer) })
+    }
+
+    fun stop() {
+        // removeObserver is @MainThread
+        runOnMainThread({
+            ProcessLifecycleOwner.get().lifecycle.removeObserver(observer)
+            executor.shutdown()
+        })
+    }
+}
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt b/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt
index ed340d2..7c3eb69 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ModifierKeysController.kt
@@ -115,10 +115,12 @@
             activeTerminalView!!.hasFocus() &&
             !(activity.resources.configuration.keyboard == Configuration.KEYBOARD_QWERTY)
 
-    // If terminal's height is less than 30% of the screen height, we need to show modifier keys in
-    // a single line to save the vertical space
-    private fun needsKeysInSingleLine(): Boolean =
-        activeTerminalView!!.height.div(activity.window.decorView.height.toFloat()) < 0.3f
+    // If terminal's height including height of modifier keys is less than 40% of the screen
+    // height, we need to show modifier keys in a single line to save the vertical space
+    private fun needsKeysInSingleLine(): Boolean {
+        val keys = if (keysInSingleLine) keysSingleLine else keysDoubleLine
+        return activeTerminalView!!.height + keys.height < 0.4f * activity.window.decorView.height
+    }
 
     companion object {
         private val BTN_KEY_CODE_MAP =
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
index 5c01ead..7e78235 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalTabFragment.kt
@@ -19,7 +19,6 @@
 import android.graphics.Bitmap
 import android.net.http.SslError
 import android.os.Bundle
-import android.os.Trace
 import android.util.Log
 import android.view.LayoutInflater
 import android.view.View
@@ -145,7 +144,6 @@
                 object : WebView.VisualStateCallback() {
                     override fun onComplete(completedRequestId: Long) {
                         if (completedRequestId == requestId) {
-                            Trace.endAsyncSection("executeTerminal", 0)
                             bootProgressView.visibility = View.GONE
                             terminalView.visibility = View.VISIBLE
                             terminalView.mapTouchToMouseEvent()
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
index 6301da4..1857175 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/VmLauncherService.kt
@@ -31,7 +31,7 @@
 import android.os.Parcel
 import android.os.Parcelable
 import android.os.ResultReceiver
-import android.os.Trace
+import android.os.SystemProperties
 import android.system.virtualmachine.VirtualMachine
 import android.system.virtualmachine.VirtualMachineCustomImageConfig
 import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig
@@ -58,12 +58,15 @@
 import java.net.InetSocketAddress
 import java.net.SocketAddress
 import java.nio.file.Files
+import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
 
 class VmLauncherService : Service() {
+    private lateinit var executorService: ExecutorService
+
     // TODO: using lateinit for some fields to avoid null
-    private var executorService: ExecutorService? = null
     private var virtualMachine: VirtualMachine? = null
     private var resultReceiver: ResultReceiver? = null
     private var server: Server? = null
@@ -73,6 +76,8 @@
     interface VmLauncherServiceCallback {
         fun onVmStart()
 
+        fun onTerminalAvailable(info: TerminalInfo)
+
         fun onVmStop()
 
         fun onVmError()
@@ -82,8 +87,13 @@
         return null
     }
 
+    override fun onCreate() {
+        super.onCreate()
+        executorService = Executors.newCachedThreadPool(TerminalThreadFactory(applicationContext))
+    }
+
     override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
-        if (intent.action == ACTION_STOP_VM_LAUNCHER_SERVICE) {
+        if (intent.action == ACTION_SHUTDOWN_VM) {
             if (debianService != null && debianService!!.shutdownDebian()) {
                 // During shutdown, change the notification content to indicate that it's closing
                 val notification = createNotificationForTerminalClose()
@@ -99,7 +109,6 @@
             Log.d(TAG, "VM instance is already started")
             return START_NOT_STICKY
         }
-        executorService = Executors.newCachedThreadPool(TerminalThreadFactory(applicationContext))
 
         val image = InstalledImage.getDefault(this)
         val json = ConfigJson.from(this, image.configPath)
@@ -108,25 +117,19 @@
         val displaySize = intent.getParcelableExtra(EXTRA_DISPLAY_INFO, DisplayInfo::class.java)
 
         customImageConfigBuilder.setAudioConfig(
-            AudioConfig.Builder()
-                .setUseSpeaker(true)
-                .setUseMicrophone(true)
-                .build()
+            AudioConfig.Builder().setUseSpeaker(true).setUseMicrophone(true).build()
         )
         if (overrideConfigIfNecessary(customImageConfigBuilder, displaySize)) {
             configBuilder.setCustomImageConfig(customImageConfigBuilder.build())
         }
         val config = configBuilder.build()
 
-        Trace.beginSection("vmCreate")
         val runner: Runner =
             try {
                 create(this, config)
             } catch (e: VirtualMachineException) {
                 throw RuntimeException("cannot create runner", e)
             }
-        Trace.endSection()
-        Trace.beginAsyncSection("debianBoot", 0)
 
         virtualMachine = runner.vm
         resultReceiver =
@@ -135,12 +138,16 @@
                 ResultReceiver::class.java,
             )
 
+        val mbc = MemBalloonController(this, virtualMachine!!)
+        mbc.start()
+
         runner.exitStatus.thenAcceptAsync { success: Boolean ->
+            mbc.stop()
             resultReceiver?.send(if (success) RESULT_STOP else RESULT_ERROR, null)
             stopSelf()
         }
-        val logPath = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
-        Logger.setup(virtualMachine!!, logPath, executorService!!)
+        val logDir = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
+        Logger.setup(virtualMachine!!, logDir, executorService)
 
         val notification =
             intent.getParcelableExtra<Notification?>(EXTRA_NOTIFICATION, Notification::class.java)
@@ -151,41 +158,73 @@
 
         portNotifier = PortNotifier(this)
 
-        // TODO: dedup this part
+        getTerminalServiceInfo()
+            .thenAcceptAsync(
+                { info ->
+                    val ipAddress = info.hostAddresses[0].hostAddress
+                    val port = info.port
+                    val bundle = Bundle()
+                    bundle.putString(KEY_TERMINAL_IPADDRESS, ipAddress)
+                    bundle.putInt(KEY_TERMINAL_PORT, port)
+                    resultReceiver!!.send(RESULT_TERMINAL_AVAIL, bundle)
+                    startDebianServer(ipAddress)
+                },
+                executorService,
+            )
+            .exceptionallyAsync(
+                { e ->
+                    Log.e(TAG, "Failed to start VM", e)
+                    resultReceiver!!.send(RESULT_ERROR, null)
+                    stopSelf()
+                    null
+                },
+                executorService,
+            )
+
+        return START_NOT_STICKY
+    }
+
+    private fun getTerminalServiceInfo(): CompletableFuture<NsdServiceInfo> {
+        val executor = Executors.newSingleThreadExecutor(TerminalThreadFactory(applicationContext))
         val nsdManager = getSystemService<NsdManager?>(NsdManager::class.java)
-        val info = NsdServiceInfo()
-        info.serviceType = "_http._tcp"
-        info.serviceName = "ttyd"
+        val queryInfo = NsdServiceInfo()
+        queryInfo.serviceType = "_http._tcp"
+        queryInfo.serviceName = "ttyd"
+        var resolvedInfo = CompletableFuture<NsdServiceInfo>()
+
         nsdManager.registerServiceInfoCallback(
-            info,
-            executorService!!,
+            queryInfo,
+            executor,
             object : NsdManager.ServiceInfoCallback {
-                var started: Boolean = false
+                var found: Boolean = false
 
                 override fun onServiceInfoCallbackRegistrationFailed(errorCode: Int) {}
 
-                override fun onServiceInfoCallbackUnregistered() {}
+                override fun onServiceInfoCallbackUnregistered() {
+                    executor.shutdown()
+                }
 
                 override fun onServiceLost() {}
 
                 override fun onServiceUpdated(info: NsdServiceInfo) {
                     Log.i(TAG, "Service found: $info")
-                    if (!started) {
-                        started = true
+                    if (!found) {
+                        found = true
                         nsdManager.unregisterServiceInfoCallback(this)
-                        startDebianServer(info.hostAddresses[0].hostAddress)
+                        resolvedInfo.complete(info)
                     }
                 }
             },
         )
 
-        return START_NOT_STICKY
+        resolvedInfo.orTimeout(VM_BOOT_TIMEOUT_SECONDS.toLong(), TimeUnit.SECONDS)
+        return resolvedInfo
     }
 
     private fun createNotificationForTerminalClose(): Notification {
         val stopIntent = Intent()
         stopIntent.setClass(this, VmLauncherService::class.java)
-        stopIntent.setAction(ACTION_STOP_VM_LAUNCHER_SERVICE)
+        stopIntent.setAction(ACTION_SHUTDOWN_VM)
         val stopPendingIntent =
             PendingIntent.getService(
                 this,
@@ -227,6 +266,21 @@
             )
             Toast.makeText(this, R.string.virgl_enabled, Toast.LENGTH_SHORT).show()
             changed = true
+        } else if (Files.exists(ImageArchive.getSdcardPathForTesting().resolve("gfxstream"))) {
+            // TODO: check if the configuration is right. current config comes from cuttlefish's one
+            builder.setGpuConfig(
+                VirtualMachineCustomImageConfig.GpuConfig.Builder()
+                    .setBackend("gfxstream")
+                    .setRendererUseEgl(false)
+                    .setRendererUseGles(false)
+                    .setRendererUseGlx(false)
+                    .setRendererUseSurfaceless(true)
+                    .setRendererUseVulkan(true)
+                    .setContextTypes(arrayOf<String>("gfxstream-vulkan", "gfxstream-composer"))
+                    .build()
+            )
+            Toast.makeText(this, "gfxstream", Toast.LENGTH_SHORT).show()
+            changed = true
         }
 
         // Set the initial display size
@@ -293,7 +347,7 @@
             return
         }
 
-        executorService!!.execute(
+        executorService.execute(
             Runnable {
                 // TODO(b/373533555): we can use mDNS for that.
                 val debianServicePortFile = File(filesDir, "debian_service_port")
@@ -321,10 +375,9 @@
                     Log.e(TAG, "failed to stop a VM instance", e)
                 }
             }
-            executorService?.shutdownNow()
-            executorService = null
             virtualMachine = null
         }
+        executorService.shutdownNow()
         super.onDestroy()
     }
 
@@ -338,12 +391,28 @@
         private const val ACTION_START_VM_LAUNCHER_SERVICE =
             "android.virtualization.START_VM_LAUNCHER_SERVICE"
         const val EXTRA_DISPLAY_INFO = "EXTRA_DISPLAY_INFO"
-        const val ACTION_STOP_VM_LAUNCHER_SERVICE: String =
-            "android.virtualization.STOP_VM_LAUNCHER_SERVICE"
+        const val ACTION_SHUTDOWN_VM: String = "android.virtualization.ACTION_SHUTDOWN_VM"
 
         private const val RESULT_START = 0
         private const val RESULT_STOP = 1
         private const val RESULT_ERROR = 2
+        private const val RESULT_TERMINAL_AVAIL = 3
+
+        private const val KEY_TERMINAL_IPADDRESS = "address"
+        private const val KEY_TERMINAL_PORT = "port"
+
+        private val VM_BOOT_TIMEOUT_SECONDS: Int =
+            {
+                val deviceName = SystemProperties.get("ro.product.vendor.device", "")
+                val cuttlefish = deviceName.startsWith("vsoc_")
+                val goldfish = deviceName.startsWith("emu64")
+
+                if (cuttlefish || goldfish) {
+                    3 * 60
+                } else {
+                    30
+                }
+            }()
 
         private fun getMyIntent(context: Context): Intent {
             return Intent(context.getApplicationContext(), VmLauncherService::class.java)
@@ -364,6 +433,11 @@
                         }
                         when (resultCode) {
                             RESULT_START -> callback.onVmStart()
+                            RESULT_TERMINAL_AVAIL -> {
+                                val ipAddress = resultData!!.getString(KEY_TERMINAL_IPADDRESS)
+                                val port = resultData!!.getInt(KEY_TERMINAL_PORT)
+                                callback.onTerminalAvailable(TerminalInfo(ipAddress!!, port))
+                            }
                             RESULT_STOP -> callback.onVmStop()
                             RESULT_ERROR -> callback.onVmError()
                         }
@@ -384,12 +458,14 @@
 
         fun stop(context: Context) {
             val i = getMyIntent(context)
-            i.setAction(ACTION_STOP_VM_LAUNCHER_SERVICE)
+            i.setAction(ACTION_SHUTDOWN_VM)
             context.startService(i)
         }
     }
 }
 
+data class TerminalInfo(val ipAddress: String, val port: Int)
+
 data class DisplayInfo(val width: Int, val height: Int, val dpi: Int, val refreshRate: Int) :
     Parcelable {
     constructor(
diff --git a/android/TerminalApp/res/values-af/strings.xml b/android/TerminalApp/res/values-af/strings.xml
index 6928614..d906b07 100644
--- a/android/TerminalApp/res/values-af/strings.xml
+++ b/android/TerminalApp/res/values-af/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> is geaktiveer"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Take wat lank neem"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Stelselgebeurtenisse"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-az/strings.xml b/android/TerminalApp/res/values-az/strings.xml
index 9b84701..b3ad1f6 100644
--- a/android/TerminalApp/res/values-az/strings.xml
+++ b/android/TerminalApp/res/values-az/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> aktivləşdirilib"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Uzunmüddətli tapşırıqlar"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistem tədbirləri"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-b+sr+Latn/strings.xml b/android/TerminalApp/res/values-b+sr+Latn/strings.xml
index 1bdef6d..6ec0a06 100644
--- a/android/TerminalApp/res/values-b+sr+Latn/strings.xml
+++ b/android/TerminalApp/res/values-b+sr+Latn/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> je omogućen"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Dugotrajni zadaci"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistemski događaji"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Kartica"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-be/strings.xml b/android/TerminalApp/res/values-be/strings.xml
index a056517..e78ba9c 100644
--- a/android/TerminalApp/res/values-be/strings.xml
+++ b/android/TerminalApp/res/values-be/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Модуль <xliff:g id="ID_1">VirGL</xliff:g> уключаны"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Працяглыя задачы"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Сістэмныя падзеі"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Укладка"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-bn/strings.xml b/android/TerminalApp/res/values-bn/strings.xml
index e81c3ea..f871f00 100644
--- a/android/TerminalApp/res/values-bn/strings.xml
+++ b/android/TerminalApp/res/values-bn/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> চালু করা আছে"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"দীর্ঘ সময় ধরে চালানো টাস্ক"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"সিস্টেম ইভেন্ট"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"ট্যাব"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-bs/strings.xml b/android/TerminalApp/res/values-bs/strings.xml
index 914cff9..1971481 100644
--- a/android/TerminalApp/res/values-bs/strings.xml
+++ b/android/TerminalApp/res/values-bs/strings.xml
@@ -90,5 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Omogućeno: <xliff:g id="ID_1">VirGL</xliff:g>"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Dugotrajni zadaci"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Događaji sistema"</string>
-    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
+    <string name="tab_default_title" msgid="2300417689389397930">"Kartica"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-ca/strings.xml b/android/TerminalApp/res/values-ca/strings.xml
index 7cb50d9..db72829 100644
--- a/android/TerminalApp/res/values-ca/strings.xml
+++ b/android/TerminalApp/res/values-ca/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> està activat"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Tasques de llarga durada"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Esdeveniments del sistema"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-cs/strings.xml b/android/TerminalApp/res/values-cs/strings.xml
index 41e7756..14fcd27 100644
--- a/android/TerminalApp/res/values-cs/strings.xml
+++ b/android/TerminalApp/res/values-cs/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Modul <xliff:g id="ID_1">VirGL</xliff:g> je aktivován"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Dlouho spuštěné úlohy"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Systémové události"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Karta"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-da/strings.xml b/android/TerminalApp/res/values-da/strings.xml
index e3eb0a8..4e153ef 100644
--- a/android/TerminalApp/res/values-da/strings.xml
+++ b/android/TerminalApp/res/values-da/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> er aktiveret"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Længerevarende opgaver"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Systemhændelser"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Fane"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-es/strings.xml b/android/TerminalApp/res/values-es/strings.xml
index 3cabc84..76ebaf9 100644
--- a/android/TerminalApp/res/values-es/strings.xml
+++ b/android/TerminalApp/res/values-es/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> se ha habilitado"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Tareas de larga duración"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Eventos del sistema"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-et/strings.xml b/android/TerminalApp/res/values-et/strings.xml
index 4adaa33..5234a4e 100644
--- a/android/TerminalApp/res/values-et/strings.xml
+++ b/android/TerminalApp/res/values-et/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> on lubatud"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Pikalt kestvad ülesanded"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Süsteemisündmused"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tabulaator"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-eu/strings.xml b/android/TerminalApp/res/values-eu/strings.xml
index 63aa7bb..403c305 100644
--- a/android/TerminalApp/res/values-eu/strings.xml
+++ b/android/TerminalApp/res/values-eu/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> gaituta dago"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Luze exekutatzen diren zereginak"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistemako gertaerak"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Fitxa"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-fr-rCA/strings.xml b/android/TerminalApp/res/values-fr-rCA/strings.xml
index 3660576..d4c1235 100644
--- a/android/TerminalApp/res/values-fr-rCA/strings.xml
+++ b/android/TerminalApp/res/values-fr-rCA/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> est activé"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Tâches de longue durée"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Événements système"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Onglet"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-gl/strings.xml b/android/TerminalApp/res/values-gl/strings.xml
index ad1cda8..504e687 100644
--- a/android/TerminalApp/res/values-gl/strings.xml
+++ b/android/TerminalApp/res/values-gl/strings.xml
@@ -90,5 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Activouse <xliff:g id="ID_1">VirGL</xliff:g>"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Tarefas de longa duración"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Eventos do sistema"</string>
-    <string name="tab_default_title" msgid="2300417689389397930">"Tabulador"</string>
+    <string name="tab_default_title" msgid="2300417689389397930">"Pestana"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-hu/strings.xml b/android/TerminalApp/res/values-hu/strings.xml
index afde089..bd4fe52 100644
--- a/android/TerminalApp/res/values-hu/strings.xml
+++ b/android/TerminalApp/res/values-hu/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"A(z) <xliff:g id="ID_1">VirGL</xliff:g> engedélyezve van"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Hosszan futó feladatok"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Rendszeresemények"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Lap"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-hy/strings.xml b/android/TerminalApp/res/values-hy/strings.xml
index 0df7a91..d33642c 100644
--- a/android/TerminalApp/res/values-hy/strings.xml
+++ b/android/TerminalApp/res/values-hy/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g>-ը միացված է"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Երկար աշխատող առաջադրանքներ"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Համակարգի իրադարձություններ"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Ներդիր"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-is/strings.xml b/android/TerminalApp/res/values-is/strings.xml
index 4d3d7a6..e7aad2b 100644
--- a/android/TerminalApp/res/values-is/strings.xml
+++ b/android/TerminalApp/res/values-is/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Kveikt er á <xliff:g id="ID_1">VirGL</xliff:g>"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Langvarandi verkefni"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Kerfistilvik"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Flipi"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-iw/strings.xml b/android/TerminalApp/res/values-iw/strings.xml
index 6b56764..6d2a0f3 100644
--- a/android/TerminalApp/res/values-iw/strings.xml
+++ b/android/TerminalApp/res/values-iw/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"‫<xliff:g id="ID_1">VirGL</xliff:g> מופעל"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"משימות ממושכות"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"אירועי מערכת"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"כרטיסייה"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-ky/strings.xml b/android/TerminalApp/res/values-ky/strings.xml
index 814c808..c80f891 100644
--- a/android/TerminalApp/res/values-ky/strings.xml
+++ b/android/TerminalApp/res/values-ky/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> иштетилди"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Узак тапшырмалар"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Тутумдук иш-чаралар"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Өтмөк"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-lv/strings.xml b/android/TerminalApp/res/values-lv/strings.xml
index 412b454..b87f61f 100644
--- a/android/TerminalApp/res/values-lv/strings.xml
+++ b/android/TerminalApp/res/values-lv/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> ir iespējots"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Ilgstoši uzdevumi"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistēmas notikumi"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Cilne"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-mk/strings.xml b/android/TerminalApp/res/values-mk/strings.xml
index 00ee26b..d071f41 100644
--- a/android/TerminalApp/res/values-mk/strings.xml
+++ b/android/TerminalApp/res/values-mk/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Овозможено: <xliff:g id="ID_1">VirGL</xliff:g>"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Задачи што се извршуваат долго"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Системски настани"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Картичка"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-nb/strings.xml b/android/TerminalApp/res/values-nb/strings.xml
index 3d3a50a..7481be8 100644
--- a/android/TerminalApp/res/values-nb/strings.xml
+++ b/android/TerminalApp/res/values-nb/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> er aktivert"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Langvarige oppgaver"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Systemhendelser"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Fane"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-pl/strings.xml b/android/TerminalApp/res/values-pl/strings.xml
index 59710d0..b5ad6d7 100644
--- a/android/TerminalApp/res/values-pl/strings.xml
+++ b/android/TerminalApp/res/values-pl/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Układ <xliff:g id="ID_1">VirGL</xliff:g> jest włączony"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Długotrwałe zadania"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Zdarzenia systemowe"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Karta"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-pt/strings.xml b/android/TerminalApp/res/values-pt/strings.xml
index ddfb46d..8c05964 100644
--- a/android/TerminalApp/res/values-pt/strings.xml
+++ b/android/TerminalApp/res/values-pt/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"O <xliff:g id="ID_1">VirGL</xliff:g> está ativado"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Tarefas de longa duração"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Eventos do sistema"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Guia"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-ro/strings.xml b/android/TerminalApp/res/values-ro/strings.xml
index b5575b4..c333535 100644
--- a/android/TerminalApp/res/values-ro/strings.xml
+++ b/android/TerminalApp/res/values-ro/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> este activat"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Activități de durată"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Evenimente de sistem"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-ru/strings.xml b/android/TerminalApp/res/values-ru/strings.xml
index c8ab061..7e86cb1 100644
--- a/android/TerminalApp/res/values-ru/strings.xml
+++ b/android/TerminalApp/res/values-ru/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g>: включено."</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Длительные задачи"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Системные события"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Вкладка"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-si/strings.xml b/android/TerminalApp/res/values-si/strings.xml
index 84c4840..7085d17 100644
--- a/android/TerminalApp/res/values-si/strings.xml
+++ b/android/TerminalApp/res/values-si/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> සබලයි"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"දිගු කාලයක් ධාවනය වන කාර්යයන්"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"පද්ධති සිදුවීම්"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"පටිත්ත"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-sk/strings.xml b/android/TerminalApp/res/values-sk/strings.xml
index 35a9806..b11ada0 100644
--- a/android/TerminalApp/res/values-sk/strings.xml
+++ b/android/TerminalApp/res/values-sk/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"Procesor <xliff:g id="ID_1">VirGL</xliff:g> je aktivovaný"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Dlho spustené úlohy"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Systémové udalosti"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-sl/strings.xml b/android/TerminalApp/res/values-sl/strings.xml
index ecd3931..8785b84 100644
--- a/android/TerminalApp/res/values-sl/strings.xml
+++ b/android/TerminalApp/res/values-sl/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> je omogočen"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Dolgotrajna opravila"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistemski dogodki"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Zavihek"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-sq/strings.xml b/android/TerminalApp/res/values-sq/strings.xml
index d1a5fe1..f540af8 100644
--- a/android/TerminalApp/res/values-sq/strings.xml
+++ b/android/TerminalApp/res/values-sq/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> është aktivizuar"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Detyrat afatgjata"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Ngjarjet e sistemit"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Tab"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-sr/strings.xml b/android/TerminalApp/res/values-sr/strings.xml
index 635c467..8620b8a 100644
--- a/android/TerminalApp/res/values-sr/strings.xml
+++ b/android/TerminalApp/res/values-sr/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> је омогућен"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Дуготрајни задаци"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Системски догађаји"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Картица"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-ta/strings.xml b/android/TerminalApp/res/values-ta/strings.xml
index 1b4c766..ba6edbf 100644
--- a/android/TerminalApp/res/values-ta/strings.xml
+++ b/android/TerminalApp/res/values-ta/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> இயக்கப்பட்டது"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"நீண்ட நேரம் இயங்கும் பணிகள்"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"சிஸ்டம் நிகழ்வுகள்"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"பிரிவு"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-tr/strings.xml b/android/TerminalApp/res/values-tr/strings.xml
index e97728c..cc3812e 100644
--- a/android/TerminalApp/res/values-tr/strings.xml
+++ b/android/TerminalApp/res/values-tr/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> etkinleştirildi"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Uzun süredir çalışan görevler"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Sistem etkinlikleri"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Sekme"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-uk/strings.xml b/android/TerminalApp/res/values-uk/strings.xml
index 8cb7601..4224d98 100644
--- a/android/TerminalApp/res/values-uk/strings.xml
+++ b/android/TerminalApp/res/values-uk/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> увімкнено"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Довготривалі завдання"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Події системи"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Вкладка"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-zh-rTW/strings.xml b/android/TerminalApp/res/values-zh-rTW/strings.xml
index 7ddb8cd..94f473d 100644
--- a/android/TerminalApp/res/values-zh-rTW/strings.xml
+++ b/android/TerminalApp/res/values-zh-rTW/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"<xliff:g id="ID_1">VirGL</xliff:g> 已啟用"</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"長時間執行的工作"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"系統事件"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"分頁"</string>
 </resources>
diff --git a/android/TerminalApp/res/values-zu/strings.xml b/android/TerminalApp/res/values-zu/strings.xml
index 1af2744..432102b 100644
--- a/android/TerminalApp/res/values-zu/strings.xml
+++ b/android/TerminalApp/res/values-zu/strings.xml
@@ -90,6 +90,5 @@
     <string name="virgl_enabled" msgid="5242525588039698086">"I-<xliff:g id="ID_1">VirGL</xliff:g> inikwe amandla."</string>
     <string name="notification_channel_long_running_name" msgid="7916541360369402952">"Imisebenzi esebenza isikhathi eside"</string>
     <string name="notification_channel_system_events_name" msgid="1004951444029742137">"Imicimbi yesistimu"</string>
-    <!-- no translation found for tab_default_title (2300417689389397930) -->
-    <skip />
+    <string name="tab_default_title" msgid="2300417689389397930">"Ithebhu"</string>
 </resources>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index e98ab5c..1c4c2eb 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -151,6 +151,17 @@
     }
 });
 
+// TODO(ioffe): add service for guest-ffa.
+const KNOWN_TEE_SERVICES: [&str; 0] = [];
+
+fn check_known_tee_service(tee_service: &str) -> binder::Result<()> {
+    if !KNOWN_TEE_SERVICES.contains(&tee_service) {
+        return Err(anyhow!("unknown tee_service {tee_service}"))
+            .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+    }
+    Ok(())
+}
+
 fn create_or_update_idsig_file(
     input_fd: &ParcelFileDescriptor,
     idsig_fd: &ParcelFileDescriptor,
@@ -487,6 +498,12 @@
     if path.starts_with("/system/product/") {
         return Ok(CallingPartition::Product);
     }
+    if path.starts_with("/data/nativetest/vendor/")
+        || path.starts_with("/data/nativetest64/vendor/")
+    {
+        return Ok(CallingPartition::Vendor);
+    }
+
     let partition = {
         let mut components = path.components();
         let Some(std::path::Component::Normal(partition)) = components.nth(1) else {
@@ -710,11 +727,38 @@
         *is_protected = config.protectedVm;
 
         if !config.teeServices.is_empty() {
+            if !config.protectedVm {
+                return Err(anyhow!("only protected VMs can request tee services"))
+                    .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+            }
             check_tee_service_permission(&caller_secontext, &config.teeServices)
                 .with_log()
                 .or_binder_exception(ExceptionCode::SECURITY)?;
         }
 
+        let mut system_tee_services = Vec::new();
+        let mut vendor_tee_services = Vec::new();
+        for tee_service in config.teeServices.clone() {
+            if !tee_service.starts_with("vendor.") {
+                check_known_tee_service(&tee_service)?;
+                system_tee_services.push(tee_service);
+            } else {
+                vendor_tee_services.push(tee_service);
+            }
+        }
+
+        // TODO(b/391774181): handle vendor tee services (which require talking to HAL) as well.
+        if !vendor_tee_services.is_empty() {
+            return Err(anyhow!("support for vendor tee services is coming soon!"))
+                .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+        }
+
+        // TODO(b/391774181): remove this check in a follow-up patch.
+        if !system_tee_services.is_empty() {
+            return Err(anyhow!("support for system tee services is coming soon!"))
+                .or_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION);
+        }
+
         let kernel = maybe_clone_file(&config.kernel)?;
         let initrd = maybe_clone_file(&config.initrd)?;
 
@@ -891,6 +935,20 @@
             })
             .collect::<binder::Result<_>>()?;
 
+        let memory_reclaim_supported =
+            system_properties::read_bool("hypervisor.memory_reclaim.supported", false)
+                .unwrap_or(false);
+
+        let balloon = config.balloon && memory_reclaim_supported;
+
+        if !balloon {
+            warn!(
+                "Memory balloon not enabled:
+                config.balloon={},hypervisor.memory_reclaim.supported={}",
+                config.balloon, memory_reclaim_supported
+            );
+        }
+
         // Actually start the VM.
         let crosvm_config = CrosvmConfig {
             cid,
@@ -930,7 +988,7 @@
             boost_uclamp: config.boostUclamp,
             gpu_config,
             audio_config,
-            balloon: config.balloon,
+            balloon,
             usb_config,
             dump_dt_fd,
             enable_hypervisor_specific_auth_method: config.enableHypervisorSpecificAuthMethod,
@@ -1402,7 +1460,7 @@
     calling_partition: CallingPartition,
 ) -> Result<()> {
     let path = format!("/proc/self/fd/{}", fd.as_raw_fd());
-    let link = fs::read_link(&path).context(format!("can't read_link {path}"))?;
+    let link = fs::read_link(&path).with_context(|| format!("can't read_link {path}"))?;
 
     // microdroid vendor image is OK
     if cfg!(vendor_modules) && link == Path::new("/vendor/etc/avf/microdroid/microdroid_vendor.img")
@@ -1410,7 +1468,10 @@
         return Ok(());
     }
 
-    let is_fd_vendor = link.starts_with("/vendor") || link.starts_with("/odm");
+    let fd_partition = find_partition(Some(&link))
+        .with_context(|| format!("can't find_partition {}", link.display()))?;
+    let is_fd_vendor =
+        fd_partition == CallingPartition::Vendor || fd_partition == CallingPartition::Odm;
     let is_caller_vendor =
         calling_partition == CallingPartition::Vendor || calling_partition == CallingPartition::Odm;
 
@@ -1586,9 +1647,8 @@
         | "virtualizationservice_data_file" // files created by VS / VirtMgr
         | "vendor_microdroid_file" // immutable dm-verity protected partition (/vendor/etc/avf/microdroid/.*)
          => Ok(()),
-        // It is difficult to require specific label types for vendor initiated VM's files, so we
-        // allow anything with a vendor prefix.
-        t if calling_partition == CallingPartition::Vendor && t.starts_with("vendor_")  => Ok(()),
+        // It is difficult to require specific label types for vendor initiated VM's files.
+        _ if calling_partition == CallingPartition::Vendor => Ok(()),
         _ => bail!("Label {} is not allowed", context),
     }
 }
@@ -1687,6 +1747,10 @@
             .or_service_specific_exception(-1)
     }
 
+    fn isMemoryBalloonEnabled(&self) -> binder::Result<bool> {
+        Ok(self.instance.balloon_enabled)
+    }
+
     fn getMemoryBalloon(&self) -> binder::Result<i64> {
         let balloon = self
             .instance
@@ -2794,6 +2858,14 @@
     }
 
     #[test]
+    fn test_vendor_in_data() {
+        assert_eq!(
+            CallingPartition::Vendor,
+            find_partition(Some(Path::new("/data/nativetest64/vendor/file"))).unwrap()
+        );
+    }
+
+    #[test]
     fn early_vm_exe_paths_match_succeeds_with_same_paths() {
         let early_vm = EarlyVm {
             name: "vm_demo_native_early".to_owned(),
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index 77710c3..15a4199 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -417,6 +417,8 @@
     pub vm_service: Mutex<Option<Strong<dyn IVirtualMachineService>>>,
     /// Recorded metrics of VM such as timestamp or cpu / memory usage.
     pub vm_metric: Mutex<VmMetric>,
+    // Whether virtio-balloon is enabled
+    pub balloon_enabled: bool,
     /// The latest lifecycle state which the payload reported itself to be in.
     payload_state: Mutex<PayloadState>,
     /// Represents the condition that payload_state was updated
@@ -449,6 +451,7 @@
         let cid = config.cid;
         let name = config.name.clone();
         let protected = config.protected;
+        let balloon_enabled = config.balloon;
         let requester_uid_name = User::from_uid(Uid::from_raw(requester_uid))
             .ok()
             .flatten()
@@ -469,6 +472,7 @@
             payload_state: Mutex::new(PayloadState::Starting),
             payload_state_updated: Condvar::new(),
             requester_uid_name,
+            balloon_enabled,
         };
         info!("{} created", &instance);
         Ok(instance)
@@ -673,6 +677,10 @@
         }
     }
 
+    fn is_vm_running(&self) -> bool {
+        matches!(&*self.vm_state.lock().unwrap(), VmState::Running { .. })
+    }
+
     /// Returns the last reported state of the VM payload.
     pub fn payload_state(&self) -> PayloadState {
         *self.payload_state.lock().unwrap()
@@ -722,6 +730,12 @@
 
     /// Returns current virtio-balloon size.
     pub fn get_memory_balloon(&self) -> Result<u64, Error> {
+        if !self.is_vm_running() {
+            bail!("get_memory_balloon when VM is not running");
+        }
+        if !self.balloon_enabled {
+            bail!("virtio-balloon is not enabled");
+        }
         let socket_path_cstring = path_to_cstring(&self.crosvm_control_socket_path);
         let mut balloon_actual = 0u64;
         // SAFETY: Pointers are valid for the lifetime of the call. Null `stats` is valid.
@@ -741,6 +755,12 @@
     /// Inflates the virtio-balloon by `num_bytes` to reclaim guest memory. Called in response to
     /// memory-trimming notifications.
     pub fn set_memory_balloon(&self, num_bytes: u64) -> Result<(), Error> {
+        if !self.is_vm_running() {
+            bail!("set_memory_balloon when VM is not running");
+        }
+        if !self.balloon_enabled {
+            bail!("virtio-balloon is not enabled");
+        }
         let socket_path_cstring = path_to_cstring(&self.crosvm_control_socket_path);
         // SAFETY: Pointer is valid for the lifetime of the call.
         let success = unsafe {
@@ -1038,8 +1058,7 @@
         .arg("--cid")
         .arg(config.cid.to_string());
 
-    if system_properties::read_bool("hypervisor.memory_reclaim.supported", false)? && config.balloon
-    {
+    if config.balloon {
         command.arg("--balloon-page-reporting");
     } else {
         command.arg("--no-balloon");
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
index a01d385..e7aeefd 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -49,6 +49,7 @@
     void stop();
 
     /** Access to the VM's memory balloon. */
+    boolean isMemoryBalloonEnabled();
     long getMemoryBalloon();
     void setMemoryBalloon(long num_bytes);
 
diff --git a/android/vm/src/main.rs b/android/vm/src/main.rs
index ff846a1..7178de5 100644
--- a/android/vm/src/main.rs
+++ b/android/vm/src/main.rs
@@ -22,8 +22,6 @@
     CpuOptions::CpuTopology::CpuTopology, IVirtualizationService::IVirtualizationService,
     PartitionType::PartitionType, VirtualMachineAppConfig::DebugLevel::DebugLevel,
 };
-#[cfg(not(llpvm_changes))]
-use anyhow::anyhow;
 use anyhow::{bail, Context, Error};
 use binder::{ProcessState, Strong};
 use clap::{Args, Parser};
@@ -220,7 +218,6 @@
     instance: PathBuf,
 
     /// Path to file containing instance_id. Required iff llpvm feature is enabled.
-    #[cfg(llpvm_changes)]
     #[arg(long = "instance-id-file")]
     instance_id: PathBuf,
 
@@ -255,26 +252,8 @@
         }
     }
 
-    fn instance_id(&self) -> Result<PathBuf, Error> {
-        cfg_if::cfg_if! {
-            if #[cfg(llpvm_changes)] {
-                Ok(self.instance_id.clone())
-            } else {
-                Err(anyhow!("LLPVM feature is disabled, --instance_id flag not supported"))
-            }
-        }
-    }
-
-    fn set_instance_id(&mut self, instance_id_file: PathBuf) -> Result<(), Error> {
-        cfg_if::cfg_if! {
-            if #[cfg(llpvm_changes)] {
-                self.instance_id = instance_id_file;
-                Ok(())
-            } else {
-                let _ = instance_id_file;
-                Err(anyhow!("LLPVM feature is disabled, --instance_id flag not supported"))
-            }
-        }
+    fn set_instance_id(&mut self, instance_id_file: PathBuf) {
+        self.instance_id = instance_id_file;
     }
 }
 
diff --git a/android/vm/src/run.rs b/android/vm/src/run.rs
index 8385fb4..1033164 100644
--- a/android/vm/src/run.rs
+++ b/android/vm/src/run.rs
@@ -87,8 +87,8 @@
         )?;
     }
 
-    let instance_id = if cfg!(llpvm_changes) {
-        let id_file = config.instance_id()?;
+    let instance_id = {
+        let id_file = config.instance_id;
         if id_file.exists() {
             let mut id = [0u8; 64];
             let mut instance_id_file = File::open(id_file)?;
@@ -100,9 +100,6 @@
             instance_id_file.write_all(&id)?;
             id
         }
-    } else {
-        // if llpvm feature flag is disabled, instance_id is not used.
-        [0u8; 64]
     };
 
     let storage = if let Some(ref path) = config.microdroid.storage {
@@ -254,10 +251,8 @@
         ..Default::default()
     };
 
-    if cfg!(llpvm_changes) {
-        app_config.set_instance_id(work_dir.join("instance_id"))?;
-        println!("instance_id file path: {}", app_config.instance_id()?.display());
-    }
+    app_config.set_instance_id(work_dir.join("instance_id"));
+    println!("instance_id file path: {}", app_config.instance_id.display());
 
     command_run_app(app_config)
 }
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/android/vm_demo_native/main.cpp b/android/vm_demo_native/main.cpp
index e1acc05..8fc14bf 100644
--- a/android/vm_demo_native/main.cpp
+++ b/android/vm_demo_native/main.cpp
@@ -329,11 +329,15 @@
                                                                       &ARpcSession_free);
     ARpcSession_setMaxIncomingThreads(session.get(), 1);
 
+    auto param = std::make_unique<std::shared_ptr<IVirtualMachine>>(std::move(vm));
+    auto paramDeleteFd = [](void* param) {
+        delete static_cast<std::shared_ptr<IVirtualMachine>*>(param);
+    };
+
     AIBinder* binder = ARpcSession_setupPreconnectedClient(
             session.get(),
             [](void* param) {
-                std::shared_ptr<IVirtualMachine> vm =
-                        *static_cast<std::shared_ptr<IVirtualMachine>*>(param);
+                IVirtualMachine* vm = static_cast<std::shared_ptr<IVirtualMachine>*>(param)->get();
                 ScopedFileDescriptor sock_fd;
                 ScopedAStatus ret = vm->connectVsock(ITestService::PORT, &sock_fd);
                 if (!ret.isOk()) {
@@ -341,7 +345,7 @@
                 }
                 return sock_fd.release();
             },
-            &vm);
+            param.release(), paramDeleteFd);
     if (binder == nullptr) {
         return Error() << "Failed to connect to vm payload";
     }
diff --git a/build/apex/Android.bp b/build/apex/Android.bp
index 5323296..8934de0 100644
--- a/build/apex/Android.bp
+++ b/build/apex/Android.bp
@@ -259,11 +259,6 @@
     srcs: [
         "sign_virt_apex.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
     required: [
         // sign_virt_apex should be runnable from outside the source tree,
         // therefore, any required tool should be listed in build/make/core/Makefile as well.
@@ -332,11 +327,6 @@
     srcs: [
         "replace_bytes.py",
     ],
-    version: {
-        py3: {
-            embedded_launcher: true,
-        },
-    },
 }
 
 // Encapsulate the contributions made by the com.android.virt to the bootclasspath.
diff --git a/build/apex/manifest.json b/build/apex/manifest.json
index b32aa7b..e596ce1 100644
--- a/build/apex/manifest.json
+++ b/build/apex/manifest.json
@@ -3,6 +3,7 @@
   "version": 2,
   "requireNativeLibs": [
     "libEGL.so",
-    "libGLESv2.so"
+    "libGLESv2.so",
+    "libvulkan.so"
   ]
 }
diff --git a/build/compos/CompOSPayloadApp/Android.bp b/build/compos/CompOSPayloadApp/Android.bp
index c6192b9..04465b3 100644
--- a/build/compos/CompOSPayloadApp/Android.bp
+++ b/build/compos/CompOSPayloadApp/Android.bp
@@ -5,5 +5,6 @@
 android_app {
     name: "CompOSPayloadApp",
     sdk_version: "current",
+    system_ext_specific: true,
     apex_available: ["com.android.compos"],
 }
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 5aa3f28..9c4d4b1 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -318,9 +318,8 @@
 
 generate_output_package() {
 	fdisk -l "${raw_disk_image}"
-	local vm_config="$SCRIPT_DIR/vm_config.json.${arch}"
+	local vm_config="$SCRIPT_DIR/vm_config.json"
 	local root_partition_num=1
-	local bios_partition_num=14
 	local efi_partition_num=15
 
 	pushd ${workdir} > /dev/null
@@ -329,9 +328,6 @@
 
 	loop=$(losetup -f --show --partscan $raw_disk_image)
 	dd if="${loop}p$root_partition_num" of=root_part
-	if [[ "$arch" == "x86_64" ]]; then
-		dd if="${loop}p$bios_partition_num" of=bios_part
-	fi
 	dd if="${loop}p$efi_partition_num" of=efi_part
 	losetup -d "${loop}"
 
@@ -342,9 +338,6 @@
 	fi
 
 	sed -i "s/{root_part_guid}/$(sfdisk --part-uuid $raw_disk_image $root_partition_num)/g" vm_config.json
-	if [[ "$arch" == "x86_64" ]]; then
-		sed -i "s/{bios_part_guid}/$(sfdisk --part-uuid $raw_disk_image $bios_partition_num)/g" vm_config.json
-	fi
 	sed -i "s/{efi_part_guid}/$(sfdisk --part-uuid $raw_disk_image $efi_partition_num)/g" vm_config.json
 
 	popd > /dev/null
diff --git a/build/debian/vm_config.json.aarch64 b/build/debian/vm_config.json
similarity index 100%
rename from build/debian/vm_config.json.aarch64
rename to build/debian/vm_config.json
diff --git a/build/debian/vm_config.json.x86_64 b/build/debian/vm_config.json.x86_64
deleted file mode 100644
index 463583f..0000000
--- a/build/debian/vm_config.json.x86_64
+++ /dev/null
@@ -1,42 +0,0 @@
-{
-    "name": "debian",
-    "disks": [
-        {
-            "partitions": [
-                {
-                    "label": "ROOT",
-                    "path": "$PAYLOAD_DIR/root_part",
-                    "writable": true,
-                    "guid": "{root_part_guid}"
-                },
-                {
-                    "label": "EFI",
-                    "path": "$PAYLOAD_DIR/efi_part",
-                    "writable": false,
-                    "guid": "{efi_part_guid}"
-                }
-            ],
-            "writable": true
-        }
-    ],
-    "sharedPath": [
-        {
-            "sharedPath": "/storage/emulated"
-        },
-        {
-            "sharedPath": "$APP_DATA_DIR/files"
-        }
-    ],
-    "protected": false,
-    "cpu_topology": "match_host",
-    "platform_version": "~1.0",
-    "memory_mib": 4096,
-    "debuggable": true,
-    "console_out": true,
-    "console_input_device": "ttyS0",
-    "network": true,
-    "auto_memory_balloon": true,
-    "gpu": {
-        "backend": "2d"
-    }
-}
diff --git a/guest/forwarder_guest_launcher/debian/service b/guest/forwarder_guest_launcher/debian/service
index 6824c70..7812d67 100644
--- a/guest/forwarder_guest_launcher/debian/service
+++ b/guest/forwarder_guest_launcher/debian/service
@@ -5,12 +5,14 @@
 After=virtiofs_internal.service
 
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/bin/forwarder_guest_launcher --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/forwarder_guest_launcher --grpc-port-file /mnt/internal/debian_service_port'
 Type=simple
 Restart=on-failure
 RestartSec=1
 User=root
 Group=root
+StandardOutput=journal
+StandardError=journal
 
 [Install]
 WantedBy=multi-user.target
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index f4c8ca9..3cb557a 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -52,10 +52,9 @@
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
-    /// grpc port number
+    /// path to a file where grpc port number is written
     #[arg(long)]
-    #[arg(alias = "grpc_port")]
-    grpc_port: String,
+    grpc_port_file: String,
 }
 
 async fn process_forwarding_request_queue(
@@ -163,11 +162,23 @@
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    env_logger::init();
+    env_logger::builder().filter_level(log::LevelFilter::Debug).init();
     debug!("Starting forwarder_guest_launcher");
     let args = Args::parse();
     let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
-    let addr = format!("https://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
+
+    // Wait for `grpc_port_file` becomes available.
+    const GRPC_PORT_MAX_RETRY_COUNT: u32 = 10;
+    for _ in 0..GRPC_PORT_MAX_RETRY_COUNT {
+        if std::path::Path::new(&args.grpc_port_file).exists() {
+            break;
+        }
+        debug!("{} does not exist. Wait 1 second", args.grpc_port_file);
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+    let grpc_port = std::fs::read_to_string(&args.grpc_port_file)?.trim().to_string();
+
+    let addr = format!("https://{}:{}", gateway_ip_addr.to_string(), grpc_port);
     let channel = Endpoint::from_shared(addr)?.connect().await?;
     let client = DebianServiceClient::new(channel);
 
diff --git a/guest/microdroid_manager/src/main.rs b/guest/microdroid_manager/src/main.rs
index d665c87..4537834 100644
--- a/guest/microdroid_manager/src/main.rs
+++ b/guest/microdroid_manager/src/main.rs
@@ -244,13 +244,14 @@
 fn verify_payload_with_instance_img(
     metadata: &Metadata,
     dice: &DiceDriver,
+    state: &mut VmInstanceState,
 ) -> Result<MicrodroidData> {
     let mut instance = InstanceDisk::new().context("Failed to load instance.img")?;
     let saved_data = instance.read_microdroid_data(dice).context("Failed to read identity data")?;
 
     if is_strict_boot() {
         // Provisioning must happen on the first boot and never again.
-        if is_new_instance_legacy() {
+        if Path::new(AVF_NEW_INSTANCE).exists() {
             ensure!(
                 saved_data.is_none(),
                 MicrodroidError::PayloadInvalidConfig(
@@ -286,12 +287,14 @@
             );
             info!("Saved data is verified.");
         }
+        *state = VmInstanceState::PreviouslySeen;
         saved_data
     } else {
         info!("Saving verified data.");
         instance
             .write_microdroid_data(&extracted_data, dice)
             .context("Failed to write identity data")?;
+        *state = VmInstanceState::NewlyCreated;
         extracted_data
     };
     Ok(instance_data)
@@ -321,13 +324,14 @@
             .context("Failed to load DICE from driver")?
     };
 
+    let mut state = VmInstanceState::Unknown;
     // Microdroid skips checking payload against instance image iff the device supports
-    // secretkeeper. In that case Microdroid use VmSecret::V2, which provide protection against
-    // rollback of boot images and packages.
+    // secretkeeper. In that case Microdroid use VmSecret::V2, which provides instance state
+    // and protection against rollback of boot images and packages.
     let instance_data = if should_defer_rollback_protection() {
         verify_payload(&metadata, None)?
     } else {
-        verify_payload_with_instance_img(&metadata, &dice)?
+        verify_payload_with_instance_img(&metadata, &dice, &mut state)?
     };
 
     let payload_metadata = metadata.payload.ok_or_else(|| {
@@ -337,7 +341,6 @@
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
     let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
-    let mut state = VmInstanceState::Unknown;
     let vm_secret = VmSecret::new(dice_artifacts, service, &mut state)
         .context("Failed to create VM secrets")?;
 
@@ -345,15 +348,7 @@
         VmInstanceState::NewlyCreated => true,
         VmInstanceState::PreviouslySeen => false,
         VmInstanceState::Unknown => {
-            // VmSecret instantiation was not able to determine the state. This should only happen
-            // for legacy secret mechanism (V1) - in which case fallback to legacy
-            // instance.img based determination of state.
-            ensure!(
-                !should_defer_rollback_protection(),
-                "VmInstanceState is Unknown whilst guest is expected to use V2 based secrets.
-                This should've never happened"
-            );
-            is_new_instance_legacy()
+            bail!("Vm instance state is still unknown, this should not have happened");
         }
     };
 
@@ -519,10 +514,6 @@
     Path::new(AVF_STRICT_BOOT).exists()
 }
 
-fn is_new_instance_legacy() -> bool {
-    Path::new(AVF_NEW_INSTANCE).exists()
-}
-
 fn is_verified_boot() -> bool {
     !Path::new(DEBUG_MICRODROID_NO_VERIFIED_BOOT).exists()
 }
diff --git a/guest/microdroid_manager/src/vm_secret.rs b/guest/microdroid_manager/src/vm_secret.rs
index f031859..674f010 100644
--- a/guest/microdroid_manager/src/vm_secret.rs
+++ b/guest/microdroid_manager/src/vm_secret.rs
@@ -35,6 +35,9 @@
     StoreSecretRequest, GetSecretResponse, GetSecretRequest};
 use secretkeeper_comm::data_types::error::SecretkeeperError;
 use std::fs;
+use std::thread;
+use rand::Rng;
+use std::time::Duration;
 use zeroize::Zeroizing;
 use std::sync::Mutex;
 use std::sync::Arc;
@@ -63,6 +66,8 @@
     0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
 ];
 
+const BACKOFF_SK_ACCESS_MS: u64 = 100;
+
 pub enum VmSecret {
     // V2 secrets are derived from 2 independently secured secrets:
     //      1. Secretkeeper protected secrets (skp secret).
@@ -118,15 +123,19 @@
             .map_err(|e| anyhow!("Failed to build a sealing_policy: {e}"))?;
         let session = SkVmSession::new(vm_service, &explicit_dice, policy)?;
         let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
-        if let Some(secret) = session.get_secret(id)? {
-            *skp_secret = secret;
-            *state = VmInstanceState::PreviouslySeen;
-        } else {
-            log::warn!("No entry found in Secretkeeper for this VM instance, creating new secret.");
-            *skp_secret = rand::random();
-            session.store_secret(id, skp_secret.clone())?;
-            *state = VmInstanceState::NewlyCreated;
-        }
+        get_or_create_sk_secret(&session, id, &mut skp_secret, state).or_else(|e| {
+            // TODO(b/399304956): Secretkeeper rejects requests when overloaded with
+            // connections from multiple clients. Backoff & retry again, hoping it is
+            // less busy then. Secretkeeper changes are required for more robust solutions.
+            log::info!(
+                "get_or_create_sk_secret failed with {e:?}. Refreshing connection & retrying!"
+            );
+            let mut rng = rand::thread_rng();
+            let backoff = rng.gen_range(BACKOFF_SK_ACCESS_MS..2 * BACKOFF_SK_ACCESS_MS);
+            thread::sleep(Duration::from_millis(backoff));
+            session.refresh()?;
+            get_or_create_sk_secret(&session, id, &mut skp_secret, state)
+        })?;
         Ok(Self::V2 {
             instance_id: id,
             dice_artifacts: explicit_dice,
@@ -283,8 +292,6 @@
     sealing_policy: Vec<u8>,
 }
 
-// TODO(b/378911776): This get_secret/store_secret fails on expired session.
-// Introduce retry after refreshing the session
 impl SkVmSession {
     fn new(
         vm_service: &Strong<dyn IVirtualMachineService>,
@@ -366,3 +373,21 @@
             ))
         })?)
 }
+
+fn get_or_create_sk_secret(
+    session: &SkVmSession,
+    id: [u8; ID_SIZE],
+    skp_secret: &mut Zeroizing<[u8; SECRET_SIZE]>,
+    state: &mut VmInstanceState,
+) -> Result<()> {
+    if let Some(secret) = session.get_secret(id)? {
+        **skp_secret = secret;
+        *state = VmInstanceState::PreviouslySeen;
+    } else {
+        log::warn!("No entry found in Secretkeeper for this VM instance, creating new secret.");
+        **skp_secret = rand::random();
+        session.store_secret(id, skp_secret.clone())?;
+        *state = VmInstanceState::NewlyCreated;
+    }
+    Ok(())
+}
diff --git a/guest/pvmfw/src/main.rs b/guest/pvmfw/src/main.rs
index 9afbcc3..30624cd 100644
--- a/guest/pvmfw/src/main.rs
+++ b/guest/pvmfw/src/main.rs
@@ -41,7 +41,6 @@
 use alloc::boxed::Box;
 use bssl_avf::Digester;
 use diced_open_dice::{bcc_handover_parse, DiceArtifacts, DiceContext, Hidden, VM_KEY_ALGORITHM};
-use hypervisor_backends::get_mem_sharer;
 use libfdt::Fdt;
 use log::{debug, error, info, trace, warn};
 use pvmfw_avb::verify_payload;
@@ -99,15 +98,7 @@
     }
 
     let guest_page_size = verified_boot_data.page_size.unwrap_or(SIZE_4KB);
-    // TODO(ptosi): Cache the (single?) granule once, in vmbase.
-    let hyp_page_size = if let Some(mem_sharer) = get_mem_sharer() {
-        Some(mem_sharer.granule().map_err(|e| {
-            error!("Failed to get granule size: {e}");
-            RebootReason::InternalError
-        })?)
-    } else {
-        None
-    };
+    let hyp_page_size = hypervisor_backends::get_granule_size();
     let _ =
         sanitize_device_tree(untrusted_fdt, vm_dtbo, vm_ref_dt, guest_page_size, hyp_page_size)?;
     let fdt = untrusted_fdt; // DT has now been sanitized.
diff --git a/guest/shutdown_runner/Cargo.toml b/guest/shutdown_runner/Cargo.toml
index 0b44baa..92f8762 100644
--- a/guest/shutdown_runner/Cargo.toml
+++ b/guest/shutdown_runner/Cargo.toml
@@ -7,6 +7,7 @@
 [dependencies]
 anyhow = "1.0.94"
 clap = { version = "4.5.20", features = ["derive"] }
+env_logger = "0.11.5"
 log = "0.4.22"
 netdev = "0.31.0"
 prost = "0.13.3"
diff --git a/guest/shutdown_runner/debian/service b/guest/shutdown_runner/debian/service
index 7188d36..a5249d0 100644
--- a/guest/shutdown_runner/debian/service
+++ b/guest/shutdown_runner/debian/service
@@ -4,12 +4,14 @@
 After=virtiofs_internal.service
 
 [Service]
-ExecStart=/usr/bin/bash -c '/usr/bin/shutdown_runner --grpc_port $(cat /mnt/internal/debian_service_port)'
+ExecStart=/usr/bin/bash -c '/usr/bin/shutdown_runner --grpc-port-file /mnt/internal/debian_service_port'
 Type=simple
 Restart=on-failure
 RestartSec=1
 User=root
 Group=root
+StandardOutput=journal
+StandardError=journal
 
 [Install]
 WantedBy=multi-user.target
diff --git a/guest/shutdown_runner/src/main.rs b/guest/shutdown_runner/src/main.rs
index 19e9883..4043002 100644
--- a/guest/shutdown_runner/src/main.rs
+++ b/guest/shutdown_runner/src/main.rs
@@ -1,3 +1,17 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
 use api::debian_service_client::DebianServiceClient;
 use api::ShutdownQueueOpeningRequest;
 use std::process::Command;
@@ -12,18 +26,28 @@
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
-    /// grpc port number
+    /// Path to a file where grpc port number is written
     #[arg(long)]
-    #[arg(alias = "grpc_port")]
-    grpc_port: String,
+    grpc_port_file: String,
 }
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    env_logger::builder().filter_level(log::LevelFilter::Debug).init();
     let args = Args::parse();
     let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
 
-    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
+    // Wait for `grpc_port_file` becomes available.
+    const GRPC_PORT_MAX_RETRY_COUNT: u32 = 10;
+    for _ in 0..GRPC_PORT_MAX_RETRY_COUNT {
+        if std::path::Path::new(&args.grpc_port_file).exists() {
+            break;
+        }
+        debug!("{} does not exist. Wait 1 second", args.grpc_port_file);
+        tokio::time::sleep(std::time::Duration::from_secs(1)).await;
+    }
+    let grpc_port = std::fs::read_to_string(&args.grpc_port_file)?.trim().to_string();
+    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), grpc_port);
 
     debug!("connect to grpc server {}", server_addr);
 
diff --git a/guest/trusty/common/Android.bp b/guest/trusty/common/Android.bp
index d6c524f..1a4c4d7 100644
--- a/guest/trusty/common/Android.bp
+++ b/guest/trusty/common/Android.bp
@@ -1,22 +1,3 @@
-soong_config_module_type {
-    name: "trusty_vm_prebuilt_etc",
-    module_type: "prebuilt_etc",
-    config_namespace: "trusty_system_vm",
-    bool_variables: [
-        "enabled",
-        "placeholder_trusted_hal",
-    ],
-    properties: ["src"],
-}
-
-soong_config_module_type {
-    name: "trusty_vm_avb_add_hash_footer",
-    module_type: "avb_add_hash_footer",
-    config_namespace: "trusty_system_vm",
-    bool_variables: ["enabled"],
-    properties: ["src"],
-}
-
 prebuilt_etc {
     name: "early_vms.xml",
     filename: "early_vms.xml",
diff --git a/guest/trusty/security_vm/launcher/security_vm_launcher-arm64.rc b/guest/trusty/security_vm/launcher/security_vm_launcher-arm64.rc
index c0e0537..b9c7147 100644
--- a/guest/trusty/security_vm/launcher/security_vm_launcher-arm64.rc
+++ b/guest/trusty/security_vm/launcher/security_vm_launcher-arm64.rc
@@ -1,9 +1,9 @@
-# TODO(b/393848713): use --protected for the vm launcher when issues are fixed
 # TODO(b/393848753): determine whether task_profiles shall be defined
 service trusty_security_vm_launcher /system_ext/bin/trusty_security_vm_launcher \
 --name trusty_security_vm_launcher \
 --kernel /system_ext/etc/vm/trusty_vm/trusty_security_vm.elf \
---memory-size-mib 32
+--memory-size-mib 32 \
+--protected
     disabled
     user system
     group system virtualmachine
diff --git a/guest/trusty/security_vm/vm/Android.bp b/guest/trusty/security_vm/vm/Android.bp
index cc01d1c..6fa0c32 100644
--- a/guest/trusty/security_vm/vm/Android.bp
+++ b/guest/trusty/security_vm/vm/Android.bp
@@ -2,11 +2,6 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-soong_config_module_type_import {
-    from: "packages/modules/Virtualization/guest/trusty/common/Android.bp",
-    module_types: ["trusty_vm_avb_add_hash_footer"],
-}
-
 // - Trusty VM payloads on arm64 are pvmfw enabled
 //   AVF VM build system uses the raw binary image (:trusty_security_vm_unsigned),
 //   adds pvmfw footer and generates a pvmfw-compliant signed elf file)
@@ -34,6 +29,7 @@
     ],
     visibility: [
         "//packages/modules/Virtualization/guest/trusty/test_vm/vm",
+        "//packages/modules/Virtualization/guest/trusty/test_vm_os/vm",
         "//vendor:__subpackages__",
     ],
 }
@@ -115,7 +111,7 @@
 
 TRUSTY_SECURITY_VM_VERSION = 1
 
-trusty_vm_avb_add_hash_footer {
+avb_add_hash_footer {
     name: "trusty_security_vm_signed_bin",
     filename: "trusty_security_vm_signed.bin",
     partition_name: "boot",
diff --git a/guest/trusty/test_vm/Android.bp b/guest/trusty/test_vm/Android.bp
index 699b673..676e231 100644
--- a/guest/trusty/test_vm/Android.bp
+++ b/guest/trusty/test_vm/Android.bp
@@ -20,6 +20,7 @@
 prebuilt_etc {
     name: "trusty_test_vm_config",
     enabled: false,
+    installable: false,
     arch: {
         arm64: {
             src: "trusty-test_vm-config-arm64.json",
@@ -40,9 +41,6 @@
         arm64: {
             enabled: true,
         },
-        x86_64: {
-            enabled: true,
-        },
     },
     src: "trusty-vm-launcher.sh",
     filename: "trusty-vm-launcher.sh",
@@ -55,16 +53,15 @@
         arm64: {
             enabled: true,
         },
-        x86_64: {
-            enabled: true,
-        },
     },
     src: "trusty-wait-ready.sh",
     filename: "trusty-wait-ready.sh",
 }
 
 sh_test {
-    name: "TrustyTestVM_UnitTests",
+    // VTS tests for all Trusted HALs defined
+    // under hardware/interfaces/security/see
+    name: "VtsSeeHalTargetTest",
     src: "trusty-ut-ctrl.sh",
     enabled: false,
     arch: {
@@ -81,9 +78,8 @@
         ":trusty_test_vm_config",
         "trusty-vm-launcher.sh",
         "trusty-wait-ready.sh",
+        ":trusty-ut-ctrl.system",
     ],
-    // TODO(b/378367793) use the AndroidTest.xml generated from the trusty
-    // test-map for test_vm payload
     test_config_template: "AndroidTest.xml",
     test_suites: [
         "general-tests",
diff --git a/guest/trusty/test_vm/AndroidTest.xml b/guest/trusty/test_vm/AndroidTest.xml
index 6fb0879..925b43c 100644
--- a/guest/trusty/test_vm/AndroidTest.xml
+++ b/guest/trusty/test_vm/AndroidTest.xml
@@ -23,6 +23,7 @@
     <!-- Target Preparers - Run Shell Commands -->
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
+        <option name="push-file" key="trusty-ut-ctrl.system" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl" />
         <option name="push-file" key="trusty-ut-ctrl.sh" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh" />
         <option name="push-file" key="trusty-vm-launcher.sh" value="/data/local/tmp/trusty_test_vm/trusty-vm-launcher.sh" />
         <option name="push-file" key="trusty-wait-ready.sh" value="/data/local/tmp/trusty_test_vm/trusty-wait-ready.sh" />
@@ -34,76 +35,16 @@
         <!--Note: the first run-command shall not expect the background command to have started -->
         <option name="run-bg-command" value="sh /data/local/tmp/trusty_test_vm/trusty-vm-launcher.sh" />
         <option name="run-command" value="sh /data/local/tmp/trusty_test_vm/trusty-wait-ready.sh" />
-        <option name="run-command" value="start storageproxyd_test_system" />
-        <option name="teardown-command" value="stop storageproxyd_test_system" />
-        <option name="teardown-command" value="killall storageproxyd_test_system || true" />
+        <option name="run-command" value="start storageproxyd_test_vm" />
+        <option name="teardown-command" value="stop storageproxyd_test_vm" />
+        <option name="teardown-command" value="killall storageproxyd_test_vm || true" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
         <option name="parse-gtest" value="true" />
         <option name="abort-if-device-lost" value="true"/>
         <option name="abort-if-root-lost" value="true" />
         <option name="per-binary-timeout" value="10m" />
-        <option name="test-command-line" key="com.android.kernel.mmutest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.mmutest"/>
-        <option name="test-command-line" key="com.android.kernel.threadtest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.threadtest"/>
-        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
-        <option name="test-command-line" key="com.android.kernel.timertest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.timertest"/>
-        <option name="test-command-line" key="com.android.kernel.btitest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.btitest"/>
-        <option name="test-command-line" key="com.android.kernel.cachetest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.cachetest"/>
-        <option name="test-command-line" key="com.android.kernel.console-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.console-unittest"/>
-        <option name="test-command-line" key="com.android.kernel.dpc-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.dpc-unittest"/>
-        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
-        <option name="test-command-line" key="com.android.kernel.ktipc.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.ktipc.test"/>
-        <option name="test-command-line" key="com.android.kernel.memorytest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.memorytest"/>
-        <option name="test-command-line" key="com.android.kernel.pactest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.pactest"/>
-        <option name="test-command-line" key="com.android.kernel.uirq-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.uirq-unittest"/>
-        <option name="test-command-line" key="com.android.kernel.usercopy-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.usercopy-unittest"/>
-        <option name="test-command-line" key="com.android.kernel.userscstest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.kernel.userscstest"/>
-        <option name="test-command-line" key="com.android.trusty.rust.keymint.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.keymint.test"/>
-        <option name="test-command-line" key="com.android.manifesttest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.manifesttest"/>
-        <option name="test-command-line" key="com.android.memref.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.memref.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.memref.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.memref.test"/>
-        <option name="test-command-line" key="com.android.timer-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.timer-unittest"/>
-        <option name="test-command-line" key="com.android.ipc-unittest.ctrl" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.ipc-unittest.ctrl"/>
-        <!--option name="test-command-line" key="com.android.trusty.cfitest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.cfitest"/-->
-        <option name="test-command-line" key="com.android.trusty.crashtest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.crashtest"/>
-        <option name="test-command-line" key="com.android.trusty.dlmalloctest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.dlmalloctest"/>
-        <option name="test-command-line" key="com.android.trusty.hwaes.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.hwaes.test"/>
-        <option name="test-command-line" key="com.android.trusty.hwbcc.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.hwbcc.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.tipc.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.tipc.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.hwkey.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwkey.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.hwbcc.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwbcc.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.hwwsk.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwwsk.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.storage.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.storage.test"/>
-        <option name="test-command-line" key="com.android.trusty.smc.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.smc.test"/>
-        <option name="test-command-line" key="com.android.uirq-unittest" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.uirq-unittest"/>
-        <!-- Unit tests for legacy hwcrypto services - these hwcrypto services are used by hwcryptohal /-->
-        <option name="test-command-line" key="com.android.trusty.hwcrypto.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.hwcrypto.test"/>
-        <option name="test-command-line" key="com.android.trusty.hwrng.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.hwrng.test"/>
-        <!-- Unit tests for hwcryptohal (exposing IHWCryptoKey/IHWCryptoOperations AIDL) - Note: VTS tests are defined alongside the interface /-->
-        <option name="test-command-line" key="com.android.trusty.rust.hwcryptohalserver.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwcryptohalserver.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.hwcryptohal_common.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwcryptohal_common.test"/>
         <option name="test-command-line" key="com.android.trusty.rust.hwcryptokey_test.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.hwcryptokey_test.test"/>
-    </test>
-    <!-- disabling storage test as they are redundant with the VTS -->
-    <!--test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
-        <option name="parse-gtest" value="true" />
-        <option name="abort-if-device-lost" value="true" />
-        <option name="abort-if-root-lost" value="true" />
-        <option name="per-binary-timeout" value="40m" />
         <option name="test-command-line" key="com.android.trusty.rust.storage_unittest_aidl.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.storage_unittest_aidl.test"/>
-        <option name="test-command-line" key="com.android.trusty.rust.storage_unittest_aidl_ns.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.storage_unittest_aidl_ns.test"/>
-        <option name="test-command-line" key="com.android.storage-unittest.tp" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.storage-unittest.tp"/>
-        <option name="test-command-line" key="com.android.storage-unittest.tdea" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.storage-unittest.tdea"/>
-        <option name="test-command-line" key="com.android.storage-unittest.nsp" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.storage-unittest.nsp"/>
-        <option name="test-command-line" key="com.android.storage-unittest.td" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.storage-unittest.td"/>
-        <option name="test-command-line" key="com.android.storage-unittest.tdp" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.storage-unittest.tdp"/>
-    </test-->
-    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
-        <option name="parse-gtest" value="true" />
-        <!--option name="abort-if-device-lost" value="true" /-->
-        <!--option name="abort-if-root-lost" value="true" /-->
-        <option name="per-binary-timeout" value="40m" />
-        <option name="test-command-line" key="com.android.trusty.rust.binder_rpc_test.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.rust.binder_rpc_test.test"/>
-        <option name="test-command-line" key="com.android.trusty.binder.test" value="/data/local/tmp/trusty_test_vm/trusty-ut-ctrl.sh com.android.trusty.binder.test"/>
     </test>
     </configuration>
diff --git a/guest/trusty/test_vm/README.md b/guest/trusty/test_vm/README.md
index 1673844..71368b5 100644
--- a/guest/trusty/test_vm/README.md
+++ b/guest/trusty/test_vm/README.md
@@ -1,7 +1,13 @@
-## Trusty test_vm
+## test_vm
 
-The Trusty test_vm ought to include the test TAs for different test types:
-- Trusty kernel OS test
-- Trusty IPC tests
-- Trusty user-space tests for service TAs (DT tree for example)
-- and most importantly the VTS tests TA for the trusted HALs.
+The Trusty test_vm ought to include the test TAs for the Trusted HALs,
+defined under hardware/interfaces/security/see:
+
+- AuthMgr
+- Secure Storage
+- HWCrypto
+- HDCP
+
+The Trusty test_vm also includes the VINTF test which allows to check the vendor
+support of the Trusted HALs (version and API hash), against the expected
+compatibility matrix for a given Android Dessert Release.
diff --git a/guest/trusty/test_vm/trusty-test_vm-config-arm64.json b/guest/trusty/test_vm/trusty-test_vm-config-arm64.json
index 18b275e..ac95aab 100644
--- a/guest/trusty/test_vm/trusty-test_vm-config-arm64.json
+++ b/guest/trusty/test_vm/trusty-test_vm-config-arm64.json
@@ -1,7 +1,8 @@
 {
     "name": "trusty_test_vm",
-    "kernel": "/data/local/tmp/trusty_test_vm/trusty_test_vm_.elf",
+    "kernel": "/data/local/tmp/trusty_test_vm/trusty_test_vm.elf",
     "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
     "memory_mib": 112,
     "protected": true
 }
diff --git a/guest/trusty/test_vm/trusty-test_vm-config-x86_64.json b/guest/trusty/test_vm/trusty-test_vm-config-x86_64.json
index d491c3a..5ce65ba 100644
--- a/guest/trusty/test_vm/trusty-test_vm-config-x86_64.json
+++ b/guest/trusty/test_vm/trusty-test_vm-config-x86_64.json
@@ -2,5 +2,6 @@
     "name": "trusty_test_vm",
     "kernel": "/data/local/tmp/trusty_test_vm/trusty_test_vm.elf",
     "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
     "memory_mib": 112
 }
diff --git a/guest/trusty/test_vm/trusty-ut-ctrl.sh b/guest/trusty/test_vm/trusty-ut-ctrl.sh
index 77a9459..2317496 100644
--- a/guest/trusty/test_vm/trusty-ut-ctrl.sh
+++ b/guest/trusty/test_vm/trusty-ut-ctrl.sh
@@ -14,4 +14,4 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-/system_ext/bin/trusty-ut-ctrl.system -D VSOCK:${2:-$(getprop trusty.test_vm.vm_cid)}:1 $1
+/data/local/tmp/trusty_test_vm/trusty-ut-ctrl -D VSOCK:${2:-$(getprop trusty.test_vm.vm_cid)}:1 $1
diff --git a/guest/trusty/test_vm/vm/Android.bp b/guest/trusty/test_vm/vm/Android.bp
index 4f696b1..f978c92 100644
--- a/guest/trusty/test_vm/vm/Android.bp
+++ b/guest/trusty/test_vm/vm/Android.bp
@@ -2,11 +2,6 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-soong_config_module_type_import {
-    from: "packages/modules/Virtualization/guest/trusty/common/Android.bp",
-    module_types: ["trusty_vm_avb_add_hash_footer"],
-}
-
 prebuilt_etc {
     name: "trusty_test_vm_elf",
     system_ext_specific: true,
@@ -94,7 +89,7 @@
 
 TRUSTY_TEST_VM_VERSION = 1
 
-trusty_vm_avb_add_hash_footer {
+avb_add_hash_footer {
     name: "trusty_test_vm_signed_bin",
     filename: "trusty_test_vm_signed.bin",
     partition_name: "boot",
diff --git a/guest/trusty/test_vm_os/Android.bp b/guest/trusty/test_vm_os/Android.bp
new file mode 100644
index 0000000..ab0d5d8
--- /dev/null
+++ b/guest/trusty/test_vm_os/Android.bp
@@ -0,0 +1,63 @@
+// Copyright (C) 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+    default_team: "trendy_team_trusty",
+}
+
+prebuilt_etc {
+    name: "test_vm_os.trusty_test_vm_config",
+    enabled: false,
+    installable: false,
+    arch: {
+        arm64: {
+            src: "trusty-test_vm-config-arm64.json",
+            enabled: true,
+        },
+        x86_64: {
+            src: "trusty-test_vm-config-x86_64.json",
+            enabled: true,
+        },
+    },
+    filename: "trusty-test_vm-config.json",
+}
+
+sh_test {
+    name: "TrustyVmUnitTests",
+    src: "trusty-ut-ctrl.sh",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+    filename_from_src: true,
+    data: [
+        ":trusty_test_vm_os_elf",
+        ":test_vm_os.trusty_test_vm_config",
+        "trusty-vm-launcher.sh",
+        "trusty-wait-ready.sh",
+        ":trusty-ut-ctrl.system",
+    ],
+    // TODO(b/378367793) use the AndroidTest.xml generated from the trusty
+    // test-map for test_vm payload
+    test_config_template: "AndroidTest.xml",
+    test_suites: [
+        "general-tests",
+    ],
+}
diff --git a/guest/trusty/test_vm_os/AndroidTest.xml b/guest/trusty/test_vm_os/AndroidTest.xml
new file mode 100644
index 0000000..be5c467
--- /dev/null
+++ b/guest/trusty/test_vm_os/AndroidTest.xml
@@ -0,0 +1,82 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2025 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+    <configuration description="Runs {MODULE}">
+    <!-- object type="module_controller" class="com.android.tradefed.testtype.suite.module.CommandSuccessModuleController" -->
+        <!--Skip the test when trusty VM is not enabled. -->
+        <!--option name="run-command" value="getprop trusty.test_vm.nonsecure_vm_ready | grep 1" /-->
+    <!--/object-->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+    <!-- Target Preparers - Run Shell Commands -->
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="cleanup" value="true" />
+        <option name="push-file" key="trusty-ut-ctrl.system" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl" />
+        <option name="push-file" key="trusty-ut-ctrl.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh" />
+        <option name="push-file" key="trusty-vm-launcher.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-vm-launcher.sh" />
+        <option name="push-file" key="trusty-wait-ready.sh" value="/data/local/tmp/trusty_test_vm_os/trusty-wait-ready.sh" />
+        <option name="push-file" key="trusty-test_vm-config.json" value="/data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json" />
+        <option name="push-file" key="trusty_test_vm_os.elf" value="/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf" />
+    </target_preparer>
+    <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+        <option name="throw-if-cmd-fail" value="true" />
+        <!--Note: the first run-command shall not expect the background command to have started -->
+        <option name="run-bg-command" value="sh /data/local/tmp/trusty_test_vm_os/trusty-vm-launcher.sh" />
+        <option name="run-command" value="sh /data/local/tmp/trusty_test_vm_os/trusty-wait-ready.sh" />
+        <option name="run-command" value="start storageproxyd_test_vm_os" />
+        <option name="teardown-command" value="stop storageproxyd_test_vm_os" />
+        <option name="teardown-command" value="killall storageproxyd_test_vm_os || true" />
+    </target_preparer>
+    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
+        <option name="parse-gtest" value="true" />
+        <option name="abort-if-device-lost" value="true"/>
+        <option name="abort-if-root-lost" value="true" />
+        <option name="per-binary-timeout" value="10m" />
+        <option name="test-command-line" key="com.android.kernel.mmutest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.mmutest"/>
+        <option name="test-command-line" key="com.android.kernel.threadtest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.threadtest"/>
+        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
+        <!--TODO(b/400064847) enable kernel.timertest when Trusty VM supports more than 2 VCPU"/-->
+        <!--option name="test-command-line" key="com.android.kernel.timertest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.timertest"/-->
+        <option name="test-command-line" key="com.android.kernel.btitest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.btitest"/>
+        <option name="test-command-line" key="com.android.kernel.cachetest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.cachetest"/>
+        <option name="test-command-line" key="com.android.kernel.console-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.console-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.dpc-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.dpc-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.iovectest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.iovectest"/>
+        <option name="test-command-line" key="com.android.kernel.ktipc.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.ktipc.test"/>
+        <option name="test-command-line" key="com.android.kernel.memorytest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.memorytest"/>
+        <option name="test-command-line" key="com.android.kernel.pactest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.pactest"/>
+        <option name="test-command-line" key="com.android.kernel.uirq-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.uirq-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.usercopy-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.usercopy-unittest"/>
+        <option name="test-command-line" key="com.android.kernel.userscstest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.kernel.userscstest"/>
+        <option name="test-command-line" key="com.android.manifesttest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.manifesttest"/>
+        <option name="test-command-line" key="com.android.memref.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.memref.test"/>
+        <option name="test-command-line" key="com.android.trusty.rust.memref.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.memref.test"/>
+        <option name="test-command-line" key="com.android.timer-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.timer-unittest"/>
+        <option name="test-command-line" key="com.android.ipc-unittest.ctrl" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.ipc-unittest.ctrl"/>
+        <!--option name="test-command-line" key="com.android.trusty.cfitest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.cfitest"/-->
+        <option name="test-command-line" key="com.android.trusty.crashtest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.crashtest"/>
+        <option name="test-command-line" key="com.android.trusty.dlmalloctest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.dlmalloctest"/>
+        <option name="test-command-line" key="com.android.trusty.rust.tipc.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.tipc.test"/>
+        <option name="test-command-line" key="com.android.uirq-unittest" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.uirq-unittest"/>
+    </test>
+    <test class="com.android.tradefed.testtype.binary.ExecutableTargetTest" >
+        <option name="parse-gtest" value="true" />
+        <!--option name="abort-if-device-lost" value="true" /-->
+        <!--option name="abort-if-root-lost" value="true" /-->
+        <option name="per-binary-timeout" value="40m" />
+        <option name="test-command-line" key="com.android.trusty.rust.binder_rpc_test.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.rust.binder_rpc_test.test"/>
+        <option name="test-command-line" key="com.android.trusty.binder.test" value="/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl.sh com.android.trusty.binder.test"/>
+    </test>
+    </configuration>
diff --git a/guest/trusty/test_vm_os/README.md b/guest/trusty/test_vm_os/README.md
new file mode 100644
index 0000000..4d65d9f
--- /dev/null
+++ b/guest/trusty/test_vm_os/README.md
@@ -0,0 +1,7 @@
+## test_vm_os
+
+The Trusty test_vm_os is meant to test the Trusty OS as a VM,
+its payload ought to include the test TAs for different test types:
+- Trusty kernel OS test
+- Trusty/Binder IPC tests
+- Trusty user-space tests for service TAs (DT tree for example)
diff --git a/guest/trusty/test_vm_os/TEST_MAPPING b/guest/trusty/test_vm_os/TEST_MAPPING
new file mode 100644
index 0000000..1506720
--- /dev/null
+++ b/guest/trusty/test_vm_os/TEST_MAPPING
@@ -0,0 +1,9 @@
+{
+  "trusty_test_vm_presubmit": [
+  ],
+  "trusty_test_vm_postsubmit": [
+    {
+        "name": "TrustyVMOS_UnitTests"
+    }
+  ]
+}
diff --git a/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json b/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json
new file mode 100644
index 0000000..9d60892
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-test_vm-config-arm64.json
@@ -0,0 +1,8 @@
+{
+    "name": "trusty_test_vm",
+    "kernel": "/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf",
+    "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
+    "memory_mib": 112,
+    "protected": true
+}
diff --git a/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json b/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json
new file mode 100644
index 0000000..5270ac7
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-test_vm-config-x86_64.json
@@ -0,0 +1,7 @@
+{
+    "name": "trusty_test_vm",
+    "kernel": "/data/local/tmp/trusty_test_vm_os/trusty_test_vm_os.elf",
+    "platform_version": "1.0",
+    "cpu_topology": "one_cpu",
+    "memory_mib": 112
+}
diff --git a/guest/trusty/test_vm_os/trusty-ut-ctrl.sh b/guest/trusty/test_vm_os/trusty-ut-ctrl.sh
new file mode 100644
index 0000000..860236b
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-ut-ctrl.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+#
+# Copyright (C) 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+/data/local/tmp/trusty_test_vm_os/trusty-ut-ctrl -D VSOCK:${2:-$(getprop trusty.test_vm_os.vm_cid)}:1 $1
diff --git a/guest/trusty/test_vm_os/trusty-vm-launcher.sh b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
new file mode 100755
index 0000000..497b188
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-vm-launcher.sh
@@ -0,0 +1,17 @@
+#!/bin/sh
+
+# Copyright 2024 Google Inc. All rights reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+/apex/com.android.virt/bin/vm run /data/local/tmp/trusty_test_vm_os/trusty-test_vm-config.json
diff --git a/guest/trusty/test_vm_os/trusty-wait-ready.sh b/guest/trusty/test_vm_os/trusty-wait-ready.sh
new file mode 100755
index 0000000..0aed284
--- /dev/null
+++ b/guest/trusty/test_vm_os/trusty-wait-ready.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+set -euo pipefail
+
+function get_cid {
+    local max_cid
+    max_cid=$(/apex/com.android.virt/bin/vm list | awk 'BEGIN { FS="[:,]" } /cid/ { print $2; }' | sort -n | tail -1)
+
+    # return the value trimmed from whitespaces
+    echo "${max_cid}" | xargs
+}
+
+function wait_for_cid {
+    TIMES=${1:-20}
+    X=0
+    local init_cid
+    init_cid=$(get_cid)
+    while [ "$TIMES" -eq 0 ] || [ "$TIMES" -gt "$X" ]
+    do
+      local cid
+      cid=$(get_cid)
+      echo "wait_for_cid: retry $(( X++ )) / $TIMES : init_cid=$init_cid cid=$cid";
+      if [ "$cid" -gt "$init_cid" ]
+      then
+        break
+      else
+        sleep 2
+      fi
+    done
+    setprop trusty.test_vm_os.vm_cid "$cid"
+}
+
+# This script is expected to be started before the trusty_test_vm is started
+# wait_for_cid gets the max cid and wait for it to be updated as an indication
+# that the trusty_test_vm has properly started.
+# wait_for_cid polls for the CID change at 2 seconds intervals
+# the input argument is the max number of retries (20 by default)
+wait_for_cid "$@"
+
+echo trusty.test_vm_os.vm_cid="$(getprop trusty.test_vm_os.vm_cid)"
diff --git a/guest/trusty/test_vm_os/vm/Android.bp b/guest/trusty/test_vm_os/vm/Android.bp
new file mode 100644
index 0000000..2e81828
--- /dev/null
+++ b/guest/trusty/test_vm_os/vm/Android.bp
@@ -0,0 +1,114 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+prebuilt_etc {
+    name: "trusty_test_vm_os_elf",
+    system_ext_specific: true,
+    filename: "trusty_test_vm_os.elf",
+    src: select((os(), arch()), {
+        ("android", "arm64"): ":trusty_test_vm_os_signed",
+        ("android", "x86_64"): ":trusty_test_vm_os_unsigned",
+        (default, default): ":empty_file",
+    }),
+}
+
+cc_binary {
+    name: "trusty_test_vm_os_signed",
+    srcs: [
+        ":trusty_test_vm_os_signed_bin_obj",
+    ],
+    // reuse the common trusty_vm_sections linker script
+    linker_scripts: [
+        ":trusty_vm_sections.ld",
+    ],
+    ldflags: [
+        // Prevent the `trusty_test_vm_os_signed_bin_obj` segment from being garbage collected.
+        "-Wl,--no-gc-sections",
+        // Prevent the build ID segments from being added, as it would corrupt the integrity
+        // of the original signed image.
+        "-Wl,--build-id=none",
+        // Use a standard page size of 4096, smaller than the default 16384, to avoid padding
+        // with extra bytes.
+        "-Wl,-z,max-page-size=4096",
+    ],
+    nocrt: true,
+    no_libcrt: true,
+    static_executable: true,
+    system_shared_libs: [],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    strip: {
+        none: true,
+    },
+}
+
+cc_genrule {
+    name: "test_vm_os.S",
+    enabled: false,
+    arch: {
+        arm64: {
+            srcs: [":trusty_test_vm_os_signed_bin"],
+            enabled: true,
+        },
+    },
+    out: ["test_vm_os.S"],
+    cmd: "(" +
+        "    echo '.section .vm_payload_signed.bin';" +
+        "    echo '.globl vm_payload_signed';" +
+        "    echo 'vm_payload_signed:';" +
+        "    echo '.incbin \"'$(in)'\"';" +
+        ") > $(out)",
+    visibility: ["//visibility:private"],
+}
+
+cc_object {
+    name: "trusty_test_vm_os_signed_bin_obj",
+    srcs: [
+        ":test_vm_os.S",
+    ],
+    crt: false,
+    static_libs: ["trusty_test_vm_os_signed_bin"],
+    system_shared_libs: [],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+    visibility: ["//visibility:private"],
+}
+
+// python -c "import hashlib; print(hashlib.sha256(b'trusty_test_vm_os_salt').hexdigest())"
+trusty_test_vm_os_salt = "74706b35d927b14539a73e14e6e91a2d3be5d46a12c02cf4084bcef5ffee6e4a"
+
+TRUSTY_TEST_VM_OS_VERSION = 1
+
+avb_add_hash_footer {
+    name: "trusty_test_vm_os_signed_bin",
+    filename: "trusty_test_vm_os_signed.bin",
+    partition_name: "boot",
+    private_key: ":trusty_vm_sign_key",
+    salt: trusty_test_vm_os_salt,
+    rollback_index: TRUSTY_TEST_VM_OS_VERSION,
+    props: [
+        {
+            name: "com.android.virt.cap",
+            value: "trusty_security_vm",
+        },
+    ],
+    src: ":trusty_test_vm_os_unsigned",
+    enabled: false,
+    arch: {
+        arm64: {
+            enabled: true,
+        },
+        x86_64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/libs/dice/TEST_MAPPING b/libs/dice/TEST_MAPPING
index a43d7a2..d2d89e4 100644
--- a/libs/dice/TEST_MAPPING
+++ b/libs/dice/TEST_MAPPING
@@ -12,6 +12,9 @@
       "name": "libdiced_open_dice.integration_test"
     },
     {
+      "name": "libdiced_open_dice_multialg.integration_test"
+    },
+    {
       "name": "libdiced_open_dice_nostd.integration_test"
     },
     {
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 986f496..739f245 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -63,6 +63,33 @@
     ],
 }
 
+rust_library {
+    name: "libdiced_open_dice_multialg",
+    defaults: ["libdiced_open_dice_defaults"],
+    host_supported: true,
+    vendor_available: true,
+    rustlibs: [
+        "libcoset",
+        "libopen_dice_android_bindgen_multialg",
+        "libopen_dice_cbor_bindgen_multialg",
+        "libzeroize",
+    ],
+    features: [
+        "std",
+        "multialg",
+    ],
+    shared_libs: [
+        "libcrypto",
+    ],
+    visibility: [
+        "//system/software_defined_vehicle:__subpackages__",
+    ],
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.virt",
+    ],
+}
+
 rust_defaults {
     name: "libdiced_open_dice_test_defaults",
     crate_name: "diced_open_dice_test",
@@ -80,6 +107,18 @@
 }
 
 rust_test {
+    name: "libdiced_open_dice_multialg.integration_test",
+    defaults: ["libdiced_open_dice_test_defaults"],
+    rustlibs: [
+        "libdiced_open_dice_multialg",
+        "libcoset",
+    ],
+    features: [
+        "multialg",
+    ],
+}
+
+rust_test {
     name: "libdiced_open_dice_nostd.integration_test",
     defaults: ["libdiced_open_dice_test_defaults"],
     rustlibs: [
@@ -174,6 +213,20 @@
 }
 
 rust_bindgen {
+    name: "libopen_dice_cbor_bindgen_multialg",
+    defaults: [
+        "libopen_dice.rust_defaults",
+        "libopen_dice_cbor_bindgen.rust_defaults",
+    ],
+    bindgen_flags: [
+        "--rustified-enum DiceKeyAlgorithm",
+        "--allowlist-type=DiceContext",
+    ],
+    whole_static_libs: ["libopen_dice_cbor_multialg"],
+    shared_libs: ["libcrypto"],
+}
+
+rust_bindgen {
     name: "libopen_dice_cbor_bindgen_nostd",
     defaults: [
         "libopen_dice_cbor_bindgen.rust_defaults",
@@ -231,6 +284,18 @@
 }
 
 rust_bindgen {
+    name: "libopen_dice_android_bindgen_multialg",
+    defaults: [
+        "libopen_dice.rust_defaults",
+        "libopen_dice_android_bindgen.rust_defaults",
+    ],
+    rustlibs: [
+        "libopen_dice_cbor_bindgen_multialg",
+    ],
+    whole_static_libs: ["libopen_dice_android_multialg"],
+}
+
+rust_bindgen {
     name: "libopen_dice_android_bindgen_nostd",
     defaults: [
         "libopen_dice_android_bindgen.rust_defaults",
diff --git a/libs/dice/open_dice/src/lib.rs b/libs/dice/open_dice/src/lib.rs
index 33fb65c..9268b03 100644
--- a/libs/dice/open_dice/src/lib.rs
+++ b/libs/dice/open_dice/src/lib.rs
@@ -43,11 +43,19 @@
 };
 pub use error::{DiceError, Result};
 pub use ops::{
-    derive_cdi_leaf_priv, generate_certificate, hash, kdf, keypair_from_seed, sign,
-    sign_cose_sign1, sign_cose_sign1_with_cdi_leaf_priv, verify,
+    derive_cdi_leaf_priv, generate_certificate, hash, kdf, keypair_from_seed, sign, verify,
+};
+#[cfg(feature = "multialg")]
+pub use ops::{
+    derive_cdi_leaf_priv_multialg, keypair_from_seed_multialg, sign_cose_sign1_multialg,
+    sign_cose_sign1_with_cdi_leaf_priv_multialg, verify_multialg,
 };
 pub use retry::{
     retry_bcc_format_config_descriptor, retry_bcc_main_flow, retry_dice_main_flow,
     retry_generate_certificate, retry_sign_cose_sign1, retry_sign_cose_sign1_with_cdi_leaf_priv,
     OwnedDiceArtifacts,
 };
+#[cfg(feature = "multialg")]
+pub use retry::{
+    retry_sign_cose_sign1_multialg, retry_sign_cose_sign1_with_cdi_leaf_priv_multialg,
+};
diff --git a/libs/dice/open_dice/src/ops.rs b/libs/dice/open_dice/src/ops.rs
index 2014118..b22b113 100644
--- a/libs/dice/open_dice/src/ops.rs
+++ b/libs/dice/open_dice/src/ops.rs
@@ -21,11 +21,17 @@
     PRIVATE_KEY_SEED_SIZE, PRIVATE_KEY_SIZE, VM_KEY_ALGORITHM,
 };
 use crate::error::{check_result, DiceError, Result};
+#[cfg(feature = "multialg")]
+use crate::KeyAlgorithm;
 use alloc::{vec, vec::Vec};
+#[cfg(feature = "multialg")]
+use open_dice_cbor_bindgen::DiceContext_;
 use open_dice_cbor_bindgen::{
     DiceCoseSignAndEncodeSign1, DiceGenerateCertificate, DiceHash, DiceKdf, DiceKeypairFromSeed,
     DicePrincipal, DiceSign, DiceVerify,
 };
+#[cfg(feature = "multialg")]
+use std::ffi::c_void;
 use std::ptr;
 
 /// Hashes the provided input using DICE's hash function `DiceHash`.
@@ -100,6 +106,44 @@
     Ok((public_key, private_key))
 }
 
+/// Deterministically generates a public and private key pair from `seed` and `key_algorithm`.
+/// Since this is deterministic, `seed` is as sensitive as a private key and can
+/// be used directly as the private key.
+#[cfg(feature = "multialg")]
+pub fn keypair_from_seed_multialg(
+    seed: &[u8; PRIVATE_KEY_SEED_SIZE],
+    key_algorithm: KeyAlgorithm,
+) -> Result<(Vec<u8>, PrivateKey)> {
+    let mut public_key = vec![0u8; key_algorithm.public_key_size()];
+    let mut private_key = PrivateKey::default();
+    // This function is used with an open-dice config that uses the same algorithms for the
+    // subject and authority. Therefore, the principal is irrelevant in this context as this
+    // function only derives the key pair cryptographically without caring about which
+    // principal it is for. Hence, we arbitrarily set it to `DicePrincipal::kDicePrincipalSubject`.
+    let principal = DicePrincipal::kDicePrincipalSubject;
+    let context = DiceContext_ {
+        authority_algorithm: key_algorithm.into(),
+        subject_algorithm: key_algorithm.into(),
+    };
+    check_result(
+        // SAFETY: The function writes to the `public_key` and `private_key` within the given
+        // bounds, and only reads the `seed`.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+        // open-dice.
+        unsafe {
+            DiceKeypairFromSeed(
+                &context as *const DiceContext_ as *mut c_void,
+                principal,
+                seed.as_ptr(),
+                public_key.as_mut_ptr(),
+                private_key.as_mut_ptr(),
+            )
+        },
+        public_key.len(),
+    )?;
+    Ok((public_key, private_key))
+}
+
 /// Derives the CDI_Leaf_Priv from the provided `dice_artifacts`.
 ///
 /// The corresponding public key is included in the leaf certificate of the DICE chain
@@ -114,6 +158,17 @@
     Ok(private_key)
 }
 
+/// Multialg variant of `derive_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn derive_cdi_leaf_priv_multialg(
+    dice_artifacts: &dyn DiceArtifacts,
+    key_algorithm: KeyAlgorithm,
+) -> Result<PrivateKey> {
+    let cdi_priv_key_seed = derive_cdi_private_key_seed(dice_artifacts.cdi_attest())?;
+    let (_, private_key) = keypair_from_seed_multialg(cdi_priv_key_seed.as_array(), key_algorithm)?;
+    Ok(private_key)
+}
+
 /// Signs the `message` with the given `private_key` using `DiceSign`.
 pub fn sign(message: &[u8], private_key: &[u8; PRIVATE_KEY_SIZE]) -> Result<Vec<u8>> {
     let mut signature = vec![0u8; VM_KEY_ALGORITHM.signature_size()];
@@ -173,6 +228,45 @@
     Ok(encoded_signature_actual_size)
 }
 
+/// Multialg variant of `sign_cose_sign1`.
+#[cfg(feature = "multialg")]
+pub fn sign_cose_sign1_multialg(
+    message: &[u8],
+    aad: &[u8],
+    private_key: &[u8; PRIVATE_KEY_SIZE],
+    encoded_signature: &mut [u8],
+    key_algorithm: KeyAlgorithm,
+) -> Result<usize> {
+    let mut encoded_signature_actual_size = 0;
+    let context = DiceContext_ {
+        authority_algorithm: key_algorithm.into(),
+        subject_algorithm: key_algorithm.into(),
+    };
+    check_result(
+        // SAFETY: The function writes to `encoded_signature` and `encoded_signature_actual_size`
+        // within the given bounds. It only reads `message`, `aad`, and `private_key` within their
+        // given bounds.
+        //
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+        // open-dice.
+        unsafe {
+            DiceCoseSignAndEncodeSign1(
+                &context as *const DiceContext_ as *mut c_void,
+                message.as_ptr(),
+                message.len(),
+                aad.as_ptr(),
+                aad.len(),
+                private_key.as_ptr(),
+                encoded_signature.len(),
+                encoded_signature.as_mut_ptr(),
+                &mut encoded_signature_actual_size,
+            )
+        },
+        encoded_signature_actual_size,
+    )?;
+    Ok(encoded_signature_actual_size)
+}
+
 /// Signs the `message` with a private key derived from the given `dice_artifacts`
 /// CDI Attest. On success, places a `CoseSign1` encoded object in `encoded_signature`.
 /// Uses `DiceCoseSignAndEncodeSign1`.
@@ -188,6 +282,19 @@
     sign_cose_sign1(message, aad, private_key.as_array(), encoded_signature)
 }
 
+/// Multialg variant of `sign_cose_sign1_with_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn sign_cose_sign1_with_cdi_leaf_priv_multialg(
+    message: &[u8],
+    aad: &[u8],
+    dice_artifacts: &dyn DiceArtifacts,
+    encoded_signature: &mut [u8],
+    key_algorithm: KeyAlgorithm,
+) -> Result<usize> {
+    let private_key = derive_cdi_leaf_priv_multialg(dice_artifacts, key_algorithm)?;
+    sign_cose_sign1_multialg(message, aad, private_key.as_array(), encoded_signature, key_algorithm)
+}
+
 /// Verifies the `signature` of the `message` with the given `public_key` using `DiceVerify`.
 pub fn verify(message: &[u8], signature: &[u8], public_key: &[u8]) -> Result<()> {
     if signature.len() != VM_KEY_ALGORITHM.signature_size()
@@ -212,6 +319,40 @@
     )
 }
 
+/// Multialg variant of `verify`.
+#[cfg(feature = "multialg")]
+pub fn verify_multialg(
+    message: &[u8],
+    signature: &[u8],
+    public_key: &[u8],
+    key_algorithm: KeyAlgorithm,
+) -> Result<()> {
+    if signature.len() != key_algorithm.signature_size()
+        || public_key.len() != key_algorithm.public_key_size()
+    {
+        return Err(DiceError::InvalidInput);
+    }
+    let context = DiceContext_ {
+        authority_algorithm: key_algorithm.into(),
+        subject_algorithm: key_algorithm.into(),
+    };
+    check_result(
+        // SAFETY: only reads the messages, signature and public key as constant values.
+        // The first argument is a pointer to a valid |DiceContext_| object for multi-alg
+        // open-dice.
+        unsafe {
+            DiceVerify(
+                &context as *const DiceContext_ as *mut c_void,
+                message.as_ptr(),
+                message.len(),
+                signature.as_ptr(),
+                public_key.as_ptr(),
+            )
+        },
+        0,
+    )
+}
+
 /// Generates an X.509 certificate from the given `subject_private_key_seed` and
 /// `input_values`, and signed by `authority_private_key_seed`.
 /// The subject private key seed is supplied here so the implementation can choose
diff --git a/libs/dice/open_dice/src/retry.rs b/libs/dice/open_dice/src/retry.rs
index cf36bc0..d793218 100644
--- a/libs/dice/open_dice/src/retry.rs
+++ b/libs/dice/open_dice/src/retry.rs
@@ -24,6 +24,11 @@
 };
 use crate::error::{DiceError, Result};
 use crate::ops::{generate_certificate, sign_cose_sign1, sign_cose_sign1_with_cdi_leaf_priv};
+#[cfg(feature = "multialg")]
+use crate::{
+    ops::{sign_cose_sign1_multialg, sign_cose_sign1_with_cdi_leaf_priv_multialg},
+    KeyAlgorithm,
+};
 use alloc::vec::Vec;
 #[cfg(feature = "serde_derive")]
 use serde_derive::{Deserialize, Serialize};
@@ -158,6 +163,19 @@
     })
 }
 
+/// Multialg variant of `retry_sign_cose_sign1`.
+#[cfg(feature = "multialg")]
+pub fn retry_sign_cose_sign1_multialg(
+    message: &[u8],
+    aad: &[u8],
+    private_key: &[u8; PRIVATE_KEY_SIZE],
+    key_algorithm: KeyAlgorithm,
+) -> Result<Vec<u8>> {
+    retry_with_measured_buffer(|encoded_signature| {
+        sign_cose_sign1_multialg(message, aad, private_key, encoded_signature, key_algorithm)
+    })
+}
+
 /// Signs a message with the given the private key derived from the
 /// CDI Attest of the given `dice_artifacts` and returns the signature
 /// as an encoded CoseSign1 object.
@@ -170,3 +188,22 @@
         sign_cose_sign1_with_cdi_leaf_priv(message, aad, dice_artifacts, encoded_signature)
     })
 }
+
+/// Multialg variant of `retry_sign_cose_sign1_with_cdi_leaf_priv`.
+#[cfg(feature = "multialg")]
+pub fn retry_sign_cose_sign1_with_cdi_leaf_priv_multialg(
+    message: &[u8],
+    aad: &[u8],
+    dice_artifacts: &dyn DiceArtifacts,
+    key_algorithm: KeyAlgorithm,
+) -> Result<Vec<u8>> {
+    retry_with_measured_buffer(|encoded_signature| {
+        sign_cose_sign1_with_cdi_leaf_priv_multialg(
+            message,
+            aad,
+            dice_artifacts,
+            encoded_signature,
+            key_algorithm,
+        )
+    })
+}
diff --git a/libs/dice/open_dice/tests/api_test.rs b/libs/dice/open_dice/tests/api_test.rs
index b0c2ca7..0f8af10 100644
--- a/libs/dice/open_dice/tests/api_test.rs
+++ b/libs/dice/open_dice/tests/api_test.rs
@@ -21,6 +21,11 @@
         retry_sign_cose_sign1, retry_sign_cose_sign1_with_cdi_leaf_priv, sign, verify,
         DiceArtifacts, PrivateKey, CDI_SIZE, HASH_SIZE, ID_SIZE, PRIVATE_KEY_SEED_SIZE,
     };
+    #[cfg(feature = "multialg")]
+    use diced_open_dice::{
+        keypair_from_seed_multialg, retry_sign_cose_sign1_multialg,
+        retry_sign_cose_sign1_with_cdi_leaf_priv_multialg, verify_multialg, KeyAlgorithm,
+    };
 
     use coset::{CborSerializable, CoseSign1};
 
@@ -93,7 +98,21 @@
         0xfc, 0x23, 0xc9, 0x21, 0x5c, 0x48, 0x21, 0x47, 0xee, 0x5b, 0xfa, 0xaf, 0x88, 0x9a, 0x52,
         0xf1, 0x61, 0x06, 0x37,
     ];
-
+    #[cfg(feature = "multialg")]
+    const EXPECTED_EC_P256_PUB_KEY: &[u8] = &[
+        0xa7, 0x93, 0x70, 0x16, 0xff, 0xe8, 0x3c, 0x23, 0x5f, 0x6b, 0xf9, 0x38, 0x7e, 0x9c, 0xe5,
+        0x21, 0xb5, 0x8a, 0x9b, 0x68, 0x5a, 0x2f, 0x62, 0xf4, 0x15, 0x94, 0x1c, 0x61, 0xb3, 0xbb,
+        0xe1, 0x26, 0x61, 0x47, 0x97, 0xbf, 0x3a, 0x1f, 0x6b, 0x87, 0x86, 0x47, 0x5e, 0xc3, 0xa6,
+        0x8b, 0x95, 0x89, 0x9e, 0x29, 0xd5, 0x55, 0x2a, 0xdd, 0x2a, 0x3f, 0xe5, 0xf0, 0x7a, 0xd6,
+        0xc4, 0x7b, 0x64, 0xe0,
+    ];
+    #[cfg(feature = "multialg")]
+    const EXPECTED_EC_P256_PRIV_KEY: &[u8] = &[
+        0x62, 0x32, 0x1b, 0xb, 0x5c, 0xac, 0x8f, 0x20, 0x61, 0xb7, 0xa3, 0xbb, 0x46, 0x2b, 0x4e,
+        0xb3, 0x3f, 0xa7, 0xf6, 0x9b, 0x2f, 0x5b, 0x80, 0xa8, 0x55, 0x5e, 0x80, 0x26, 0xbb, 0x72,
+        0xbe, 0xe7, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+        0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
+    ];
     const EXPECTED_SIGNATURE: &[u8] = &[
         0x44, 0xae, 0xcc, 0xe2, 0xb9, 0x96, 0x18, 0x39, 0x0e, 0x61, 0x0f, 0x53, 0x07, 0xbf, 0xf2,
         0x32, 0x3d, 0x44, 0xd4, 0xf2, 0x07, 0x23, 0x30, 0x85, 0x32, 0x18, 0xd2, 0x69, 0xb8, 0x29,
@@ -140,6 +159,41 @@
         assert!(verify_result.is_err());
     }
 
+    #[cfg(feature = "multialg")]
+    #[test]
+    fn sign_cose_sign1_verify_multialg() {
+        let (pub_key, priv_key) = get_test_key_pair_ec_p256();
+
+        let signature_res = retry_sign_cose_sign1_multialg(
+            b"MyMessage",
+            b"MyAad",
+            priv_key.as_array(),
+            KeyAlgorithm::EcdsaP256,
+        );
+        assert!(signature_res.is_ok());
+        let signature = signature_res.unwrap();
+        let cose_sign1_res = CoseSign1::from_slice(&signature);
+        assert!(cose_sign1_res.is_ok());
+        let mut cose_sign1 = cose_sign1_res.unwrap();
+
+        let mut verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+            verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_ok());
+
+        verify_result = cose_sign1.verify_signature(b"BadAad", |sign, data| {
+            verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_err());
+
+        // if we modify the signature, the payload should no longer verify
+        cose_sign1.signature.push(0xAA);
+        verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+            verify_multialg(data, sign, &pub_key, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_err());
+    }
+
     struct TestArtifactsForSigning {}
 
     impl DiceArtifacts for TestArtifactsForSigning {
@@ -182,6 +236,41 @@
         assert!(verify_result.is_err());
     }
 
+    #[cfg(feature = "multialg")]
+    #[test]
+    fn sign_cose_sign1_with_cdi_leaf_priv_verify_multialg() {
+        let dice = TestArtifactsForSigning {};
+
+        let signature_res = retry_sign_cose_sign1_with_cdi_leaf_priv_multialg(
+            b"MyMessage",
+            b"MyAad",
+            &dice,
+            KeyAlgorithm::EcdsaP256,
+        );
+        assert!(signature_res.is_ok());
+        let signature = signature_res.unwrap();
+        let cose_sign1_res = CoseSign1::from_slice(&signature);
+        assert!(cose_sign1_res.is_ok());
+        let mut cose_sign1 = cose_sign1_res.unwrap();
+
+        let mut verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+            verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_ok());
+
+        verify_result = cose_sign1.verify_signature(b"BadAad", |sign, data| {
+            verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_err());
+
+        // if we modify the signature, the payload should no longer verify
+        cose_sign1.signature.push(0xAA);
+        verify_result = cose_sign1.verify_signature(b"MyAad", |sign, data| {
+            verify_multialg(data, sign, EXPECTED_EC_P256_PUB_KEY, KeyAlgorithm::EcdsaP256)
+        });
+        assert!(verify_result.is_err());
+    }
+
     fn get_test_key_pair() -> (Vec<u8>, PrivateKey) {
         let seed = hash(b"MySeedString").unwrap();
         assert_eq!(seed, EXPECTED_SEED);
@@ -196,4 +285,23 @@
 
         (pub_key, priv_key)
     }
+
+    #[cfg(feature = "multialg")]
+    fn get_test_key_pair_ec_p256() -> (Vec<u8>, PrivateKey) {
+        let seed = hash(b"MySeedString").unwrap();
+        assert_eq!(seed, EXPECTED_SEED);
+        let cdi_attest = &seed[..CDI_SIZE];
+        assert_eq!(cdi_attest, EXPECTED_CDI_ATTEST);
+        let cdi_private_key_seed =
+            derive_cdi_private_key_seed(cdi_attest.try_into().unwrap()).unwrap();
+        assert_eq!(cdi_private_key_seed.as_array(), EXPECTED_CDI_PRIVATE_KEY_SEED);
+        let (pub_key, priv_key) =
+            keypair_from_seed_multialg(cdi_private_key_seed.as_array(), KeyAlgorithm::EcdsaP256)
+                .unwrap();
+
+        assert_eq!(&pub_key, EXPECTED_EC_P256_PUB_KEY);
+        assert_eq!(priv_key.as_array(), EXPECTED_EC_P256_PRIV_KEY);
+
+        (pub_key, priv_key)
+    }
 }
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
index 5fcb975..b8b4ace 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachine.java
@@ -288,17 +288,7 @@
                 percent = 50;
             }
 
-            synchronized (mLock) {
-                try {
-                    if (mVirtualMachine != null) {
-                        long bytes = mConfig.getMemoryBytes();
-                        mVirtualMachine.setMemoryBalloon(bytes * percent / 100);
-                    }
-                } catch (Exception e) {
-                    /* Caller doesn't want our exceptions. Log them instead. */
-                    Log.w(TAG, "TrimMemory failed: ", e);
-                }
-            }
+            setMemoryBalloonByPercent(percent);
         }
     }
 
@@ -1392,6 +1382,24 @@
         }
     }
 
+    /** @hide */
+    public void setMemoryBalloonByPercent(int percent) {
+        if (percent < 0 || percent > 100) {
+            Log.e(TAG, String.format("Invalid percent value: %d", percent));
+            return;
+        }
+        synchronized (mLock) {
+            try {
+                if (mVirtualMachine != null && mVirtualMachine.isMemoryBalloonEnabled()) {
+                    long bytes = mConfig.getMemoryBytes();
+                    mVirtualMachine.setMemoryBalloon(bytes * percent / 100);
+                }
+            } catch (RemoteException | ServiceSpecificException e) {
+                Log.w(TAG, "Cannot setMemoryBalloon", e);
+            }
+        }
+    }
+
     private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) {
         ByteBuffer byteBuffer =
                 ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size());
@@ -1799,7 +1807,7 @@
      * {@linkplain #connectToVsockServer binder request}, and wait for {@link
      * VirtualMachineCallback#onPayloadFinished} to be called.
      *
-     * <p>A stopped virtual machine can be re-started by calling {@link #run()}.
+     * <p>A stopped virtual machine cannot be re-started.
      *
      * <p>NOTE: This method may block and should not be called on the main thread.
      *
diff --git a/libs/libhypervisor_backends/src/hypervisor.rs b/libs/libhypervisor_backends/src/hypervisor.rs
index aa65133..7c274f5 100644
--- a/libs/libhypervisor_backends/src/hypervisor.rs
+++ b/libs/libhypervisor_backends/src/hypervisor.rs
@@ -152,3 +152,8 @@
 pub fn get_device_assigner() -> Option<&'static dyn DeviceAssigningHypervisor> {
     get_hypervisor().as_device_assigner()
 }
+
+/// Gets the unique hypervisor granule size, if any.
+pub fn get_granule_size() -> Option<usize> {
+    get_hypervisor().get_granule_size()
+}
diff --git a/libs/libhypervisor_backends/src/hypervisor/common.rs b/libs/libhypervisor_backends/src/hypervisor/common.rs
index bfe638f..f229e14 100644
--- a/libs/libhypervisor_backends/src/hypervisor/common.rs
+++ b/libs/libhypervisor_backends/src/hypervisor/common.rs
@@ -32,6 +32,13 @@
     fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
         None
     }
+
+    /// Returns the granule used by all APIs (MEM_SHARE, MMIO_GUARD, device assignment, ...).
+    ///
+    /// If no such API is supported or if they support different granule sizes, returns None.
+    fn get_granule_size(&self) -> Option<usize> {
+        None
+    }
 }
 
 pub trait MmioGuardedHypervisor {
diff --git a/libs/libhypervisor_backends/src/hypervisor/geniezone.rs b/libs/libhypervisor_backends/src/hypervisor/geniezone.rs
index 76e010b..0913ff3 100644
--- a/libs/libhypervisor_backends/src/hypervisor/geniezone.rs
+++ b/libs/libhypervisor_backends/src/hypervisor/geniezone.rs
@@ -84,6 +84,10 @@
     fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
         Some(self)
     }
+
+    fn get_granule_size(&self) -> Option<usize> {
+        <Self as MemSharingHypervisor>::granule(self).ok()
+    }
 }
 
 impl MmioGuardedHypervisor for GeniezoneHypervisor {
diff --git a/libs/libhypervisor_backends/src/hypervisor/kvm_aarch64.rs b/libs/libhypervisor_backends/src/hypervisor/kvm_aarch64.rs
index 233097b..f183107 100644
--- a/libs/libhypervisor_backends/src/hypervisor/kvm_aarch64.rs
+++ b/libs/libhypervisor_backends/src/hypervisor/kvm_aarch64.rs
@@ -90,6 +90,10 @@
     fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
         Some(self)
     }
+
+    fn get_granule_size(&self) -> Option<usize> {
+        <Self as MemSharingHypervisor>::granule(self).ok()
+    }
 }
 
 impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
diff --git a/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs b/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs
index 7f9ea4d..d72f788 100644
--- a/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs
+++ b/libs/libhypervisor_backends/src/hypervisor/kvm_x86.rs
@@ -84,6 +84,10 @@
     fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
         Some(self)
     }
+
+    fn get_granule_size(&self) -> Option<usize> {
+        <Self as MemSharingHypervisor>::granule(self).ok()
+    }
 }
 
 macro_rules! vmcall {
diff --git a/libs/libhypervisor_backends/src/lib.rs b/libs/libhypervisor_backends/src/lib.rs
index 33dc5ad..3c81ac8 100644
--- a/libs/libhypervisor_backends/src/lib.rs
+++ b/libs/libhypervisor_backends/src/lib.rs
@@ -24,5 +24,6 @@
 
 pub use error::{Error, Result};
 pub use hypervisor::{
-    get_device_assigner, get_mem_sharer, get_mmio_guard, DeviceAssigningHypervisor, KvmError,
+    get_device_assigner, get_granule_size, get_mem_sharer, get_mmio_guard,
+    DeviceAssigningHypervisor, KvmError,
 };
diff --git a/libs/libservice_vm_requests/src/client_vm.rs b/libs/libservice_vm_requests/src/client_vm.rs
index 4e54510..8ad10fd 100644
--- a/libs/libservice_vm_requests/src/client_vm.rs
+++ b/libs/libservice_vm_requests/src/client_vm.rs
@@ -25,7 +25,7 @@
 use core::result;
 use coset::{AsCborValue, CborSerializable, CoseSign, CoseSign1};
 use der::{Decode, Encode};
-use diced_open_dice::{DiceArtifacts, HASH_SIZE};
+use diced_open_dice::DiceArtifacts;
 use log::{debug, error, info};
 use microdroid_kernel_hashes::{HASH_SIZE as KERNEL_HASH_SIZE, OS_HASHES};
 use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
@@ -252,7 +252,7 @@
     Ok(false)
 }
 
-fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<[u8; HASH_SIZE]> {
+fn expected_kernel_authority_hash(service_vm_entry: &Value) -> Result<Vec<u8>> {
     let cose_sign1 = CoseSign1::from_cbor_value(service_vm_entry.clone())?;
     let payload = cose_sign1.payload.ok_or_else(|| {
         error!("No payload found in the service VM DICE chain entry");
diff --git a/libs/libservice_vm_requests/src/dice.rs b/libs/libservice_vm_requests/src/dice.rs
index ef9d894..ba67450 100644
--- a/libs/libservice_vm_requests/src/dice.rs
+++ b/libs/libservice_vm_requests/src/dice.rs
@@ -19,8 +19,8 @@
 use alloc::vec::Vec;
 use bssl_avf::{ed25519_verify, Digester, EcKey};
 use cbor_util::{
-    cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array,
-    value_to_byte_array, value_to_bytes, value_to_map, value_to_num, value_to_text,
+    cbor_value_type, get_label_value, get_label_value_as_bytes, value_to_array, value_to_bytes,
+    value_to_map, value_to_num, value_to_text,
 };
 use ciborium::value::Value;
 use core::cell::OnceCell;
@@ -31,7 +31,7 @@
     Algorithm, AsCborValue, CborSerializable, CoseError, CoseKey, CoseSign1, KeyOperation, KeyType,
     Label,
 };
-use diced_open_dice::{DiceMode, HASH_SIZE};
+use diced_open_dice::DiceMode;
 use log::{debug, error, info};
 use service_vm_comm::RequestProcessingError;
 
@@ -288,8 +288,8 @@
 pub(crate) struct DiceChainEntryPayload {
     pub(crate) subject_public_key: PublicKey,
     mode: DiceMode,
-    pub(crate) code_hash: [u8; HASH_SIZE],
-    pub(crate) authority_hash: [u8; HASH_SIZE],
+    pub(crate) code_hash: Vec<u8>,
+    pub(crate) authority_hash: Vec<u8>,
     config_descriptor: ConfigDescriptor,
 }
 
@@ -327,12 +327,12 @@
                 }
                 MODE => builder.mode(to_mode(value)?)?,
                 CODE_HASH => {
-                    let code_hash = value_to_byte_array(value, "DiceChainEntryPayload code_hash")?;
+                    let code_hash = value_to_bytes(value, "DiceChainEntryPayload code_hash")?;
                     builder.code_hash(code_hash)?;
                 }
                 AUTHORITY_HASH => {
                     let authority_hash =
-                        value_to_byte_array(value, "DiceChainEntryPayload authority_hash")?;
+                        value_to_bytes(value, "DiceChainEntryPayload authority_hash")?;
                     builder.authority_hash(authority_hash)?;
                 }
                 CONFIG_DESC => {
@@ -524,8 +524,8 @@
 struct PayloadBuilder {
     subject_public_key: OnceCell<PublicKey>,
     mode: OnceCell<DiceMode>,
-    code_hash: OnceCell<[u8; HASH_SIZE]>,
-    authority_hash: OnceCell<[u8; HASH_SIZE]>,
+    code_hash: OnceCell<Vec<u8>>,
+    authority_hash: OnceCell<Vec<u8>>,
     config_descriptor: OnceCell<ConfigDescriptor>,
 }
 
@@ -552,11 +552,11 @@
         set_once(&self.mode, mode, "mode")
     }
 
-    fn code_hash(&mut self, code_hash: [u8; HASH_SIZE]) -> Result<()> {
+    fn code_hash(&mut self, code_hash: Vec<u8>) -> Result<()> {
         set_once(&self.code_hash, code_hash, "code_hash")
     }
 
-    fn authority_hash(&mut self, authority_hash: [u8; HASH_SIZE]) -> Result<()> {
+    fn authority_hash(&mut self, authority_hash: Vec<u8>) -> Result<()> {
         set_once(&self.authority_hash, authority_hash, "authority_hash")
     }
 
@@ -570,7 +570,9 @@
         // the Open Profile for DICE spec.
         let mode = self.mode.take().unwrap_or(DiceMode::kDiceModeNotInitialized);
         let code_hash = take_value(&mut self.code_hash, "code_hash")?;
+        validate_hash_size(code_hash.len(), "code_hash")?;
         let authority_hash = take_value(&mut self.authority_hash, "authority_hash")?;
+        validate_hash_size(authority_hash.len(), "authority_hash")?;
         let config_descriptor = take_value(&mut self.config_descriptor, "config_descriptor")?;
         Ok(DiceChainEntryPayload {
             subject_public_key,
@@ -581,3 +583,18 @@
         })
     }
 }
+
+fn validate_hash_size(len: usize, name: &str) -> Result<()> {
+    // According to the Android Profile for DICE specification, SHA-256, SHA-384, and SHA-512
+    // are all acceptable hash algorithms.
+    const ACCEPTABLE_HASH_SIZES: [usize; 3] = [32, 48, 64];
+    if ACCEPTABLE_HASH_SIZES.contains(&len) {
+        Ok(())
+    } else {
+        error!(
+            "Invalid hash size for {}: {}. Acceptable hash sizes are: {:?}",
+            name, len, ACCEPTABLE_HASH_SIZES
+        );
+        Err(RequestProcessingError::InvalidDiceChain)
+    }
+}
diff --git a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
index 67a4716..8452344 100644
--- a/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/libs/libvirtualization_jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -59,28 +59,29 @@
         JNIEnv *mEnv;
         jobject mProvider;
         jmethodID mMid;
-    } state;
+    };
 
-    state.mEnv = env;
-    state.mProvider = provider;
-    state.mMid = mid;
+    auto state = std::make_unique<State>(env, provider, mid);
 
     using RequestFun = int (*)(void *);
     RequestFun requestFunc = [](void *param) -> int {
-        State *state = reinterpret_cast<State *>(param);
+        State *state = static_cast<State *>(param);
         int ownedFd = state->mEnv->CallIntMethod(state->mProvider, state->mMid);
         // FD is owned by PFD in Java layer, need to dupe it so that
         // ARpcSession_setupPreconnectedClient can take ownership when it calls unique_fd internally
         return fcntl(ownedFd, F_DUPFD_CLOEXEC, 0);
     };
 
+    auto paramDeleteFunc = [](void *param) { delete static_cast<State *>(param); };
+
     RpcSessionHandle session;
     // We need a thread pool to be able to support linkToDeath, or callbacks
     // (b/268335700). These threads are currently created eagerly, so we don't
     // want too many. The number 1 is chosen after some discussion, and to match
     // the server-side default (mMaxThreads on RpcServer).
     ARpcSession_setMaxIncomingThreads(session.get(), 1);
-    auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &state);
+    auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, state.release(),
+                                                      paramDeleteFunc);
     return AIBinder_toJavaBinder(env, client);
 }
 
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..9518c38 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()
@@ -110,6 +111,7 @@
         .truncate(true)
         .open("dump_dt.dtb")
         .with_context(|| "Failed to open device tree dump file dump_dt.dtb")?;
+    let is_updatable = service.isUpdatableVmSupported()?;
     let vm = VmInstance::create(
         service.as_ref(),
         &config,
@@ -142,7 +144,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 +165,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 is_updatable {
+        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 +216,18 @@
     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 Some(val) = rustutils::system_properties::read(prop)? else {
+        return Ok(-1);
+    };
+    val.parse::<i32>().with_context(|| format!("Failed to read {prop}"))
+}
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 59a57f1..2434ed0 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -101,7 +101,7 @@
     private static final String INSTANCE_IMG = TEST_ROOT + "instance.img";
     private static final String INSTANCE_ID_FILE = TEST_ROOT + "instance_id";
 
-    private static final String DEBUG_LEVEL_FULL = "full --enable-earlycon";
+    private static final String DEBUG_LEVEL_FULL = "full";
     private static final String DEBUG_LEVEL_NONE = "none";
 
     private static final int MIN_MEM_ARM64 = 170;
@@ -555,6 +555,7 @@
             throws Exception {
         // Preconditions
         assumeKernelSupported(os);
+        assumeVmTypeSupported(os, false);
 
         File key = findTestFile("test.com.android.virt.pem");
         Map<String, File> keyOverrides = Map.of();
@@ -582,6 +583,7 @@
     public void testBootFailsWhenVbMetaDigestDoesNotMatchBootconfig(String os) throws Exception {
         // protectedVmWithImageSignedWithDifferentKeyRunsPvmfw() is the protected case.
         assumeKernelSupported(os);
+        assumeVmTypeSupported(os, false);
 
         // Sign everything with key1 except vbmeta
         File key = findTestFile("test.com.android.virt.pem");
@@ -1155,6 +1157,8 @@
     @Test
     @CddTest
     public void testRunEmptyPayload() throws Exception {
+        assumeVmTypeSupported("microdroid", false);
+
         CommandRunner android = new CommandRunner(getDevice());
 
         // Create the idsig file for the APK
@@ -1463,7 +1467,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());
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 418a88e..9d08ed7 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -2166,11 +2166,6 @@
         assumeFalse(
                 "Cuttlefish/Goldfish doesn't support device tree under /proc/device-tree",
                 isCuttlefish() || isGoldfish());
-        if (!isUpdatableVmSupported()) {
-            // TODO(b/389611249): Non protected VMs using legacy secret mechanisms do not reliably
-            // implement `AVmPayload_isNewInstance`.
-            assumeProtectedVM();
-        }
         VirtualMachine vm = forceCreateNewVirtualMachine("test_vm_a", config);
         TestResults testResults =
                 runVmTestService(
diff --git a/tests/vts/AndroidTest.xml b/tests/vts/AndroidTest.xml
index 6926f9f..a59f161 100644
--- a/tests/vts/AndroidTest.xml
+++ b/tests/vts/AndroidTest.xml
@@ -21,7 +21,7 @@
     <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
         <option name="cleanup" value="true" />
         <option name="push" value="vts_libavf_test->/data/nativetest64/vendor/vts_libavf_test" />
-        <option name="push" value="rialto.bin->/data/local/tmp/rialto.bin" />
+        <option name="push" value="rialto.bin->/data/nativetest64/vendor/rialto.bin" />
     </target_preparer>
 
     <object type="module_controller" class="com.android.tradefed.testtype.suite.module.ArchModuleController">
diff --git a/tests/vts/src/vts_libavf_test.rs b/tests/vts/src/vts_libavf_test.rs
index dc37aad..c13b510 100644
--- a/tests/vts/src/vts_libavf_test.rs
+++ b/tests/vts/src/vts_libavf_test.rs
@@ -75,7 +75,7 @@
 
 fn run_rialto(protected_vm: bool) -> Result<()> {
     let kernel_file =
-        File::open("/data/local/tmp/rialto.bin").context("Failed to open kernel file")?;
+        File::open("/data/nativetest64/vendor/rialto.bin").context("Failed to open kernel file")?;
     let kernel_fd = kernel_file.into_raw_fd();
 
     // SAFETY: AVirtualMachineRawConfig_create() isn't unsafe but rust_bindgen forces it to be seen