Merge "Change Terminal app's image tag to SDK_INT_FULL" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
index 662fef5..35c5570 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.kt
@@ -29,7 +29,6 @@
 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
@@ -328,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,
@@ -363,7 +362,6 @@
                 )
                 .build()
 
-        Trace.beginAsyncSection("executeTerminal", 0)
         run(this, this, notification, getDisplayInfo())
     }
 
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 345e8dd..2d7468d 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
@@ -63,6 +63,7 @@
 import java.util.concurrent.CompletableFuture
 import java.util.concurrent.ExecutorService
 import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
 
 class VmLauncherService : Service() {
     inner class VmLauncherServiceBinder : android.os.Binder() {
@@ -71,8 +72,9 @@
 
     private val binder = VmLauncherServiceBinder()
 
+    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
@@ -167,8 +169,13 @@
         }
     }
 
+    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()
@@ -184,7 +191,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)
@@ -200,15 +206,12 @@
         }
         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 =
@@ -222,7 +225,7 @@
             stopSelf()
         }
         val logDir = getFileStreamPath(virtualMachine!!.name + ".log").toPath()
-        Logger.setup(virtualMachine!!, logDir, executorService!!)
+        Logger.setup(virtualMachine!!, logDir, executorService)
 
         val notification =
             intent.getParcelableExtra<Notification?>(EXTRA_NOTIFICATION, Notification::class.java)
@@ -246,6 +249,15 @@
                 },
                 executorService,
             )
+            .exceptionallyAsync(
+                { e ->
+                    Log.e(TAG, "Failed to start VM", e)
+                    resultReceiver!!.send(RESULT_ERROR, null)
+                    stopSelf()
+                    null
+                },
+                executorService,
+            )
 
         return START_NOT_STICKY
     }
@@ -282,13 +294,15 @@
                 }
             },
         )
+
+        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,
@@ -411,7 +425,7 @@
             return
         }
 
-        executorService!!.execute(
+        executorService.execute(
             Runnable {
                 // TODO(b/373533555): we can use mDNS for that.
                 val debianServicePortFile = File(filesDir, "debian_service_port")
@@ -439,10 +453,9 @@
                     Log.e(TAG, "failed to stop a VM instance", e)
                 }
             }
-            executorService?.shutdownNow()
-            executorService = null
             virtualMachine = null
         }
+        executorService.shutdownNow()
         super.onDestroy()
     }
 
@@ -456,8 +469,7 @@
         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
@@ -467,6 +479,19 @@
         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 const val INITIAL_MEM_BALLOON_PERCENT = 10
         private const val MAX_MEM_BALLOON_PERCENT = 50
         private const val MEM_BALLOON_INFLATE_INTERVAL_MILLIS = 60000L
@@ -516,7 +541,7 @@
 
         fun stop(context: Context) {
             val i = getMyIntent(context)
-            i.setAction(ACTION_STOP_VM_LAUNCHER_SERVICE)
+            i.setAction(ACTION_SHUTDOWN_VM)
             context.startService(i)
         }
     }
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/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,
 };