Merge "Add tap and settings intent for the terminal notification" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
index 7256015..95bcbbc 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/SettingsRecoveryActivity.kt
@@ -15,18 +15,49 @@
  */
 package com.android.virtualization.terminal
 
+import android.content.Intent
 import android.os.Bundle
-import android.widget.Toast
+import android.util.Log
 import androidx.appcompat.app.AppCompatActivity
+import androidx.lifecycle.lifecycleScope
+import com.android.virtualization.vmlauncher.InstallUtils
 import com.google.android.material.card.MaterialCardView
+import com.google.android.material.dialog.MaterialAlertDialogBuilder
+import java.io.IOException
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
+
+const val TAG: String = "VmTerminalApp"
 
 class SettingsRecoveryActivity : AppCompatActivity() {
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(R.layout.settings_recovery)
         val resetCard = findViewById<MaterialCardView>(R.id.settings_recovery_reset_card)
+        val dialog = MaterialAlertDialogBuilder(this)
+            .setTitle(R.string.settings_recovery_reset_dialog_title)
+            .setMessage(R.string.settings_recovery_reset_dialog_message)
+            .setPositiveButton(R.string.settings_recovery_reset_dialog_confirm) { _, _ ->
+                // This coroutine will be killed when the activity is killed. The behavior is both acceptable
+                // either removing is done or not
+                lifecycleScope.launch(Dispatchers.IO) {
+                    try {
+                        InstallUtils.unInstall(this@SettingsRecoveryActivity)
+                        // Restart terminal
+                        val intent =
+                            baseContext.packageManager.getLaunchIntentForPackage(baseContext.packageName)
+                        intent?.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                        finish()
+                        startActivity(intent)
+                    } catch (e: IOException) {
+                        Log.e(TAG, "VM image reset failed.")
+                    }
+                }
+            }
+            .setNegativeButton(R.string.settings_recovery_reset_dialog_cancel) { dialog, _ -> dialog.dismiss() }
+            .create()
         resetCard.setOnClickListener {
-            Toast.makeText(this@SettingsRecoveryActivity, R.string.settings_recovery_reset_message, Toast.LENGTH_SHORT).show()
+            dialog.show()
         }
     }
 }
\ No newline at end of file
diff --git a/android/TerminalApp/res/values/strings.xml b/android/TerminalApp/res/values/strings.xml
index 070807c..ca803ec 100644
--- a/android/TerminalApp/res/values/strings.xml
+++ b/android/TerminalApp/res/values/strings.xml
@@ -87,8 +87,14 @@
     <string name="settings_recovery_reset_title">Change to Initial version</string>
     <!-- Settings menu subtitle for resetting the virtual machine image [CHAR LIMIT=none] -->
     <string name="settings_recovery_reset_sub_title">Remove all</string>
-    <!-- Toast message for reset is completed [CHAR LIMIT=none] -->
-    <string name="settings_recovery_reset_message">VM reset</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] -->
+    <string name="settings_recovery_reset_dialog_confirm">Confirm</string>
+    <!-- Dialog button cancel for restarting the terminal [CHAR LIMIT=16] -->
+    <string name="settings_recovery_reset_dialog_cancel">Cancel</string>
 
     <!-- Notification action button for settings [CHAR LIMIT=none] -->
     <string name="service_notification_settings">Settings</string>
diff --git a/build/debian/build.sh b/build/debian/build.sh
index 5829ecc..b4436c1 100755
--- a/build/debian/build.sh
+++ b/build/debian/build.sh
@@ -104,6 +104,7 @@
 
 	source "$HOME"/.cargo/env
 	rustup target add "${arch}"-unknown-linux-gnu
+	cargo install cargo-license
 }
 
 download_debian_cloud_image() {
@@ -124,6 +125,9 @@
 	mkdir -p "${dst}/files/usr/local/bin/$1"
 	cp "${workdir}/$1/${arch}-unknown-linux-gnu/debug/$1" "${dst}/files/usr/local/bin/$1/AVF"
 	chmod 777 "${dst}/files/usr/local/bin/$1/AVF"
+
+	mkdir -p "${dst}/files/usr/share/doc/$1"
+	cargo license > "${dst}/files/usr/share/doc/$1/copyright"
 	popd > /dev/null
 }
 
@@ -140,6 +144,8 @@
 	mkdir -p "${dst}/files/usr/local/bin/ttyd"
 	cp /tmp/stage/${arch}-linux-musl/bin/ttyd "${dst}/files/usr/local/bin/ttyd/AVF"
 	chmod 777 "${dst}/files/usr/local/bin/ttyd/AVF"
