Merge "Import translations. DO NOT MERGE ANYWHERE" into main
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
index ab03049..bd1af49 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/ConfigJson.java
@@ -16,6 +16,7 @@
 
 package com.android.virtualization.terminal;
 
+
 import android.content.Context;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.graphics.Rect;
@@ -39,6 +40,7 @@
 import java.io.FileReader;
 import java.io.IOException;
 import java.io.Reader;
+import java.nio.file.Files;
 import java.nio.file.Path;
 import java.util.Arrays;
 import java.util.HashMap;
@@ -209,13 +211,26 @@
                                 GUEST_GID,
                                 0007,
                                 "android",
-                                "android");
+                                "android",
+                                false, /* app domain is set to false so that crosvm is spin up as child of virtmgr */
+                                "");
                     }
                     return null;
                 }
+                Path socketPath = context.getFilesDir().toPath().resolve("internal.virtiofs");
+                Files.deleteIfExists(socketPath);
                 return new SharedPath(
-                        sharedPath, terminalUid, terminalUid, 0, 0, 0007, "internal", "internal");
-            } catch (NameNotFoundException e) {
+                        sharedPath,
+                        terminalUid,
+                        terminalUid,
+                        0,
+                        0,
+                        0007,
+                        "internal",
+                        "internal",
+                        true, /* app domain is set to true so that crosvm is spin up from app context */
+                        socketPath.toString());
+            } catch (NameNotFoundException | IOException e) {
                 return null;
             }
         }
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index eab737b..bfa425d 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -173,8 +173,8 @@
         findViewById(R.id.btn_ctrl)
                 .setOnClickListener(
                         (v) -> {
-                            mWebView.loadUrl(TerminalView.CTRL_KEY_HANDLER);
-                            mWebView.loadUrl(TerminalView.ENABLE_CTRL_KEY);
+                            mWebView.evaluateJavascript(TerminalView.CTRL_KEY_HANDLER, null);
+                            mWebView.evaluateJavascript(TerminalView.ENABLE_CTRL_KEY, null);
                         });
 
         View.OnClickListener modifierButtonClickListener =
@@ -300,6 +300,8 @@
                                                     .setVisibility(View.VISIBLE);
                                             mBootCompleted.open();
                                             updateModifierKeysVisibility();
+                                            mWebView.evaluateJavascript(
+                                                    TerminalView.TOUCH_TO_MOUSE_HANDLER, null);
                                         }
                                     }
                                 });
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
index efa3dcc..3f09e35 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/TerminalView.java
@@ -51,7 +51,7 @@
     // keycode 97(A)-122(Z) is converted to a small letter, and mapped to ctrl code
     public static final String CTRL_KEY_HANDLER =
             """
-javascript: (function() {
+(function() {
   window.term.attachCustomKeyEventHandler((e) => {
       if (window.ctrl) {
           keyCode = e.keyCode;
@@ -77,7 +77,74 @@
   });
 })();
 """;
