Merge "Close stdios when fork/exec'ing virtmgr" into main
diff --git a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
index c32d017..433e89c 100644
--- a/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
+++ b/android/FerrochromeApp/java/com/android/virtualization/ferrochrome/OpenUrlActivity.java
@@ -30,8 +30,8 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        boolean isRoot = isTaskRoot();
         finish();
+
         if (!Intent.ACTION_SEND.equals(getIntent().getAction())) {
             return;
         }
@@ -49,16 +49,6 @@
             return;
         }
         Log.i(TAG, "Sending " + scheme + " URL to VM");
-        if (isRoot) {
-            Log.w(
-                    TAG,
-                    "Cannot open URL without starting "
-                            + FerrochromeActivity.class.getSimpleName()
-                            + " first, starting it now");
-            startActivity(
-                    new Intent(this, FerrochromeActivity.class).setAction(Intent.ACTION_MAIN));
-            return;
-        }
         startActivity(
                 new Intent(ACTION_VM_OPEN_URL)
                         .setFlags(
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
index 828d923..def464e 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/ClipboardHandler.java
@@ -34,7 +34,7 @@
         mVmAgent = vmAgent;
     }
 
-    private VmAgent.Connection getConnection() {
+    private VmAgent.Connection getConnection() throws InterruptedException {
         return mVmAgent.connect();
     }
 
@@ -53,7 +53,7 @@
 
         try {
             getConnection().sendData(VmAgent.WRITE_CLIPBOARD_TYPE_TEXT_PLAIN, data);
-        } catch (RuntimeException e) {
+        } catch (InterruptedException | RuntimeException e) {
             Log.e(TAG, "Failed to write clipboard data to VM", e);
         }
     }
@@ -63,7 +63,7 @@
         VmAgent.Data data;
         try {
             data = getConnection().sendAndReceive(VmAgent.READ_CLIPBOARD_FROM_VM, null);
-        } catch (RuntimeException e) {
+        } catch (InterruptedException | RuntimeException e) {
             Log.e(TAG, "Failed to read clipboard data from VM", e);
             return;
         }
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
index 54543b0..fb75533 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -52,16 +52,12 @@
     private DisplayProvider mDisplayProvider;
     private VmAgent mVmAgent;
     private ClipboardHandler mClipboardHandler;
+    private OpenUrlHandler mOpenUrlHandler;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-        String action = getIntent().getAction();
-        if (!ACTION_VM_LAUNCHER.equals(action)) {
-            finish();
-            Log.e(TAG, "onCreate unsupported intent action: " + action);
-            return;
-        }
+        Log.d(TAG, "onCreate intent: " + getIntent());
         checkAndRequestRecordAudioPermission();
         mExecutorService = Executors.newCachedThreadPool();
 
@@ -98,6 +94,8 @@
 
         mVmAgent = new VmAgent(mVirtualMachine);
         mClipboardHandler = new ClipboardHandler(this, mVmAgent);
+        mOpenUrlHandler = new OpenUrlHandler(mVmAgent);
+        handleIntent(getIntent());
     }
 
     private void makeFullscreen() {
@@ -146,6 +144,7 @@
         super.onDestroy();
         mExecutorService.shutdownNow();
         mInputForwarder.cleanUp();
+        mOpenUrlHandler.shutdown();
         Log.d(TAG, "destroyed");
     }
 