+	mkdir -p "${dst}/files/usr/share/doc/ttyd"
+	cp LICENSE "${dst}/files/usr/share/doc/ttyd/copyright"
 	popd > /dev/null
 	popd > /dev/null
 }
diff --git a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
index 251f88c..4c1b2f5 100644
--- a/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/forwarder_guest_launcher.service/AVF
@@ -2,8 +2,9 @@
 Description=Port forwarding service in guest VM
 After=syslog.target
 After=network.target
+After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/local/bin/forwarder_guest_launcher --host 192.168.0.1
+ExecStart=/usr/local/bin/forwarder_guest_launcher --host 192.168.0.1 --grpc_port $(cat /mnt/internal/debian_service_port)
 Type=simple
 Restart=on-failure
 RestartSec=1
diff --git a/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF b/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
index 7d163fb..81347a7 100644
--- a/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
+++ b/build/debian/fai_config/files/etc/systemd/system/ip_addr_reporter.service/AVF
@@ -3,8 +3,9 @@
 After=syslog.target
 After=network.target
 Requires=ttyd.service
+After=virtiofs_internal.service
 [Service]
-ExecStart=/usr/local/bin/ip_addr_reporter
+ExecStart=/usr/local/bin/ip_addr_reporter --grpc_port $(cat /mnt/internal/debian_service_port)
 Type=simple
 Restart=on-failure
 User=root
diff --git a/guest/forwarder_guest/Cargo.toml b/guest/forwarder_guest/Cargo.toml
index 65f57cf..ce50e4c 100644
--- a/guest/forwarder_guest/Cargo.toml
+++ b/guest/forwarder_guest/Cargo.toml
@@ -2,6 +2,7 @@
 name = "forwarder_guest"
 version = "0.1.0"
 edition = "2021"
+license = "Apache-2.0"
 
 [dependencies]
 clap = { version = "4.5.19", features = ["derive"] }
diff --git a/guest/forwarder_guest_launcher/Cargo.toml b/guest/forwarder_guest_launcher/Cargo.toml
index 03d3f7f..b7f9eaf 100644
--- a/guest/forwarder_guest_launcher/Cargo.toml
+++ b/guest/forwarder_guest_launcher/Cargo.toml
@@ -2,6 +2,7 @@
 name = "forwarder_guest_launcher"
 version = "0.1.0"
 edition = "2021"
+license = "Apache-2.0"
 
 [dependencies]
 anyhow = "1.0.91"
diff --git a/guest/forwarder_guest_launcher/src/main.rs b/guest/forwarder_guest_launcher/src/main.rs
index 59ee8c6..d753d19 100644
--- a/guest/forwarder_guest_launcher/src/main.rs
+++ b/guest/forwarder_guest_launcher/src/main.rs
@@ -33,13 +33,17 @@
     #[arg(long)]
     #[arg(alias = "host")]
     host_addr: String,
+    /// grpc port number
+    #[arg(long)]
+    #[arg(alias = "grpc_port")]
+    grpc_port: String,
 }
 
 #[tokio::main]
 async fn main() -> Result<(), Box<dyn std::error::Error>> {
     println!("Starting forwarder_guest_launcher");
     let args = Args::parse();
-    let addr = format!("https://{}:12000", args.host_addr);
+    let addr = format!("https://{}:{}", args.host_addr, args.grpc_port);
 
     let channel = Endpoint::from_shared(addr)?.connect().await?;
     let mut client = DebianServiceClient::new(channel);
diff --git a/guest/ip_addr_reporter/Cargo.toml b/guest/ip_addr_reporter/Cargo.toml
index e255eaf..7592e3f 100644
--- a/guest/ip_addr_reporter/Cargo.toml
+++ b/guest/ip_addr_reporter/Cargo.toml
@@ -2,8 +2,10 @@
 name = "ip_addr_reporter"
 version = "0.1.0"
 edition = "2021"
+license = "Apache-2.0"
 
 [dependencies]
+clap = { version = "4.5.20", features = ["derive"] }
 netdev = "0.31.0"
 prost = "0.13.3"
 tokio = { version = "1.40.0", features = ["rt-multi-thread"] }
diff --git a/guest/ip_addr_reporter/src/main.rs b/guest/ip_addr_reporter/src/main.rs
index 5784a83..2c782d3 100644
--- a/guest/ip_addr_reporter/src/main.rs
+++ b/guest/ip_addr_reporter/src/main.rs
@@ -1,17 +1,27 @@
 use api::debian_service_client::DebianServiceClient;
 use api::IpAddr;
 
+use clap::Parser;
 pub mod api {
     tonic::include_proto!("com.android.virtualization.vmlauncher.proto");
 }
 
+#[derive(Parser)]
+/// Flags for running command
+pub struct Args {
+    /// grpc port number
+    #[arg(long)]
+    #[arg(alias = "grpc_port")]
+    grpc_port: String,
+}
+
 #[tokio::main]
 async fn main() -> Result<(), String> {
+    let args = Args::parse();
     let gateway_ip_addr = netdev::get_default_gateway()?.ipv4[0];
     let ip_addr = netdev::get_default_interface()?.ipv4[0].addr();
-    const PORT: i32 = 12000;
 
-    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), PORT);
+    let server_addr = format!("http://{}:{}", gateway_ip_addr.to_string(), args.grpc_port);
 
     println!("local ip addr: {}", ip_addr.to_string());
     println!("coonect to grpc server {}", server_addr);
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
index 1febe27..53dd677 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/InstallUtils.java
@@ -49,6 +49,10 @@
         return Files.exists(getInstallationCompletedPath(context));
     }
 