-    public static final String ENABLE_CTRL_KEY = "javascript:(function(){window.ctrl=true;})();";
+    public static final String ENABLE_CTRL_KEY = "(function(){window.ctrl=true;})();";
+
+    // TODO(b/375326606): consider contribution on
+    // upstream(https://github.com/xtermjs/xterm.js/issues/3727)
+    public static final String TOUCH_TO_MOUSE_HANDLER =
+            """
+(function() {
+let convertTouchToMouse = false;
+function touchHandler(event) {
+  const contextmenuByTouch =
+      event.type === 'contextmenu' && event.pointerType === 'touch';
+  // Only proceed for long touches (contextmenu) or when converting touch to
+  // mouse
+  if (!contextmenuByTouch && !convertTouchToMouse) {
+    return;
+  }
+
+  const touch = event.changedTouches ? event.changedTouches[0] : event;
+
+  let type;
+  switch (event.type) {
+    case 'contextmenu':
+      convertTouchToMouse = true;
+      type = 'mousedown';
+      break;
+    case 'touchmove':
+      type = 'mousemove';
+      break;
+    case 'touchend':
+      convertTouchToMouse = false;
+      type = 'mouseup';
+      break;
+    default:
+      convertTouchToMouse = false;
+      return;
+  }
+
+  const simulatedEvent = new MouseEvent(type, {
+    bubbles: true,
+    cancelable: true,
+    view: window,
+    detail: 1,
+    screenX: touch.screenX,
+    screenY: touch.screenY,
+    clientX: touch.clientX,
+    clientY: touch.clientY,
+    button: 0,  // left click
+  });
+
+  touch.target.dispatchEvent(simulatedEvent);
+
+  // Prevent default behavior for touch events (except contextmenu)
+  if (event.type !== 'contextmenu') {
+    event.preventDefault();
+    event.stopPropagation();
+  }
+}
+const eventOptions = {
+  capture: true,
+  passive: false
+};
+document.addEventListener('touchstart', touchHandler, eventOptions);
+document.addEventListener('touchmove', touchHandler, eventOptions);
+document.addEventListener('touchend', touchHandler, eventOptions);
+document.addEventListener('touchcancel', touchHandler, eventOptions);
+document.addEventListener('contextmenu', touchHandler, eventOptions);
+})();
+""";
 
     private final AccessibilityManager mA11yManager;
 
@@ -307,10 +374,7 @@
     public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
         InputConnection inputConnection = super.onCreateInputConnection(outAttrs);
         if (outAttrs != null) {
-            // TODO(b/378642568): consider using InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
-            // here..
-            outAttrs.inputType =
-                    InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
+            outAttrs.inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
         }
         return inputConnection;
     }
diff --git a/android/compos_verify/verify.rs b/android/compos_verify/verify.rs
index a3f18d5..b94ebbc 100644
--- a/android/compos_verify/verify.rs
+++ b/android/compos_verify/verify.rs
@@ -124,6 +124,7 @@
         &idsig_manifest_ext_apk,
         &VmParameters {
             name: String::from("ComposVerify"),
+            os: String::from("microdroid"),
             cpu_topology: VmCpuTopology::OneCpu, // This VM runs very little work at boot
             debug_mode: args.debug,
             ..Default::default()
diff --git a/android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index dde75e1..3748899 100644
--- a/android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/android/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -48,5 +48,6 @@
      * callback, unless the returned ICompilationTask is cancelled. The caller should maintain
      * a reference to the ICompilationTask until compilation completes or is cancelled.
      */
-    ICompilationTask startTestCompile(ApexSource apexSource, ICompilationTaskCallback callback);
+    ICompilationTask startTestCompile(
+            ApexSource apexSource, ICompilationTaskCallback callback, String os);
 }
diff --git a/android/composd/src/instance_manager.rs b/android/composd/src/instance_manager.rs
index 9e94035..d1b0b99 100644
--- a/android/composd/src/instance_manager.rs
+++ b/android/composd/src/instance_manager.rs
@@ -46,11 +46,12 @@
         self.start_instance(CURRENT_INSTANCE_DIR, vm_parameters)
     }
 
