Merge changes Id33c0af3,I66d97987,Idbd4bf2f into main
* changes:
virtmgr: Wait for socket file before connecting
virtiofs: Add systemd unit to mount virtiofs inside guest
virtiofs: Request permission to access storage
diff --git a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
index af41c85..e278165 100644
--- a/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
+++ b/android/TerminalApp/java/com/android/virtualization/terminal/MainActivity.java
@@ -26,8 +26,11 @@
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
import android.graphics.fonts.FontStyle;
+import android.net.Uri;
import android.net.http.SslError;
import android.os.Bundle;
+import android.os.Environment;
+import android.provider.Settings;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
@@ -44,6 +47,10 @@
import android.webkit.WebViewClient;
import android.widget.Toast;
+import androidx.activity.result.ActivityResult;
+import androidx.activity.result.ActivityResultLauncher;
+import androidx.activity.result.contract.ActivityResultContracts;
+
import com.android.virtualization.vmlauncher.InstallUtils;
import com.android.virtualization.vmlauncher.VmLauncherService;
import com.android.virtualization.vmlauncher.VmLauncherServices;
@@ -81,6 +88,7 @@
private WebView mWebView;
private AccessibilityManager mAccessibilityManager;
private static final int POST_NOTIFICATIONS_PERMISSION_REQUEST_CODE = 101;
+ private ActivityResultLauncher<Intent> manageExternalStorageActivityResultLauncher;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -111,12 +119,41 @@
connectToTerminalService();
readClientCertificate();
+ manageExternalStorageActivityResultLauncher =
+ registerForActivityResult(
+ new ActivityResultContracts.StartActivityForResult(),
+ (ActivityResult result) -> {
+ if (Environment.isExternalStorageManager()) {
+ Toast.makeText(this, "Storage permission set!", Toast.LENGTH_SHORT)
+ .show();
+ } else {
+ Toast.makeText(
+ this,
+ "Storage permission not set",
+ Toast.LENGTH_SHORT)
+ .show();
+ }
+ startVm();
+ });
+
// if installer is launched, it will be handled in onActivityResult
if (!launchInstaller) {
- startVm();
+ if (!Environment.isExternalStorageManager()) {
+ requestStoragePermissions(this, manageExternalStorageActivityResultLauncher);
+ } else {
+ startVm();
+ }
}
}
+ private void requestStoragePermissions(
+ Context context, ActivityResultLauncher<Intent> activityResultLauncher) {
+ Intent intent = new Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION);
+ Uri uri = Uri.fromParts("package", context.getPackageName(), null);
+ intent.setData(uri);
+ activityResultLauncher.launch(intent);
+ }
+
private URL getTerminalServiceUrl() {
Configuration config = getResources().getConfiguration();
@@ -406,7 +443,11 @@
Log.e(TAG, "Failed to start VM. Installer returned error.");
finish();
}
- startVm();
+ if (!Environment.isExternalStorageManager()) {
+ requestStoragePermissions(this, manageExternalStorageActivityResultLauncher);
+ } else {
+ startVm();
+ }
}
}
diff --git a/android/virtmgr/src/crosvm.rs b/android/virtmgr/src/crosvm.rs
index b2283d0..b28834a 100644
--- a/android/virtmgr/src/crosvm.rs
+++ b/android/virtmgr/src/crosvm.rs
@@ -1244,6 +1244,9 @@
}
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);
+ }
command
.arg("--vhost-user-fs")
.arg(format!("{},tag={}", &shared_path.socket_path, &shared_path.tag));
@@ -1269,6 +1272,23 @@
Ok(result)
}
+fn wait_for_file(path: &str, timeout_secs: u64) -> Result<(), std::io::Error> {
+ let start_time = std::time::Instant::now();
+ let timeout = Duration::from_secs(timeout_secs);
+
+ while start_time.elapsed() < timeout {
+ if std::fs::metadata(path).is_ok() {
+ return Ok(()); // File exists
+ }
+ thread::sleep(Duration::from_millis(100));
+ }
+
+ Err(std::io::Error::new(
+ std::io::ErrorKind::NotFound,
+ format!("File not found within {} seconds: {}", timeout_secs, path),
+ ))
+}
+
/// Ensure that the configuration has a valid combination of fields set, or return an error if not.
fn validate_config(config: &CrosvmConfig) -> Result<(), Error> {
if config.bootloader.is_none() && config.kernel.is_none() {
diff --git a/build/debian/fai_config/files/etc/systemd/system/virtiofs.service/AVF b/build/debian/fai_config/files/etc/systemd/system/virtiofs.service/AVF
new file mode 100644
index 0000000..31ed059
--- /dev/null
+++ b/build/debian/fai_config/files/etc/systemd/system/virtiofs.service/AVF
@@ -0,0 +1,13 @@
+[Unit]
+Description=Mount virtiofs emulated file path
+After=network.target
+
+[Service]
+Type=oneshot
+User=root
+Group=root
+ExecStart=/bin/bash -c 'mkdir -p /mnt/shared; chown 1000:100 /mnt/shared; mount -t virtiofs android /mnt/shared'
+RemainAfterExit=yes
+
+[Install]
+WantedBy=multi-user.target
diff --git a/build/debian/fai_config/scripts/AVF/10-systemd b/build/debian/fai_config/scripts/AVF/10-systemd
index 6a106c6..0886f72 100755
--- a/build/debian/fai_config/scripts/AVF/10-systemd
+++ b/build/debian/fai_config/scripts/AVF/10-systemd
@@ -6,3 +6,4 @@
chmod +x $target/usr/local/bin/ttyd
ln -s /etc/systemd/system/ttyd.service $target/etc/systemd/system/multi-user.target.wants/ttyd.service
ln -s /etc/systemd/system/ip_addr_reporter.service $target/etc/systemd/system/multi-user.target.wants/ip_addr_reporter.service
+ln -s /etc/systemd/system/virtiofs.service $target/etc/systemd/system/multi-user.target.wants/virtiofs.service
diff --git a/build/debian/vm_config.json.aarch64 b/build/debian/vm_config.json.aarch64
index 9f9295c..5b7489e 100644
--- a/build/debian/vm_config.json.aarch64
+++ b/build/debian/vm_config.json.aarch64
@@ -7,6 +7,14 @@
"writable": true
}
],
+ "sharedPath": [
+ {
+ "sharedPath": "/storage/emulated"
+ },
+ {
+ "sharedPath": "/data/data/com.google.android.virtualization.terminal/files"
+ }
+ ],
"protected": false,
"cpu_topology": "match_host",
"platform_version": "~1.0",
diff --git a/build/debian/vm_config.json.x86_64 b/build/debian/vm_config.json.x86_64
index 2fb9faa..8a491e4 100644
--- a/build/debian/vm_config.json.x86_64
+++ b/build/debian/vm_config.json.x86_64
@@ -7,6 +7,14 @@
"writable": true
}
],
+ "sharedPath": [
+ {
+ "sharedPath": "/storage/emulated"
+ },
+ {
+ "sharedPath": "/data/data/com.google.android.virtualization.terminal/files"
+ }
+ ],
"kernel": "$PAYLOAD_DIR/vmlinuz",
"initrd": "$PAYLOAD_DIR/initrd.img",
"params": "root=/dev/vda1",
diff --git a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
index 5d6b13f..a259fe2 100644
--- a/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
+++ b/libs/vm_launcher_lib/java/com/android/virtualization/vmlauncher/ConfigJson.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Rect;
+import android.os.Environment;
import android.system.virtualmachine.VirtualMachineConfig;
import android.system.virtualmachine.VirtualMachineCustomImageConfig;
import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
@@ -157,22 +158,40 @@
private static class SharedPathJson {
private SharedPathJson() {}
- // Package ID of Terminal app.
- private static final String TERMINAL_PACKAGE_ID =
- "com.google.android.virtualization.terminal";
private String sharedPath;
+ private static final int GUEST_UID = 1000;
+ private static final int GUEST_GID = 100;
private SharedPath toConfig(Context context) {
try {
- int uid =
- context.getPackageManager()
- .getPackageUidAsUser(TERMINAL_PACKAGE_ID, context.getUserId());
-
- return new SharedPath(sharedPath, uid, uid, 0, 0, 0007, "android", "android");
+ int terminalUid = getTerminalUid(context);
+ if (sharedPath.contains("emulated")) {
+ if (Environment.isExternalStorageManager()) {
+ int currentUserId = context.getUserId();
+ String path = sharedPath + "/" + currentUserId + "/Download";
+ return new SharedPath(
+ path,
+ terminalUid,
+ terminalUid,
+ GUEST_UID,
+ GUEST_GID,
+ 0007,
+ "android",
+ "android");
+ }
+ return null;
+ }
+ return new SharedPath(
+ sharedPath, terminalUid, terminalUid, 0, 0, 0007, "internal", "internal");
} catch (NameNotFoundException e) {
return null;
}
}
+
+ private int getTerminalUid(Context context) throws NameNotFoundException {
+ return context.getPackageManager()
+ .getPackageUidAsUser(context.getPackageName(), context.getUserId());
+ }
}
private static class InputJson {