Merge "Remove default 6 GB size from MainActivity" into main
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index ca803ec..3448388 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -83,17 +83,17 @@
     <string name="settings_recovery_title">Recovery</string>
     <!-- Settings menu subtitle for recoverying image [CHAR LIMIT=none] -->
     <string name="settings_recovery_sub_title">Partition Recovery options</string>
-    <!-- Settings menu title for resetting the virtual machine image [CHAR LIMIT=none] -->
+    <!-- Settings menu title for resetting the terminal [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_title">Change to Initial version</string>
-    <!-- Settings menu subtitle for resetting the virtual machine image [CHAR LIMIT=none] -->
+    <!-- Settings menu subtitle for resetting the terminal [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_sub_title">Remove all</string>
-    <!-- Dialog title for restarting the terminal [CHAR LIMIT=none] -->
-    <string name="settings_recovery_reset_dialog_title">Reset the virtual machine</string>
-    <!-- Dialog message for restarting the terminal [CHAR LIMIT=none] -->
-    <string name="settings_recovery_reset_dialog_message">Data will be deleted.</string>
-    <!-- Dialog button confirm for restarting the terminal [CHAR LIMIT=16] -->
+    <!-- Dialog title for resetting the terminal [CHAR LIMIT=none] -->
+    <string name="settings_recovery_reset_dialog_title">Reset terminal</string>
+    <!-- Dialog message for resetting the terminal [CHAR LIMIT=none] -->
+    <string name="settings_recovery_reset_dialog_message">Data will be deleted</string>
+    <!-- Dialog button confirm for resetting the terminal [CHAR LIMIT=16] -->
     <string name="settings_recovery_reset_dialog_confirm">Confirm</string>
-    <!-- Dialog button cancel for restarting the terminal [CHAR LIMIT=16] -->
+    <!-- Dialog button cancel for resetting the terminal [CHAR LIMIT=16] -->
     <string name="settings_recovery_reset_dialog_cancel">Cancel</string>
 
     <!-- Notification action button for settings [CHAR LIMIT=none] -->
@@ -101,7 +101,7 @@
     <!-- Notification title for foreground service notification [CHAR LIMIT=none] -->
     <string name="service_notification_title">Terminal is running</string>
     <!-- Notification content for foreground service notification [CHAR LIMIT=none] -->
-    <string name="service_notification_content">Click to open the terminal.</string>
+    <string name="service_notification_content">Click to open the terminal</string>
     <!-- Notification action button for closing the virtual machine [CHAR LIMIT=none] -->
     <string name="service_notification_quit_action">Close</string>
 </resources>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index e2b2804..4fb8be4 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -534,7 +534,13 @@
             clone_or_prepare_logger_fd(console_out_fd, format!("Console({})", cid))?;
         let console_in_fd = console_in_fd.map(clone_file).transpose()?;
         let log_fd = clone_or_prepare_logger_fd(log_fd, format!("Log({})", cid))?;
-        let dump_dt_fd = dump_dt_fd.map(clone_file).transpose()?;
+        let dump_dt_fd = if let Some(fd) = dump_dt_fd {
+            Some(clone_file(fd)?)
+        } else if debug_config.dump_device_tree {
+            Some(prepare_dump_dt_file(&temporary_directory)?)
+        } else {
+            None
+        };
 
         // Counter to generate unique IDs for temporary image files.
         let mut next_temporary_image_id = 0;
@@ -1669,6 +1675,16 @@
     Ok(ramdump)
 }
 
+/// Create the empty device tree dump file
+fn prepare_dump_dt_file(temporary_directory: &Path) -> binder::Result<File> {
+    let path = temporary_directory.join("device_tree.dtb");
+    let file = File::create(path)
+        .context("Failed to prepare device tree dump file")
+        .with_log()
+        .or_service_specific_exception(-1)?;
+    Ok(file)
+}
+
 fn is_protected(config: &VirtualMachineConfig) -> bool {
     match config {
         VirtualMachineConfig::RawConfig(config) => config.protectedVm,
diff --git a/android/virtmgr/src/debug_config.rs b/android/virtmgr/src/debug_config.rs
index 74559de..6e2bfef 100644
--- a/android/virtmgr/src/debug_config.rs
+++ b/android/virtmgr/src/debug_config.rs
@@ -30,6 +30,7 @@
 
 const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
     "hypervisor.virtualizationmanager.debug_policy.path";
+const DUMP_DT_SYSPROP: &str = "hypervisor.virtualizationmanager.dump_device_tree";
 const DEVICE_TREE_EMPTY_TREE_SIZE_BYTES: usize = 100; // rough estimation.
 
 struct DPPath {
@@ -183,6 +184,7 @@
 #[derive(Debug, Default)]
 pub struct DebugConfig {
     pub debug_level: DebugLevel,
+    pub dump_device_tree: bool,
     debug_policy: DebugPolicy,
 }
 
@@ -193,8 +195,13 @@
             info!("Debug policy is disabled");
             Default::default()
         });
+        let dump_dt_sysprop = system_properties::read_bool(DUMP_DT_SYSPROP, false);
+        let dump_device_tree = dump_dt_sysprop.unwrap_or_else(|e| {
+            warn!("Failed to read sysprop {DUMP_DT_SYSPROP}: {e}");
+            false
+        });
 
-        Self { debug_level, debug_policy }
+        Self { debug_level, debug_policy, dump_device_tree }
     }
 
     fn get_debug_policy() -> Option<DebugPolicy> {
diff --git a/build/debian/fai_config/package_config/AVF b/build/debian/fai_config/package_config/AVF
index 1be57fe..2e55e90 100644
--- a/build/debian/fai_config/package_config/AVF
+++ b/build/debian/fai_config/package_config/AVF
@@ -1,3 +1,5 @@
 PACKAGES install
 
+bpfcc-tools
+linux-headers-generic
 procps
diff --git a/docs/debug/README.md b/docs/debug/README.md
index 4b42531..6e51efa 100644
--- a/docs/debug/README.md
+++ b/docs/debug/README.md
@@ -45,6 +45,25 @@
 Note: `--debug full` is the default option when omitted. You need to explicitly
 use `--debug none` to set the debug level to NONE.
 
+### Dump device tree
+
+The VMs device tree can be dumped on creation by adding the `--dump_device_tree`
+argument and passing a path where the device tree gets dumped to, as follows:
+
+```shell
+adb shell /apex/com.android.virt/bin/vm run-microdroid --dump-device-tree PATH
+```
+
+Note: you can set the system property
+`hypervisor.virtualizationmanager.dump_device_tree` to true to always dump the
+device tree to `/data/misc/virtualizationservice/$CID/device_tree.dtb` where
+$CID is the CID of the VM. To set the property, run:
+
+```shell
+adb root
+adb shell setprop hypervisor.virtualizationmanager.dump_device_tree true
+```
+
 ### Debug policy
 
 Debug policy is a per-device property which forcibly enables selected debugging
diff --git a/guest/forwarder_guest_launcher/Cargo.toml b/guest/forwarder_guest_launcher/Cargo.toml
index 03fda56..c875484 100644
--- a/guest/forwarder_guest_launcher/Cargo.toml
+++ b/guest/forwarder_guest_launcher/Cargo.toml
@@ -7,9 +7,13 @@
 [dependencies]
 anyhow = "1.0.91"
 clap = { version = "4.5.20", features = ["derive"] }
+csv-async = { version = "1.3.0", features = ["tokio"] }
 env_logger = "0.11.5"
+futures = "0.3.31"
+listeners = "0.2.1"
 log = "0.4.22"
 prost = "0.13.3"
+serde = { version = "1.0.215", features = ["derive"] }
 tokio = { version = "1.40.0", features = ["process", "rt-multi-thread"] }
 tonic = "0.12.3"
 vsock = "0.5.1"
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index abb39f6..0e06c66 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -14,19 +14,39 @@
 
 //! Launcher of forwarder_guest
 
-use anyhow::Context;
+use anyhow::{anyhow, Context};
 use clap::Parser;
+use csv_async::AsyncReader;
 use debian_service::debian_service_client::DebianServiceClient;
-use debian_service::QueueOpeningRequest;
-use log::debug;
+use debian_service::{QueueOpeningRequest, ReportVmActivePortsRequest};
+use futures::stream::StreamExt;
+use log::{debug, error};
+use serde::Deserialize;
+use std::collections::HashSet;
+use std::process::Stdio;
+use tokio::io::BufReader;
 use tokio::process::Command;
-use tonic::transport::Endpoint;
+use tokio::try_join;
+use tonic::transport::{Channel, Endpoint};
 use tonic::Request;
 
 mod debian_service {
     tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
 }
 
+const NON_PREVILEGED_PORT_RANGE_START: i32 = 1024;
+const TCPSTATES_IP_4: i8 = 4;
+const TCPSTATES_STATE_LISTEN: &str = "LISTEN";
+
+#[derive(Debug, Deserialize)]
+#[serde(rename_all = "UPPERCASE")]
+struct TcpStateRow {
+    ip: i8,
+    lport: i32,
+    oldstate: String,
+    newstate: String,
+}
+
 #[derive(Parser)]
 /// Flags for running command
 pub struct Args {
@@ -40,15 +60,9 @@
     grpc_port: String,
 }
 
-#[tokio::main]
-async fn main() -> Result<(), Box<dyn std::error::Error>> {
-    env_logger::init();
-    debug!("Starting forwarder_guest_launcher");
-    let args = Args::parse();
-    let addr = format!("https://{}:{}", args.host_addr, args.grpc_port);
-
-    let channel = Endpoint::from_shared(addr)?.connect().await?;
-    let mut client = DebianServiceClient::new(channel);
+async fn process_forwarding_request_queue(
+    mut client: DebianServiceClient<Channel>,
+) -> Result<(), Box<dyn std::error::Error>> {
     let cid = vsock::get_local_cid().context("Failed to get CID of VM")?;
     let mut res_stream = client
         .open_forwarding_request_queue(Request::new(QueueOpeningRequest { cid: cid as i32 }))
@@ -72,5 +86,78 @@
             .arg(format!("vsock:2:{}", vsock_port))
             .spawn();
     }
+    Err(anyhow!("process_forwarding_request_queue is terminated").into())
+}
+
+async fn send_active_ports_report(
+    listening_ports: HashSet<i32>,
+    client: &mut DebianServiceClient<Channel>,
+) -> Result<(), Box<dyn std::error::Error>> {
+    let res = client
+        .report_vm_active_ports(Request::new(ReportVmActivePortsRequest {
+            ports: listening_ports.into_iter().collect(),
+        }))
+        .await?
+        .into_inner();
+    if res.success {
+        debug!("Successfully reported active ports to the host");
+    } else {
+        error!("Failure response received from the host for reporting active ports");
+    }
+    Ok(())
+}
+
+async fn report_active_ports(
+    mut client: DebianServiceClient<Channel>,
+) -> Result<(), Box<dyn std::error::Error>> {
+    let mut cmd =
+        Command::new("/usr/sbin/tcpstates-bpfcc").arg("-s").stdout(Stdio::piped()).spawn()?;
+    let stdout = cmd.stdout.take().context("Failed to get stdout of tcpstates")?;
+    let mut csv_reader = AsyncReader::from_reader(BufReader::new(stdout));
+    let header = csv_reader.headers().await?.clone();
+
+    // TODO(b/340126051): Consider using NETLINK_SOCK_DIAG for the optimization.
+    let listeners = listeners::get_all()?;
+    // TODO(b/340126051): Support distinguished port forwarding for ipv6 as well.
+    let mut listening_ports: HashSet<_> = listeners
+        .iter()
+        .map(|x| x.socket)
+        .filter(|x| x.is_ipv4())
+        .map(|x| x.port().into())
+        .filter(|x| *x >= NON_PREVILEGED_PORT_RANGE_START) // Ignore privileged ports
+        .collect();
+    send_active_ports_report(listening_ports.clone(), &mut client).await?;
+
+    let mut records = csv_reader.records();
+    while let Some(record) = records.next().await {
+        let row: TcpStateRow = record?.deserialize(Some(&header))?;
+        if row.ip != TCPSTATES_IP_4 {
+            continue;
+        }
+        match (row.oldstate.as_str(), row.newstate.as_str()) {
+            (_, TCPSTATES_STATE_LISTEN) => {
+                listening_ports.insert(row.lport);
+            }
+            (TCPSTATES_STATE_LISTEN, _) => {
+                listening_ports.remove(&row.lport);
+            }
+            (_, _) => continue,
+        }
+        send_active_ports_report(listening_ports.clone(), &mut client).await?;
+    }
+
+    Err(anyhow!("report_active_ports is terminated").into())
+}
+
+#[tokio::main]
+async fn main() -> Result<(), Box<dyn std::error::Error>> {
+    env_logger::init();
+    debug!("Starting forwarder_guest_launcher");
+    let args = Args::parse();
+    let addr = format!("https://{}:{}", args.host_addr, args.grpc_port);
+    let channel = Endpoint::from_shared(addr)?.connect().await?;
+    let client = DebianServiceClient::new(channel);
+
+    try_join!(process_forwarding_request_queue(client.clone()), report_active_ports(client))?;
     Ok(())
 }
diff --git a/guest/trusty/security_vm/launcher/src/main.rs b/guest/trusty/security_vm/launcher/src/main.rs
index 4298181..9611f26 100644
--- a/guest/trusty/security_vm/launcher/src/main.rs
+++ b/guest/trusty/security_vm/launcher/src/main.rs
@@ -44,8 +44,8 @@
     #[arg(long, default_value_t = 128)]
     memory_size_mib: i32,
 
-    /// CPU Topology exposed to the VM <one-cpu|match_host>
-    #[arg(long, default_value = "one_cpu", value_parser = parse_cpu_topology)]
+    /// CPU Topology exposed to the VM <one-cpu|match-host>
+    #[arg(long, default_value = "one-cpu", value_parser = parse_cpu_topology)]
     cpu_topology: CpuTopology,
 }
 
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index adab521..03d7fef 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -1345,9 +1345,8 @@
     }
 
     @Test
-    @Parameters(method = "gkiVersions")
+    @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @Ignore("b/360388014") // TODO(b/360388014): fix & re-enable
     public void microdroidDeviceTreeCompat(String os) throws Exception {
         assumeArm64Supported();
         final String configPath = "assets/vm_config.json";
@@ -1374,9 +1373,8 @@
     }
 
     @Test
-    @Parameters(method = "gkiVersions")
+    @Parameters(method = "osVersions")
     @TestCaseName("{method}_os_{0}")
-    @Ignore("b/360388014") // TODO(b/360388014): fix & re-enable
     public void microdroidProtectedDeviceTreeCompat(String os) throws Exception {
         assumeArm64Supported();
         final String configPath = "assets/vm_config.json";