-    pub fn start_test_instance(&self, prefer_staged: bool) -> Result<CompOsInstance> {
+    pub fn start_test_instance(&self, prefer_staged: bool, os: &str) -> Result<CompOsInstance> {
         let mut vm_parameters = new_vm_parameters()?;
         vm_parameters.name = String::from("ComposdTest");
         vm_parameters.debug_mode = true;
         vm_parameters.prefer_staged = prefer_staged;
+        vm_parameters.os = os.to_owned();
         self.start_instance(TEST_INSTANCE_DIR, vm_parameters)
     }
 
@@ -83,7 +84,8 @@
     // number of dex2oat threads.
     let cpu_topology = VmCpuTopology::MatchHost;
     let memory_mib = Some(compos_memory_mib()?);
-    Ok(VmParameters { cpu_topology, memory_mib, ..Default::default() })
+    let os = "microdroid".to_owned();
+    Ok(VmParameters { cpu_topology, memory_mib, os, ..Default::default() })
 }
 
 fn compos_memory_mib() -> Result<i32> {
diff --git a/android/composd/src/service.rs b/android/composd/src/service.rs
index 49cfd3a..3cc40af 100644
--- a/android/composd/src/service.rs
+++ b/android/composd/src/service.rs
@@ -60,6 +60,7 @@
         &self,
         apex_source: ApexSource,
         callback: &Strong<dyn ICompilationTaskCallback>,
+        os: &str,
     ) -> binder::Result<Strong<dyn ICompilationTask>> {
         check_permissions()?;
         let prefer_staged = match apex_source {
@@ -67,7 +68,7 @@
             ApexSource::PreferStaged => true,
             _ => unreachable!("Invalid ApexSource {:?}", apex_source),
         };
-        to_binder_result(self.do_start_test_compile(prefer_staged, callback))
+        to_binder_result(self.do_start_test_compile(prefer_staged, callback, os))
     }
 }
 
@@ -93,9 +94,12 @@
         &self,
         prefer_staged: bool,
         callback: &Strong<dyn ICompilationTaskCallback>,
+        os: &str,
     ) -> Result<Strong<dyn ICompilationTask>> {
-        let comp_os =
-            self.instance_manager.start_test_instance(prefer_staged).context("Starting CompOS")?;
+        let comp_os = self
+            .instance_manager
+            .start_test_instance(prefer_staged, os)
+            .context("Starting CompOS")?;
 
         let target_dir_name = TEST_ARTIFACTS_SUBDIR.to_owned();
         let task = OdrefreshTask::start(
diff --git a/android/composd_cmd/composd_cmd.rs b/android/composd_cmd/composd_cmd.rs
index 6d096a1..6281bd0 100644
--- a/android/composd_cmd/composd_cmd.rs
+++ b/android/composd_cmd/composd_cmd.rs
@@ -46,6 +46,10 @@
         /// If any APEX is staged, prefer the staged version.
         #[clap(long)]
         prefer_staged: bool,
+
+        /// OS for the VM.
+        #[clap(long, default_value = "microdroid")]
+        os: String,
     },
 }
 
@@ -56,7 +60,7 @@
 
     match action {
         Actions::StagedApexCompile {} => run_staged_apex_compile()?,
-        Actions::TestCompile { prefer_staged } => run_test_compile(prefer_staged)?,
+        Actions::TestCompile { prefer_staged, os } => run_test_compile(prefer_staged, &os)?,
     }
 
     println!("All Ok!");
@@ -116,9 +120,9 @@
     run_async_compilation(|service, callback| service.startStagedApexCompile(callback))
 }
 
-fn run_test_compile(prefer_staged: bool) -> Result<()> {
+fn run_test_compile(prefer_staged: bool, os: &str) -> Result<()> {
     let apex_source = if prefer_staged { ApexSource::PreferStaged } else { ApexSource::NoStaged };
-    run_async_compilation(|service, callback| service.startTestCompile(apex_source, callback))
+    run_async_compilation(|service, callback| service.startTestCompile(apex_source, callback, os))
 }
 
 fn run_async_compilation<F>(start_compile_fn: F) -> Result<()>
diff --git a/android/virtmgr/src/aidl.rs b/android/virtmgr/src/aidl.rs
index 55de0af..c2f7663 100644
--- a/android/virtmgr/src/aidl.rs
+++ b/android/virtmgr/src/aidl.rs
@@ -1018,7 +1018,12 @@
                 guest_gid: path.guestGid,
                 mask: path.mask,
                 tag: path.tag.clone(),
-                socket_path: temporary_directory.join(&path.socket).to_string_lossy().to_string(),
+                socket_path: temporary_directory
+                    .join(&path.socketPath)
+                    .to_string_lossy()
+                    .to_string(),
+                socket_fd: maybe_clone_file(&path.socketFd)?,
+                app_domain: path.appDomain,
             })
         })
         .collect()
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index 1ccabec..a385b82 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -235,6 +235,8 @@
     pub mask: i32,
     pub tag: String,
     pub socket_path: String,