@@ -172,18 +171,16 @@
 
     @Override
     protected void onNewIntent(Intent intent) {
-        String action = intent.getAction();
-        if (!ACTION_VM_OPEN_URL.equals(action)) {
-            Log.e(TAG, "onNewIntent unsupported intent action: " + action);
-            return;
-        }
-        Log.d(TAG, "onNewIntent intent action: " + action);
-        String text = intent.getStringExtra(Intent.EXTRA_TEXT);
-        if (text != null) {
-            mExecutorService.execute(
-                    () -> {
-                        mVmAgent.connect().sendData(VmAgent.OPEN_URL, text.getBytes());
-                    });
+        Log.d(TAG, "onNewIntent intent: " + intent);
+        handleIntent(intent);
+    }
+
+    private void handleIntent(Intent intent) {
+        if (ACTION_VM_OPEN_URL.equals(intent.getAction())) {
+            String url = intent.getStringExtra(Intent.EXTRA_TEXT);
+            if (url != null) {
+                mOpenUrlHandler.sendUrlToVm(url);
+            }
         }
     }
 
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
new file mode 100644
index 0000000..fb0c6bf
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/OpenUrlHandler.java
@@ -0,0 +1,50 @@
+/*
+ * 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 com.android.virtualization.vmlauncher;
+
+import android.util.Log;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+class OpenUrlHandler {
+    private static final String TAG = MainActivity.TAG;
+
+    private final VmAgent mVmAgent;
+    private final ExecutorService mExecutorService;
+
+    OpenUrlHandler(VmAgent vmAgent) {
+        mVmAgent = vmAgent;
+        mExecutorService = Executors.newSingleThreadExecutor();
+    }
+
+    void shutdown() {
+        mExecutorService.shutdownNow();
+    }
+
+    void sendUrlToVm(String url) {
+        mExecutorService.execute(
+                () -> {
+                    try {
+                        mVmAgent.connect().sendData(VmAgent.OPEN_URL, url.getBytes());
+                        Log.d(TAG, "Successfully sent URL to the VM");
+                    } catch (InterruptedException | RuntimeException e) {
+                        Log.e(TAG, "Failed to send URL to the VM", e);
+                    }
+                });
+    }
+}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
index 78da6c0..af1d298 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmAgent.java
@@ -17,8 +17,10 @@
 package com.android.virtualization.vmlauncher;
 
 import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineException;
+import android.util.Log;
 
 import libcore.io.Streams;
 
@@ -39,6 +41,7 @@
     private static final int DATA_SHARING_SERVICE_PORT = 3580;
     private static final int HEADER_SIZE = 8; // size of the header
     private static final int SIZE_OFFSET = 4; // offset of the size field in the header
+    private static final long RETRY_INTERVAL_MS = 1_000;
 
     static final byte READ_CLIPBOARD_FROM_VM = 0;
     static final byte WRITE_CLIPBOARD_TYPE_EMPTY = 1;
@@ -51,13 +54,26 @@
         mVirtualMachine = vm;
     }
 
-    /** Connect to the agent and returns the communication channel established. This can block. */
-    Connection connect() {
-        try {
-            // TODO: wait until the VM is up and the agent is running
-            return new Connection(mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT));
-        } catch (VirtualMachineException e) {
-            throw new RuntimeException("Failed to connect to the VM agent", e);
+    /**
+     * Connects to the agent and returns the established communication channel. This can block.
+     *
+     * @throws InterruptedException If the current thread was interrupted
+     */
+    Connection connect() throws InterruptedException {
+        boolean shouldLog = true;
+        while (true) {
+            if (Thread.interrupted()) {
+                throw new InterruptedException();
+            }
+            try {
+                return new Connection(mVirtualMachine.connectVsock(DATA_SHARING_SERVICE_PORT));
+            } catch (VirtualMachineException e) {
+                if (shouldLog) {
+                    shouldLog = false;
+                    Log.d(TAG, "Still waiting for VM agent to start", e);
+                }
+            }
+            SystemClock.sleep(RETRY_INTERVAL_MS);
         }
     }
 
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index f1bfd8c..144524f 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -411,9 +411,9 @@
 
         let state = &mut *self.state.lock().unwrap();
         let console_out_fd =
-            clone_or_prepare_logger_fd(&debug_config, console_out_fd, format!("Console({})", cid))?;
+            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(&debug_config, log_fd, format!("Log({})", cid))?;
+        let log_fd = clone_or_prepare_logger_fd(log_fd, format!("Log({})", cid))?;
 
         // Counter to generate unique IDs for temporary image files.
         let mut next_temporary_image_id = 0;
@@ -1563,7 +1563,6 @@
 }
 
 fn clone_or_prepare_logger_fd(
-    debug_config: &DebugConfig,
     fd: Option<&ParcelFileDescriptor>,
     tag: String,
 ) -> Result<Option<File>, Status> {
@@ -1571,10 +1570,6 @@
         return Ok(Some(clone_file(fd)?));
     }
 
-    if !debug_config.should_prepare_console_output() {
-        return Ok(None);
-    };
-
     let (read_fd, write_fd) =
         pipe().context("Failed to create pipe").or_service_specific_exception(-1)?;
 
diff --git a/android/vm/src/run.rs b/android/vm/src/run.rs
index cb15802..b3743ae 100644
--- a/android/vm/src/run.rs
+++ b/android/vm/src/run.rs
@@ -36,7 +36,7 @@
 use std::fs::File;
 use std::io;
 use std::io::{Read, Write};
-use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::os::fd::AsFd;
 use std::path::{Path, PathBuf};
 use vmclient::{ErrorCode, VmInstance};
 use vmconfig::{get_debug_level, open_parcel_file, VmConfig};
@@ -365,16 +365,6 @@
 }
 
 /// Safely duplicate the file descriptor.
-fn duplicate_fd<T: AsRawFd>(file: T) -> io::Result<File> {
-    let fd = file.as_raw_fd();
-    // SAFETY: This just duplicates a file descriptor which we know to be valid, and we check for an
-    // an error.
-    let dup_fd = unsafe { libc::dup(fd) };
-    if dup_fd < 0 {
-        Err(io::Error::last_os_error())
-    } else {
-        // SAFETY: We have just duplicated the file descriptor so we own it, and `from_raw_fd` takes
-        // ownership of it.
-        Ok(unsafe { File::from_raw_fd(dup_fd) })
-    }
+fn duplicate_fd<T: AsFd>(file: T) -> io::Result<File> {
+    Ok(file.as_fd().try_clone_to_owned()?.into())
 }