+    public static void unInstall(Context context) throws IOException {
+        Files.delete(getInstallationCompletedPath(context));
+    }
+
     public static boolean createInstalledMarker(Context context) {
         try {
             File file = new File(getInstallationCompletedPath(context).toString());
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
index 5cd7b92..f672b7b 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/VmLauncherService.java
@@ -27,11 +27,20 @@
 import android.system.virtualmachine.VirtualMachineException;
 import android.util.Log;
 
+import io.grpc.Grpc;
 import io.grpc.InsecureServerCredentials;
+import io.grpc.Metadata;
 import io.grpc.Server;
+import io.grpc.ServerCall;
+import io.grpc.ServerCallHandler;
+import io.grpc.ServerInterceptor;
+import io.grpc.Status;
 import io.grpc.okhttp.OkHttpServerBuilder;
 
+import java.io.File;
+import java.io.FileOutputStream;
 import java.io.IOException;
+import java.net.InetSocketAddress;
 import java.nio.file.Path;
 import java.util.Objects;
 import java.util.concurrent.ExecutorService;
@@ -137,19 +146,54 @@
     }
 
     private void startDebianServer() {
+        ServerInterceptor interceptor =
+                new ServerInterceptor() {
+                    @Override
+                    public <ReqT, RespT> ServerCall.Listener<ReqT> interceptCall(
+                            ServerCall<ReqT, RespT> call,
+                            Metadata headers,
+                            ServerCallHandler<ReqT, RespT> next) {
+                        // Refer to VirtualizationSystemService.TetheringService
+                        final String VM_STATIC_IP_ADDR = "192.168.0.2";
+                        InetSocketAddress remoteAddr =
+                                (InetSocketAddress)
+                                        call.getAttributes().get(Grpc.TRANSPORT_ATTR_REMOTE_ADDR);
+
+                        if (remoteAddr != null
+                                && Objects.equals(
+                                        remoteAddr.getAddress().getHostAddress(),
+                                        VM_STATIC_IP_ADDR)) {
+                            // Allow the request only if it is from VM
+                            return next.startCall(call, headers);
+                        }
+                        Log.d(TAG, "blocked grpc request from " + remoteAddr);
+                        call.close(Status.Code.PERMISSION_DENIED.toStatus(), new Metadata());
+                        return new ServerCall.Listener<ReqT>() {};
+                    }
+                };
         new Thread(
                         () -> {
-                            // TODO(b/372666638): gRPC for java doesn't support vsock for now.
-                            // In addition, let's consider using a dynamic port and SSL(and client
-                            // certificate)
-                            int port = 12000;
                             try {
+                                // TODO(b/372666638): gRPC for java doesn't support vsock for now.
+                                int port = 0;
                                 mServer =
                                         OkHttpServerBuilder.forPort(
                                                         port, InsecureServerCredentials.create())
+                                                .intercept(interceptor)
                                                 .addService(new DebianServiceImpl(this))
                                                 .build()
                                                 .start();
+
+                                // TODO(b/373533555): we can use mDNS for that.
+                                String debianServicePortFileName = "debian_service_port";
+                                File debianServicePortFile =
+                                        new File(getFilesDir(), debianServicePortFileName);
+                                try (FileOutputStream writer =
+                                        new FileOutputStream(debianServicePortFile)) {
+                                    writer.write(String.valueOf(mServer.getPort()).getBytes());
+                                } catch (IOException e) {
+                                    Log.d(TAG, "cannot write grpc port number", e);
+                                }
                             } catch (IOException e) {
                                 Log.d(TAG, "grpc server error", e);
                             }