+    pub socket_fd: Option<File>,
+    pub app_domain: bool,
 }
 
 /// virtio-input device configuration from `external/crosvm/src/crosvm/config.rs`
@@ -912,6 +914,9 @@
 
 fn run_virtiofs(config: &CrosvmConfig) -> io::Result<()> {
     for shared_path in &config.shared_paths {
+        if shared_path.app_domain {
+            continue;
+        }
         let ugid_map_value = format!(
             "{} {} {} {} {} /",
             shared_path.guest_uid,
@@ -1267,12 +1272,23 @@
     }
 
     for shared_path in &config.shared_paths {
-        if let Err(e) = wait_for_file(&shared_path.socket_path, 5) {
-            bail!("Error waiting for file: {}", e);
+        if shared_path.app_domain {
+            if let Some(socket_fd) = &shared_path.socket_fd {
+                let socket_path =
+                    add_preserved_fd(&mut preserved_fds, socket_fd.try_clone().unwrap());
+                let raw_fd: i32 = socket_path.rsplit_once('/').unwrap().1.parse().unwrap();
+                command
+                    .arg("--vhost-user-fs")
+                    .arg(format!("tag={},socket-fd={}", &shared_path.tag, raw_fd));
+            }
+        } else {
+            if let Err(e) = wait_for_file(&shared_path.socket_path, 5) {
+                bail!("Error waiting for file: {}", e);
+            }
+            command
+                .arg("--vhost-user-fs")
+                .arg(format!("{},tag={}", &shared_path.socket_path, &shared_path.tag));
         }
-        command
-            .arg("--vhost-user-fs")
-            .arg(format!("{},tag={}", &shared_path.socket_path, &shared_path.tag));
     }
 
     debug!("Preserving FDs {:?}", preserved_fds);
diff --git a/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl b/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl
index 7be7a5f..71ec02c 100644
--- a/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl
+++ b/android/virtualizationservice/aidl/android/system/virtualizationservice/SharedPath.aidl
@@ -39,5 +39,11 @@
     String tag;
 
     /** socket name for vhost-user-fs */
-    String socket;
+    String socketPath;
+
+    /** socket fd for crosvm to connect */
+    @nullable ParcelFileDescriptor socketFd;
+
+    /** crosvm started from appDomain */
+    boolean appDomain;
 }
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
index 8230166..3829f9f 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -33,6 +33,8 @@
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
+import android.net.LocalSocket;
+import android.net.LocalSocketAddress;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
@@ -56,6 +58,8 @@
 import java.io.OutputStream;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.nio.file.Files;
+import java.nio.file.Path;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -635,6 +639,34 @@
         }
     }
 
+    private void startCrosvmVirtiofs(
+            String sharedPath,
+            int host_uid,
+            int guest_uid,
+            int guest_gid,
+            String tagName,
+            int mask,
+            String socketPath)
+            throws IOException {
+        String ugidMapValue =
+                String.format("%d %d %d %d %d /", guest_uid, guest_gid, host_uid, host_uid, mask);
+        String cfgArg = String.format("ugid_map='%s'", ugidMapValue);
+        ProcessBuilder pb =
+                new ProcessBuilder(
+                        "/apex/com.android.virt/bin/crosvm",
+                        "device",
+                        "fs",
+                        "--socket=" + socketPath,
+                        "--tag=" + tagName,
+                        "--shared-dir=" + sharedPath,
+                        "--cfg",
+                        cfgArg,
+                        "--disable-sandbox",
+                        "--skip-pivot-root=true");
+
+        pb.start();
+    }
+
     VirtualMachineRawConfig toVsRawConfig() throws IllegalStateException, IOException {
         VirtualMachineRawConfig config = new VirtualMachineRawConfig();
         VirtualMachineCustomImageConfig customImageConfig = getCustomImageConfig();
@@ -714,6 +746,38 @@
                                 .orElse(0)];
         for (int i = 0; i < config.sharedPaths.length; i++) {
             config.sharedPaths[i] = customImageConfig.getSharedPaths()[i].toParcelable();
+            if (config.sharedPaths[i].appDomain) {
+                try {
+                    String socketPath = customImageConfig.getSharedPaths()[i].getSocketPath();
+                    startCrosvmVirtiofs(
+                            config.sharedPaths[i].sharedPath,
+                            config.sharedPaths[i].hostUid,
+                            config.sharedPaths[i].guestUid,
+                            config.sharedPaths[i].guestGid,
+                            config.sharedPaths[i].tag,
+                            config.sharedPaths[i].mask,
+                            socketPath);
+                    long startTime = System.currentTimeMillis();
+                    long deadline = startTime + 5000;
+                    // TODO: use socketpair instead of crosvm creating the named sockets.
+                    while (!Files.exists(Path.of(socketPath))
+                            && System.currentTimeMillis() < deadline) {
+                        Thread.sleep(200);
+                    }
+                    if (!Files.exists(Path.of(socketPath))) {
+                        throw new IOException("Timeout waiting for socket: " + socketPath);
+                    }
+                    LocalSocket socket = new LocalSocket();
+                    socket.connect(
+                            new LocalSocketAddress(
+                                    socketPath, LocalSocketAddress.Namespace.FILESYSTEM));
+                    config.sharedPaths[i].socketFd =
+                            ParcelFileDescriptor.dup(socket.getFileDescriptor());
+                } catch (IOException | InterruptedException e) {
+                    Log.e(TAG, "startCrosvmVirtiofs failed", e);
+                    throw new RuntimeException(e);
+                }
+            }
         }
 
         config.displayConfig =
diff --git a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 9b0709d..93f29a9 100644
--- a/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/libs/framework-virtualization/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -317,6 +317,8 @@
         private final int mask;
         private final String tag;
         private final String socket;
+        private final boolean appDomain;
+        private final String socketPath;
 
         public SharedPath(
                 String path,
@@ -326,7 +328,9 @@
                 int guestGid,
                 int mask,
                 String tag,
-                String socket) {
+                String socket,
+                boolean appDomain,
+                String socketPath) {
             this.path = path;
             this.hostUid = hostUid;
             this.hostGid = hostGid;
@@ -335,6 +339,8 @@
             this.mask = mask;
             this.tag = tag;
             this.socket = socket;
+            this.appDomain = appDomain;
+            this.socketPath = socketPath;
         }
 
         android.system.virtualizationservice.SharedPath toParcelable() {
@@ -347,7 +353,8 @@
             parcelable.guestGid = this.guestGid;
             parcelable.mask = this.mask;
             parcelable.tag = this.tag;
-            parcelable.socket = this.socket;
+            parcelable.socketPath = this.socket;
+            parcelable.appDomain = this.appDomain;
             return parcelable;
         }
 
@@ -390,6 +397,16 @@
         public String getSocket() {
             return socket;
         }
+
+        /** @hide */
+        public boolean getAppDomain() {
+            return appDomain;
+        }
+
+        /** @hide */
+        public String getSocketPath() {
+            return socketPath;
+        }
     }
 
     /** @hide */
diff --git a/libs/libcompos_common/compos_client.rs b/libs/libcompos_common/compos_client.rs
index 316eaa9..6872582 100644
--- a/libs/libcompos_common/compos_client.rs
+++ b/libs/libcompos_common/compos_client.rs
@@ -58,6 +58,8 @@
 pub struct VmParameters {
     /// The name of VM for identifying.
     pub name: String,
+    /// The OS of VM.
+    pub os: String,
     /// Whether the VM should be debuggable.
     pub debug_mode: bool,
     /// CPU topology of the VM. Defaults to 1 vCPU.
@@ -129,6 +131,7 @@
 
         let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
             name: parameters.name.clone(),
+            osName: parameters.os.clone(),
             apk: Some(apk_fd),
             idsig: Some(idsig_fd),
             instanceId: instance_id,