Merge "[test] Use on-demand key provisioning in AvfRkpdVmAttestationTestApp" into main
diff --git a/Android.bp b/Android.bp
index 7cedfb7..dcf67dd 100644
--- a/Android.bp
+++ b/Android.bp
@@ -30,6 +30,7 @@
"release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
"release_avf_enable_virt_cpufreq",
+ "release_avf_support_custom_vm_with_paravirtualized_devices",
],
properties: [
"cfgs",
@@ -60,6 +61,9 @@
release_avf_enable_virt_cpufreq: {
cfgs: ["virt_cpufreq"],
},
+ release_avf_support_custom_vm_with_paravirtualized_devices: {
+ cfgs: ["paravirtualized_devices"],
+ },
},
}
@@ -69,6 +73,7 @@
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_dice_changes",
+ "release_avf_enable_vendor_modules",
"release_avf_enable_virt_cpufreq",
],
properties: [
@@ -82,6 +87,9 @@
release_avf_enable_dice_changes: {
cflags: ["-DAVF_OPEN_DICE_CHANGES=1"],
},
+ release_avf_enable_vendor_modules: {
+ cflags: ["-DAVF_ENABLE_VENDOR_MODULES=1"],
+ },
release_avf_enable_virt_cpufreq: {
cflags: ["-DAVF_ENABLE_VIRT_CPUFREQ=1"],
},
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 58dcc06..1c4f5ca 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -36,15 +36,9 @@
"name": "initrd_bootconfig.test"
},
{
- "name": "libdice_policy.test"
- },
- {
"name": "libapkzip.test"
},
{
- "name": "libsecretkeeper_comm.test"
- },
- {
"name": "libdice_driver_test"
}
],
diff --git a/apex/Android.bp b/apex/Android.bp
index 3b5141e..e6c809c 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -42,6 +42,7 @@
"release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
"release_avf_enable_virt_cpufreq",
+ "release_avf_support_custom_vm_with_paravirtualized_devices",
],
properties: [
"androidManifest",
@@ -50,6 +51,7 @@
"prebuilts",
"systemserverclasspath_fragments",
"vintf_fragments",
+ "apps",
],
}
@@ -96,6 +98,11 @@
canned_fs_config: "canned_fs_config",
},
},
+ release_avf_support_custom_vm_with_paravirtualized_devices: {
+ apps: [
+ "VmLauncherApp",
+ ],
+ },
},
}
diff --git a/apex/canned_fs_config_sys_nice b/apex/canned_fs_config_sys_nice
index 5b12eb5..90c9747 100644
--- a/apex/canned_fs_config_sys_nice
+++ b/apex/canned_fs_config_sys_nice
@@ -1,2 +1,3 @@
/bin/virtualizationservice 0 2000 0755 capabilities=0x1000001 # CAP_CHOWN, CAP_SYS_RESOURCE
-/bin/crosvm 0 3013 0755 capabilities=0x800000 # SYS_NICE
+/bin/crosvm 0 3013 0755 capabilities=0x800000 # CAP_SYS_NICE
+/bin/virtmgr 0 3013 0755 capabilities=0x800000 # CAP_SYS_NICE
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index 8edd899..5f91987 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -289,7 +289,7 @@
FdConfig::OutputDir(dir) => {
let mode = validate_file_mode(mode)?;
let new_fd = openat(
- dir.as_raw_fd(),
+ Some(dir.as_raw_fd()),
basename,
// This function is supposed to be only called when FUSE/authfs thinks the file
// does not exist. However, if the file does exist from the view of fd_server
@@ -319,10 +319,14 @@
FdConfig::InputDir(_) => Err(new_errno_error(Errno::EACCES)),
FdConfig::OutputDir(_) => {
let mode = validate_file_mode(mode)?;
- mkdirat(dir_fd, basename, mode).map_err(new_errno_error)?;
- let new_dir_fd =
- openat(dir_fd, basename, OFlag::O_DIRECTORY | OFlag::O_RDONLY, Mode::empty())
- .map_err(new_errno_error)?;
+ mkdirat(Some(dir_fd), basename, mode).map_err(new_errno_error)?;
+ let new_dir_fd = openat(
+ Some(dir_fd),
+ basename,
+ OFlag::O_DIRECTORY | OFlag::O_RDONLY,
+ Mode::empty(),
+ )
+ .map_err(new_errno_error)?;
// SAFETY: new_dir_fd is just created and not an error.
let fd_owner = unsafe { OwnedFd::from_raw_fd(new_dir_fd) };
Ok((new_dir_fd, FdConfig::OutputDir(fd_owner)))
@@ -403,7 +407,7 @@
}
fn open_readonly_at(dir_fd: BorrowedFd, path: &Path) -> nix::Result<File> {
- let new_fd = openat(dir_fd.as_raw_fd(), path, OFlag::O_RDONLY, Mode::empty())?;
+ let new_fd = openat(Some(dir_fd.as_raw_fd()), path, OFlag::O_RDONLY, Mode::empty())?;
// SAFETY: new_fd is just created successfully and not owned.
let new_file = unsafe { File::from_raw_fd(new_fd) };
Ok(new_file)
diff --git a/compos/composd/src/fd_server_helper.rs b/compos/composd/src/fd_server_helper.rs
index 24371b5..91860ab 100644
--- a/compos/composd/src/fd_server_helper.rs
+++ b/compos/composd/src/fd_server_helper.rs
@@ -23,7 +23,7 @@
use nix::unistd::pipe2;
use std::fs::File;
use std::io::Read;
-use std::os::unix::io::{AsRawFd, FromRawFd, OwnedFd};
+use std::os::unix::io::{AsRawFd, OwnedFd};
use std::path::Path;
const FD_SERVER_BIN: &str = "/apex/com.android.virt/bin/fd_server";
@@ -106,12 +106,8 @@
}
fn create_pipe() -> Result<(File, File)> {
- let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
- // SAFETY: We are the sole owner of raw_read and it is valid as it was just created.
- let read_fd = unsafe { File::from_raw_fd(raw_read) };
- // SAFETY: We are the sole owner of raw_write and it is valid as it was just created.
- let write_fd = unsafe { File::from_raw_fd(raw_write) };
- Ok((read_fd, write_fd))
+ let (read_fd, write_fd) = pipe2(OFlag::O_CLOEXEC)?;
+ Ok((read_fd.into(), write_fd.into()))
}
fn wait_for_fd_server_ready(mut ready_fd: File) -> Result<()> {
diff --git a/flags/cpp/include/android/avf_cc_flags.h b/flags/cpp/include/android/avf_cc_flags.h
index 536ea9f..c922266 100644
--- a/flags/cpp/include/android/avf_cc_flags.h
+++ b/flags/cpp/include/android/avf_cc_flags.h
@@ -27,5 +27,13 @@
#endif
}
+inline bool IsVendorModulesFlagEnabled() {
+#ifdef AVF_ENABLE_VENDOR_MODULES
+ return AVF_ENABLE_VENDOR_MODULES;
+#else
+ return false;
+#endif
+}
+
} // namespace virtualization
} // namespace android
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index d746c7c..51b91ca 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -63,11 +63,13 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.view.MotionEvent;
import android.system.virtualizationcommon.DeathReason;
import android.system.virtualizationcommon.ErrorCode;
import android.system.virtualizationservice.IVirtualMachine;
import android.system.virtualizationservice.IVirtualMachineCallback;
import android.system.virtualizationservice.IVirtualizationService;
+import android.system.virtualizationservice.InputDevice;
import android.system.virtualizationservice.MemoryTrimLevel;
import android.system.virtualizationservice.PartitionType;
import android.system.virtualizationservice.VirtualMachineAppConfig;
@@ -79,6 +81,8 @@
import com.android.internal.annotations.GuardedBy;
import com.android.system.virtualmachine.flags.Flags;
+import libcore.io.IoBridge;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -89,6 +93,8 @@
import java.io.OutputStream;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
import java.nio.channels.FileChannel;
import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileVisitResult;
@@ -154,6 +160,8 @@
@SuppressLint("MinMaxConstant") // Won't change: see man 7 vsock.
public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
+ private ParcelFileDescriptor mTouchSock;
+
/**
* Status of a virtual machine
*
@@ -431,11 +439,6 @@
VirtualMachineConfig config = VirtualMachineConfig.from(vmDescriptor.getConfigFd());
vm = new VirtualMachine(context, name, config, VirtualizationService.getInstance());
config.serialize(vm.mConfigFilePath);
- if (vm.mInstanceIdPath != null) {
- vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
- vm.claimInstance();
- }
-
try {
vm.mInstanceFilePath.createNewFile();
} catch (IOException e) {
@@ -452,12 +455,16 @@
}
vm.importEncryptedStoreFrom(vmDescriptor.getEncryptedStoreFd());
}
+ if (vm.mInstanceIdPath != null) {
+ vm.importInstanceIdFrom(vmDescriptor.getInstanceIdFd());
+ vm.claimInstance();
+ }
}
return vm;
} catch (VirtualMachineException | RuntimeException e) {
// If anything goes wrong, delete any files created so far and the VM's directory
try {
- vmInstanceCleanup(context, name);
+ deleteRecursively(vmDir);
} catch (Exception innerException) {
e.addSuppressed(innerException);
}
@@ -850,9 +857,73 @@
createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)
throws IllegalStateException, IOException {
VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig();
+
+ // Handle input devices here
+ List<InputDevice> inputDevices = new ArrayList<>();
+ if (vmConfig.getCustomImageConfig() != null
+ && vmConfig.getCustomImageConfig().useTouch()
+ && rawConfig.displayConfig != null) {
+ ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+ mTouchSock = pfds[0];
+ InputDevice.SingleTouch t = new InputDevice.SingleTouch();
+ t.width = rawConfig.displayConfig.width;
+ t.height = rawConfig.displayConfig.height;
+ t.pfd = pfds[1];
+ inputDevices.add(InputDevice.singleTouch(t));
+ }
+ rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]);
+
return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig);
}
+ private void addInputEvent(ByteBuffer buffer, short type, short code, int value) {
+ buffer.putShort(type);
+ buffer.putShort(code);
+ buffer.putInt(value);
+ }
+
+ /** @hide */
+ public boolean sendSingleTouchEvent(MotionEvent event) {
+ if (mTouchSock == null) {
+ Log.d(TAG, "mTouchSock == null");
+ return false;
+ }
+ // from include/uapi/linux/input-event-codes.h in the kernel.
+ short EV_SYN = 0x00;
+ short EV_ABS = 0x03;
+ short EV_KEY = 0x01;
+ short BTN_TOUCH = 0x14a;
+ short ABS_X = 0x00;
+ short ABS_Y = 0x01;
+ short SYN_REPORT = 0x00;
+
+ int x = (int) event.getX();
+ int y = (int) event.getY();
+ boolean down = event.getAction() != MotionEvent.ACTION_UP;
+
+ ByteBuffer byteBuffer =
+ ByteBuffer.allocate(32 /* (type: u16 + code: u16 + value: i32) * 4 */);
+ byteBuffer.clear();
+ byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
+
+ addInputEvent(byteBuffer, EV_ABS, ABS_X, x);
+ addInputEvent(byteBuffer, EV_ABS, ABS_Y, y);
+ addInputEvent(byteBuffer, EV_KEY, BTN_TOUCH, down ? 1 : 0);
+ addInputEvent(byteBuffer, EV_SYN, SYN_REPORT, 0);
+
+ try {
+ IoBridge.write(
+ mTouchSock.getFileDescriptor(),
+ byteBuffer.array(),
+ 0,
+ byteBuffer.array().length);
+ } catch (IOException e) {
+ Log.d(TAG, "cannot send touch evt", e);
+ return false;
+ }
+ return true;
+ }
+
private android.system.virtualizationservice.VirtualMachineConfig
createVirtualMachineConfigForAppFrom(
VirtualMachineConfig vmConfig, IVirtualizationService service)
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 89df1f2..7cf5893 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -34,6 +34,7 @@
private static final String KEY_DISK_WRITABLES = "disk_writables";
private static final String KEY_DISK_IMAGES = "disk_images";
private static final String KEY_DISPLAY_CONFIG = "display_config";
+ private static final String KEY_TOUCH = "touch";
@Nullable private final String name;
@NonNull private final String kernelPath;
@@ -42,6 +43,7 @@
@Nullable private final String[] params;
@Nullable private final Disk[] disks;
@Nullable private final DisplayConfig displayConfig;
+ private final boolean touch;
@Nullable
public Disk[] getDisks() {
@@ -73,6 +75,10 @@
return params;
}
+ public boolean useTouch() {
+ return touch;
+ }
+
/** @hide */
public VirtualMachineCustomImageConfig(
String name,
@@ -81,7 +87,8 @@
String bootloaderPath,
String[] params,
Disk[] disks,
- DisplayConfig displayConfig) {
+ DisplayConfig displayConfig,
+ boolean touch) {
this.name = name;
this.kernelPath = kernelPath;
this.initrdPath = initrdPath;
@@ -89,6 +96,7 @@
this.params = params;
this.disks = disks;
this.displayConfig = displayConfig;
+ this.touch = touch;
}
static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -116,7 +124,7 @@
PersistableBundle displayConfigPb =
customImageConfigBundle.getPersistableBundle(KEY_DISPLAY_CONFIG);
builder.setDisplayConfig(DisplayConfig.from(displayConfigPb));
-
+ builder.useTouch(customImageConfigBundle.getBoolean(KEY_TOUCH));
return builder.build();
}
@@ -145,6 +153,7 @@
Optional.ofNullable(displayConfig)
.map(dc -> dc.toPersistableBundle())
.orElse(null));
+ pb.putBoolean(KEY_TOUCH, touch);
return pb;
}
@@ -193,6 +202,7 @@
private List<String> params = new ArrayList<>();
private List<Disk> disks = new ArrayList<>();
private DisplayConfig displayConfig;
+ private boolean touch;
/** @hide */
public Builder() {}
@@ -240,6 +250,12 @@
}
/** @hide */
+ public Builder useTouch(boolean touch) {
+ this.touch = touch;
+ return this;
+ }
+
+ /** @hide */
public VirtualMachineCustomImageConfig build() {
return new VirtualMachineCustomImageConfig(
this.name,
@@ -248,7 +264,8 @@
this.bootloaderPath,
this.params.toArray(new String[0]),
this.disks.toArray(new Disk[0]),
- displayConfig);
+ displayConfig,
+ touch);
}
}
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
index 8c0c20e..4a9e943 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -38,9 +38,11 @@
import com.android.internal.annotations.GuardedBy;
import com.android.system.virtualmachine.flags.Flags;
+import java.io.File;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.ref.WeakReference;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
@@ -357,6 +359,30 @@
return null;
}
+ private static final String JSON_SUFFIX = ".json";
+ private static final List<String> SUPPORTED_OS_LIST_FROM_CFG =
+ extractSupportedOSListFromConfig();
+
+ private boolean isVendorModuleEnabled() {
+ return VirtualizationService.nativeIsVendorModulesFlagEnabled();
+ }
+
+ private static List<String> extractSupportedOSListFromConfig() {
+ List<String> supportedOsList = new ArrayList<>();
+ File directory = new File("/apex/com.android.virt/etc");
+ File[] files = directory.listFiles();
+ if (files != null) {
+ for (File file : files) {
+ String fileName = file.getName();
+ if (fileName.endsWith(JSON_SUFFIX)) {
+ supportedOsList.add(
+ fileName.substring(0, fileName.length() - JSON_SUFFIX.length()));
+ }
+ }
+ }
+ return supportedOsList;
+ }
+
/**
* Returns a list of supported OS names.
*
@@ -366,13 +392,10 @@
@FlaggedApi(Flags.FLAG_AVF_V_TEST_APIS)
@NonNull
public List<String> getSupportedOSList() throws VirtualMachineException {
- synchronized (sCreateLock) {
- VirtualizationService service = VirtualizationService.getInstance();
- try {
- return Arrays.asList(service.getBinder().getSupportedOSList());
- } catch (RemoteException e) {
- throw e.rethrowAsRuntimeException();
- }
+ if (isVendorModuleEnabled()) {
+ return SUPPORTED_OS_LIST_FROM_CFG;
+ } else {
+ return Arrays.asList("microdroid");
}
}
diff --git a/java/framework/src/android/system/virtualmachine/VirtualizationService.java b/java/framework/src/android/system/virtualmachine/VirtualizationService.java
index 57990a9..9063fa6 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualizationService.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualizationService.java
@@ -51,6 +51,12 @@
private native boolean nativeIsOk(int clientFd);
/*
+ * Retrieve boolean value whether RELEASE_AVF_ENABLE_VENDOR_MODULES build flag is enabled or
+ * not.
+ */
+ static native boolean nativeIsVendorModulesFlagEnabled();
+
+ /*
* Spawns a new virtmgr subprocess that will host a VirtualizationService
* AIDL service.
*/
@@ -63,7 +69,9 @@
IBinder binder = nativeConnect(mClientFd.getFd());
if (binder == null) {
- throw new VirtualMachineException("Could not connect to Virtualization Manager");
+ throw new SecurityException(
+ "Could not connect to Virtualization Manager. Please consider checking"
+ + " android.permission.MANAGE_VIRTUAL_MACHINE permission");
}
mBinder = IVirtualizationService.Stub.asInterface(binder);
}
diff --git a/java/jni/Android.bp b/java/jni/Android.bp
index 74a1766..4a569d4 100644
--- a/java/jni/Android.bp
+++ b/java/jni/Android.bp
@@ -16,6 +16,7 @@
"liblog",
"libnativehelper",
],
+ static_libs: ["libavf_cc_flags"],
}
cc_library_shared {
diff --git a/java/jni/android_system_virtualmachine_VirtualizationService.cpp b/java/jni/android_system_virtualmachine_VirtualizationService.cpp
index fbd1fd5..4f02112 100644
--- a/java/jni/android_system_virtualmachine_VirtualizationService.cpp
+++ b/java/jni/android_system_virtualmachine_VirtualizationService.cpp
@@ -17,6 +17,7 @@
#define LOG_TAG "VirtualizationService"
#include <android-base/unique_fd.h>
+#include <android/avf_cc_flags.h>
#include <android/binder_ibinder_jni.h>
#include <jni.h>
#include <log/log.h>
@@ -101,3 +102,9 @@
}
return pfds[0].revents == 0;
}
+
+extern "C" JNIEXPORT jboolean JNICALL
+Java_android_system_virtualmachine_VirtualizationService_nativeIsVendorModulesFlagEnabled(
+ [[maybe_unused]] JNIEnv* env, [[maybe_unused]] jobject obj) {
+ return android::virtualization::IsVendorModulesFlagEnabled();
+}
diff --git a/libs/android_display_backend/Android.bp b/libs/android_display_backend/Android.bp
index 6ad5fab..32587dd 100644
--- a/libs/android_display_backend/Android.bp
+++ b/libs/android_display_backend/Android.bp
@@ -11,6 +11,9 @@
backend: {
java: {
enabled: true,
+ apex_available: [
+ "com.android.virt",
+ ],
},
cpp: {
enabled: false,
@@ -42,7 +45,6 @@
"android.system.virtualizationservice_internal-ndk",
"android.system.virtualizationcommon-ndk",
"android.system.virtualizationservice-ndk",
- "libyuv",
],
shared_libs: [
"libbinder_ndk",
diff --git a/libs/android_display_backend/crosvm_android_display_client.cpp b/libs/android_display_backend/crosvm_android_display_client.cpp
index a16b7f2..66320f3 100644
--- a/libs/android_display_backend/crosvm_android_display_client.cpp
+++ b/libs/android_display_backend/crosvm_android_display_client.cpp
@@ -18,38 +18,27 @@
#include <aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.h>
#include <android/binder_manager.h>
#include <android/binder_process.h>
-#include <android/native_window.h>
-#include <android/native_window_aidl.h>
-#include <libyuv.h>
-#include <stdint.h>
-#include <utils/Errors.h>
+#include <system/graphics.h> // for HAL_PIXEL_FORMAT_*
#include <condition_variable>
#include <memory>
#include <mutex>
-#include <vector>
+using aidl::android::crosvm::BnCrosvmAndroidDisplayService;
using aidl::android::system::virtualizationservice_internal::IVirtualizationServiceInternal;
-
-#define LIBEXPORT __attribute__((visibility("default"))) extern "C"
-
-typedef void (*android_display_log_callback_type)(const char* message);
-
-static void android_display_log_callback_stub(const char* message) {
- (void)message;
-}
+using aidl::android::view::Surface;
namespace {
-class DisplayService : public aidl::android::crosvm::BnCrosvmAndroidDisplayService {
+class DisplayService : public BnCrosvmAndroidDisplayService {
public:
DisplayService() = default;
virtual ~DisplayService() = default;
- ndk::ScopedAStatus setSurface(aidl::android::view::Surface* surface) override {
+ ndk::ScopedAStatus setSurface(Surface* surface) override {
{
std::lock_guard lk(mSurfaceReadyMutex);
- mSurface = std::make_unique<aidl::android::view::Surface>(surface->release());
+ mSurface = std::make_unique<Surface>(surface->release());
}
mSurfaceReady.notify_one();
return ::ndk::ScopedAStatus::ok();
@@ -64,7 +53,7 @@
return ::ndk::ScopedAStatus::ok();
}
- aidl::android::view::Surface* getSurface() {
+ Surface* getSurface() {
std::unique_lock lk(mSurfaceReadyMutex);
mSurfaceReady.wait(lk, [this] { return mSurface != nullptr; });
return mSurface.get();
@@ -73,214 +62,116 @@
private:
std::condition_variable mSurfaceReady;
std::mutex mSurfaceReadyMutex;
- std::unique_ptr<aidl::android::view::Surface> mSurface;
+ std::unique_ptr<Surface> mSurface;
};
-void ErrorF(android_display_log_callback_type error_callback, const char* format, ...) {
- char buffer[1024];
-
- va_list vararg;
- va_start(vararg, format);
- vsnprintf(buffer, sizeof(buffer), format, vararg);
- va_end(vararg);
-
- error_callback(buffer);
-}
-
} // namespace
-struct android_display_context {
- uint32_t width;
- uint32_t height;
- std::shared_ptr<DisplayService> displayService;
+typedef void (*ErrorCallback)(const char* message);
+
+struct AndroidDisplayContext {
+ std::shared_ptr<IVirtualizationServiceInternal> virt_service;
+ std::shared_ptr<DisplayService> disp_service;
+ ErrorCallback error_callback;
+
+ AndroidDisplayContext(ErrorCallback cb) : error_callback(cb) {
+ auto disp_service = ::ndk::SharedRefBase::make<DisplayService>();
+
+ // Creates DisplayService and register it to the virtualizationservice. This is needed
+ // because this code is executed inside of crosvm which runs as an app. Apps are not allowed
+ // to register a service to the service manager.
+ auto virt_service = IVirtualizationServiceInternal::fromBinder(ndk::SpAIBinder(
+ AServiceManager_waitForService("android.system.virtualizationservice")));
+ if (virt_service == nullptr) {
+ errorf("Failed to find virtualization service");
+ return;
+ }
+ auto status = virt_service->setDisplayService(disp_service->asBinder());
+ if (!status.isOk()) {
+ errorf("Failed to register display service");
+ return;
+ }
+
+ this->virt_service = virt_service;
+ this->disp_service = disp_service;
+ ABinderProcess_startThreadPool();
+ }
+
+ ~AndroidDisplayContext() {
+ if (virt_service == nullptr) {
+ errorf("Not connected to virtualization service");
+ return;
+ }
+ auto status = this->virt_service->clearDisplayService();
+ if (!status.isOk()) {
+ errorf("Failed to clear display service");
+ }
+ }
+
+ void errorf(const char* format, ...) {
+ char buffer[1024];
+
+ va_list vararg;
+ va_start(vararg, format);
+ vsnprintf(buffer, sizeof(buffer), format, vararg);
+ va_end(vararg);
+
+ error_callback(buffer);
+ }
};
-LIBEXPORT
-struct android_display_context* create_android_display_context(
- const char* name, size_t name_len, android_display_log_callback_type error_callback) {
- auto ctx = new android_display_context();
-
- auto service = ::ndk::SharedRefBase::make<DisplayService>();
-
- if (strlen(name) != name_len) {
- ErrorF(error_callback, "Invalid service name length. Expected %u, actual %u", name_len,
- strlen(name));
- return nullptr;
- }
- ::ndk::SpAIBinder binder(
- AServiceManager_waitForService("android.system.virtualizationservice"));
-
- auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
- if (virt_service == nullptr) {
- ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
- return nullptr;
- }
- auto status = virt_service->setDisplayService(service->asBinder());
- if (!status.isOk()) {
- ErrorF(error_callback, "Failed to register %s",
- aidl::android::crosvm::ICrosvmAndroidDisplayService::descriptor);
- return nullptr;
- }
-
- ABinderProcess_startThreadPool();
-
- auto surface = service->getSurface();
- ctx->width = static_cast<uint32_t>(ANativeWindow_getWidth(surface->get()));
- ctx->height = static_cast<uint32_t>(ANativeWindow_getHeight(surface->get()));
- ctx->displayService = service;
- return ctx;
+extern "C" struct AndroidDisplayContext* create_android_display_context(
+ const char*, ErrorCallback error_callback) {
+ return new AndroidDisplayContext(error_callback);
}
-LIBEXPORT
-void destroy_android_display_context(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- auto service = ::ndk::SharedRefBase::make<DisplayService>();
- ::ndk::SpAIBinder binder(
- AServiceManager_waitForService("android.system.virtualizationservice"));
- auto virt_service = IVirtualizationServiceInternal::fromBinder(binder);
- if (virt_service != nullptr) {
- auto status = virt_service->clearDisplayService();
- } else {
- ErrorF(error_callback, "Failed to find android.system.virtualizationservice");
- }
-
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return;
- }
-
+extern "C" void destroy_android_display_context(struct AndroidDisplayContext* ctx) {
delete ctx;
}
-LIBEXPORT
-uint32_t get_android_display_width(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return -1;
+extern "C" ANativeWindow* create_android_surface(struct AndroidDisplayContext* ctx, uint32_t width,
+ uint32_t height) {
+ if (ctx->disp_service == nullptr) {
+ ctx->errorf("Display service was not created");
+ return nullptr;
}
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
- return -1;
+ // Note: crosvm always uses BGRA8888 or BGRX8888. See devices/src/virtio/gpu/mod.rs in crosvm
+ // where the SetScanoutBlob command is handled. Let's use BGRA not BGRX with a hope that we will
+ // need alpha blending for the cursor surface.
+ int format = HAL_PIXEL_FORMAT_BGRA_8888;
+ ANativeWindow* surface = ctx->disp_service->getSurface()->get(); // this can block
+ if (ANativeWindow_setBuffersGeometry(surface, width, height, format) != 0) {
+ ctx->errorf("Failed to set buffer gemoetry");
+ return nullptr;
}
- return ctx->width;
+ // TODO(b/332785161): if we know that surface can get destroyed dynamically while VM is running,
+ // consider calling ANativeWindow_acquire here and _release in destroy_android_surface, so that
+ // crosvm doesn't hold a dangling pointer.
+ return surface;
}
-LIBEXPORT
-uint32_t get_android_display_height(android_display_log_callback_type error_callback,
- struct android_display_context* ctx) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return -1;
- }
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface for ctx:%p.", ctx);
- return -1;
- }
- return ctx->height;
+extern "C" void destroy_android_surface(struct AndroidDisplayContext*, ANativeWindow*) {
+ // NOT IMPLEMENTED
}
-uint16_t RGBA8888ToRGB565(uint8_t r, uint8_t g, uint8_t b, uint8_t a) {
- (void)a;
- return (static_cast<uint16_t>(r >> 3) << 11) | (static_cast<uint16_t>(g >> 2) << 5) |
- (static_cast<uint16_t>(b >> 3) << 0);
+extern "C" bool get_android_surface_buffer(struct AndroidDisplayContext* ctx,
+ ANativeWindow* surface,
+ ANativeWindow_Buffer* out_buffer) {
+ if (out_buffer == nullptr) {
+ ctx->errorf("out_buffer is null");
+ return false;
+ }
+ if (ANativeWindow_lock(surface, out_buffer, nullptr) != 0) {
+ ctx->errorf("Failed to lock buffer");
+ return false;
+ }
+ return true;
}
-LIBEXPORT
-void blit_android_display(android_display_log_callback_type error_callback,
- struct android_display_context* ctx, uint32_t width, uint32_t height,
- uint8_t* pixels, size_t pixels_num_bytes) {
- if (!ctx) {
- ErrorF(error_callback, "Invalid context.");
- return;
- }
- if (!ctx->displayService->getSurface()) {
- ErrorF(error_callback, "Invalid context surface.");
- return;
- }
- if (pixels_num_bytes != width * height * 4) {
- ErrorF(error_callback, "Invalid buffer size.");
- return;
- }
- ANativeWindow* anw = ctx->displayService->getSurface()->get();
- if (!anw) {
- ErrorF(error_callback, "Invalid context surface.");
- return;
- }
-
- ANativeWindow_Buffer anwBuffer = {};
- if (ANativeWindow_lock(anw, &anwBuffer, nullptr) != android::OK) {
- ErrorF(error_callback, "Failed to lock ANativeWindow.");
- return;
- }
-
- // Source is always BGRA8888.
- auto* src = reinterpret_cast<uint32_t*>(pixels);
- auto srcWidth = static_cast<uint32_t>(width);
- auto srcHeight = static_cast<uint32_t>(height);
- auto srcStrideBytes = srcWidth * 4;
- auto srcStridePixels = srcWidth;
-
- auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
- auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
-
- // Scale to fit if needed.
- std::vector<uint32_t> scaledSrc;
- if (srcWidth != dstWidth || srcHeight != dstHeight) {
- const float ratioWidth = static_cast<float>(dstWidth) / static_cast<float>(srcWidth);
- const float ratioHeight = static_cast<float>(dstHeight) / static_cast<float>(srcHeight);
- const float ratioUsed = std::min(ratioWidth, ratioHeight);
-
- uint32_t scaledSrcWidth = static_cast<uint32_t>(static_cast<float>(srcWidth) * ratioUsed);
- uint32_t scaledSrcHeight = static_cast<uint32_t>(static_cast<float>(srcHeight) * ratioUsed);
- uint32_t scaledSrcStrideBytes = scaledSrcWidth * 4;
- uint32_t scaledSrcStridePixels = scaledSrcWidth;
-
- scaledSrc.resize(scaledSrcHeight * scaledSrcStridePixels);
-
- libyuv::ARGBScale(reinterpret_cast<uint8_t*>(src), srcStrideBytes, srcWidth, srcHeight,
- reinterpret_cast<uint8_t*>(scaledSrc.data()), scaledSrcStrideBytes,
- scaledSrcWidth, scaledSrcHeight, libyuv::kFilterBilinear);
-
- src = scaledSrc.data();
- srcWidth = scaledSrcWidth;
- srcHeight = scaledSrcHeight;
- srcStrideBytes = scaledSrcStrideBytes;
- srcStridePixels = scaledSrcStridePixels;
- }
-
- if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM) {
- auto* dst = reinterpret_cast<uint32_t*>(anwBuffer.bits);
- auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
-
- for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
- for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
- dst[(h * dstStridePixels) + w] = src[(h * srcStridePixels) + w];
- }
- }
- } else if (anwBuffer.format == AHARDWAREBUFFER_FORMAT_R5G6B5_UNORM) {
- auto* dst = reinterpret_cast<uint16_t*>(anwBuffer.bits);
- auto dstWidth = static_cast<uint32_t>(anwBuffer.width);
- auto dstHeight = static_cast<uint32_t>(anwBuffer.height);
- auto dstStridePixels = static_cast<uint32_t>(anwBuffer.stride);
-
- for (uint32_t h = 0; h < std::min(srcHeight, dstHeight); h++) {
- for (uint32_t w = 0; w < std::min(srcWidth, dstWidth); w++) {
- uint32_t srcPixel = src[(h * srcStridePixels) + w];
- uint8_t* srcPixelBytes = reinterpret_cast<uint8_t*>(&srcPixel);
- uint8_t r = srcPixelBytes[2];
- uint8_t g = srcPixelBytes[1];
- uint8_t b = srcPixelBytes[0];
- uint8_t a = srcPixelBytes[3];
- dst[(h * dstStridePixels) + w] = RGBA8888ToRGB565(r, g, b, a);
- }
- }
- } else {
- ErrorF(error_callback, "Unhandled format: %d", anwBuffer.format);
- }
-
- if (ANativeWindow_unlockAndPost(anw) != android::OK) {
- ErrorF(error_callback, "Failed to unlock and post ANativeWindow.");
+extern "C" void post_android_surface_buffer(struct AndroidDisplayContext* ctx,
+ ANativeWindow* surface) {
+ if (ANativeWindow_unlockAndPost(surface) != 0) {
+ ctx->errorf("Failed to unlock and post surface.");
return;
}
}
diff --git a/microdroid/README.md b/microdroid/README.md
index baf41b0..c0cba97 100644
--- a/microdroid/README.md
+++ b/microdroid/README.md
@@ -41,7 +41,7 @@
## Building an app
A [vm
-payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/vm_payload/)
+payload](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/vm_payload/)
is a shared library file that gets executed in microdroid. It is packaged as
part of an Android application. The library should have an entry point
`AVmPayload_main` as shown below:
@@ -132,12 +132,12 @@
### Using the APIs
Use the [Android Virtualization Framework Java
-APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/javalib/api/system-current.txt)
+APIs](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/README.md)
in your app to create a microdroid VM and run payload in it. The APIs are currently
@SystemApi, and only available to preinstalled apps.
If you are looking for an example usage of the APIs, you may refer to the [demo
-app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/demo/).
+app](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/demo/).
## Running Microdroid with vendor image
diff --git a/microdroid/kernel/README.md b/microdroid/kernel/README.md
index 92b7cfe..52df333 100644
--- a/microdroid/kernel/README.md
+++ b/microdroid/kernel/README.md
@@ -29,7 +29,7 @@
```
Note that
-[`--config=fast`](https://android.googlesource.com/kernel/build/+/refs/heads/master/kleaf/docs/fast.md)
+[`--config=fast`](https://android.googlesource.com/kernel/build/+/refs/heads/main/kleaf/docs/fast.md)
is not mandatory, but will make your build much faster.
The build may fail in case you are doing an incremental build and the config has changed (b/257288175). Until that issue
diff --git a/service_vm/manager/src/lib.rs b/service_vm/manager/src/lib.rs
index 273b54d..987325d 100644
--- a/service_vm/manager/src/lib.rs
+++ b/service_vm/manager/src/lib.rs
@@ -30,7 +30,6 @@
use service_vm_comm::{Request, Response, ServiceVmRequest, VmType};
use std::fs::{self, File, OpenOptions};
use std::io::{self, BufRead, BufReader, BufWriter, Read, Write};
-use std::os::unix::io::FromRawFd;
use std::path::{Path, PathBuf};
use std::sync::{Condvar, Mutex};
use std::thread;
@@ -294,10 +293,8 @@
pub fn android_log_fd() -> io::Result<File> {
let (reader_fd, writer_fd) = nix::unistd::pipe()?;
- // SAFETY: These are new FDs with no previous owner.
- let reader = unsafe { File::from_raw_fd(reader_fd) };
- // SAFETY: These are new FDs with no previous owner.
- let writer = unsafe { File::from_raw_fd(writer_fd) };
+ let reader = File::from(reader_fd);
+ let writer = File::from(writer_fd);
thread::spawn(|| {
for line in BufReader::new(reader).lines() {
diff --git a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
index 6c82de8..f881909 100644
--- a/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/device/MicrodroidDeviceTestBase.java
@@ -155,6 +155,12 @@
public VirtualMachine forceCreateNewVirtualMachine(String name, VirtualMachineConfig config)
throws VirtualMachineException {
final VirtualMachineManager vmm = getVirtualMachineManager();
+ deleteVirtualMachineIfExists(name);
+ return vmm.create(name, config);
+ }
+
+ protected void deleteVirtualMachineIfExists(String name) throws VirtualMachineException {
+ VirtualMachineManager vmm = getVirtualMachineManager();
boolean deleteExisting;
try {
deleteExisting = vmm.get(name) != null;
@@ -166,7 +172,6 @@
if (deleteExisting) {
vmm.delete(name);
}
- return vmm.create(name, config);
}
public void prepareTestSetup(boolean protectedVm, String gki) {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 2b53571..2b5c564 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -828,6 +828,11 @@
AtomsProto.VmExited atomVmExited = data.get(2).getAtom().getVmExited();
assertThat(atomVmExited.getVmIdentifier()).isEqualTo("VmRunApp");
assertThat(atomVmExited.getDeathReason()).isEqualTo(AtomsProto.VmExited.DeathReason.KILLED);
+ assertThat(atomVmExited.getExitSignal()).isEqualTo(9);
+ // In CPU & memory related fields, check whether positive values are collected or not.
+ assertThat(atomVmExited.getGuestTimeMillis()).isGreaterThan(0);
+ assertThat(atomVmExited.getRssVmKb()).isGreaterThan(0);
+ assertThat(atomVmExited.getRssCrosvmKb()).isGreaterThan(0);
// Check UID and elapsed_time by comparing each other.
assertThat(atomVmBooted.getUid()).isEqualTo(atomVmCreationRequested.getUid());
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index 8f4df63..29e9014 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -37,6 +37,7 @@
import static org.junit.Assume.assumeTrue;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
+import static java.util.stream.Collectors.toList;
import android.app.Instrumentation;
import android.app.UiAutomation;
@@ -110,6 +111,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
+import java.util.stream.Stream;
import co.nstant.in.cbor.CborDecoder;
import co.nstant.in.cbor.model.Array;
@@ -1055,6 +1057,51 @@
changeDebugLevel(DEBUG_LEVEL_NONE, DEBUG_LEVEL_FULL);
}
+ // Copy the Vm directory, creating the target Vm directory if it does not already exist.
+ private void copyVmDirectory(String sourceVmName, String targetVmName) throws IOException {
+ Path sourceVm = getVmDirectory(sourceVmName);
+ Path targetVm = getVmDirectory(targetVmName);
+ if (!Files.exists(targetVm)) {
+ Files.createDirectories(targetVm);
+ }
+
+ try (Stream<Path> stream = Files.list(sourceVm)) {
+ for (Path f : stream.collect(toList())) {
+ Files.copy(f, targetVm.resolve(f.getFileName()), REPLACE_EXISTING);
+ }
+ }
+ }
+
+ private Path getVmDirectory(String vmName) {
+ Context context = getContext();
+ Path filePath = Paths.get(context.getDataDir().getPath(), "vm", vmName);
+ return filePath;
+ }
+
+ // Create a fresh VM with the given `vmName`, instance_id & instance.img. This function creates
+ // a Vm with a different temporary name & copies it to target VM directory. This ensures this
+ // VM is not in cache of `VirtualMachineManager` which makes it possible to modify underlying
+ // files.
+ private void createUncachedVmWithName(
+ String vmName, VirtualMachineConfig config, File vmIdBackup, File vmInstanceBackup)
+ throws Exception {
+ deleteVirtualMachineIfExists(vmName);
+ forceCreateNewVirtualMachine("test_vm_tmp", config);
+ copyVmDirectory("test_vm_tmp", vmName);
+ if (vmInstanceBackup != null) {
+ Files.copy(
+ vmInstanceBackup.toPath(),
+ getVmFile(vmName, "instance.img").toPath(),
+ REPLACE_EXISTING);
+ }
+ if (vmIdBackup != null) {
+ Files.copy(
+ vmIdBackup.toPath(),
+ getVmFile(vmName, "instance_id").toPath(),
+ REPLACE_EXISTING);
+ }
+ }
+
@Test
@CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-7"})
public void changingDebuggableVmNonDebuggableInvalidatesVmIdentity() throws Exception {
@@ -1089,29 +1136,17 @@
Files.copy(vmId.toPath(), vmIdBackup.toPath(), REPLACE_EXISTING);
}
- forceCreateNewVirtualMachine("test_vm", normalConfig);
-
- if (vmInstanceBackup != null) {
- Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
- }
- if (vmIdBackup != null) {
- Files.copy(vmIdBackup.toPath(), vmId.toPath(), REPLACE_EXISTING);
- }
- assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
+ createUncachedVmWithName("test_vm_rerun", normalConfig, vmIdBackup, vmInstanceBackup);
+ assertThat(tryBootVm(TAG, "test_vm_rerun").payloadStarted).isTrue();
// Launch the same VM with a different debug level. The Java API prohibits this
// (thankfully).
// For testing, we do that by creating a new VM with debug level, and overwriting the old
// instance data to the new VM instance data.
VirtualMachineConfig debugConfig = builder.setDebugLevel(toLevel).build();
- forceCreateNewVirtualMachine("test_vm", debugConfig);
- if (vmInstanceBackup != null) {
- Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
- }
- if (vmIdBackup != null) {
- Files.copy(vmIdBackup.toPath(), vmId.toPath(), REPLACE_EXISTING);
- }
- assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
+ createUncachedVmWithName(
+ "test_vm_changed_debug_level", debugConfig, vmIdBackup, vmInstanceBackup);
+ assertThat(tryBootVm(TAG, "test_vm_changed_debug_level").payloadStarted).isFalse();
}
private static class VmCdis {
@@ -1555,7 +1590,6 @@
assertFileContentsAreEqualInTwoVms("storage.img", vmNameOrig, vmNameImport);
}
assertThat(vmImport).isNotEqualTo(vmOrig);
- vmm.delete(vmNameOrig);
assertThat(vmImport).isEqualTo(vmm.get(vmNameImport));
TestResults testResults =
runVmTestService(
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 279b4ec..c5f1ab7 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -17,7 +17,7 @@
use crate::{get_calling_pid, get_calling_uid};
use crate::atom::{write_vm_booted_stats, write_vm_creation_stats};
use crate::composite::make_composite_image;
-use crate::crosvm::{CrosvmConfig, DiskFile, DisplayConfig, PayloadState, VmContext, VmInstance, VmState};
+use crate::crosvm::{CrosvmConfig, DiskFile, DisplayConfig, InputDeviceOption, PayloadState, VmContext, VmInstance, VmState};
use crate::debug_config::DebugConfig;
use crate::dt_overlay::{create_device_tree_overlay, VM_DT_OVERLAY_MAX_SIZE, VM_DT_OVERLAY_PATH};
use crate::payload::{add_microdroid_payload_images, add_microdroid_system_images, add_microdroid_vendor_image};
@@ -32,6 +32,7 @@
AssignableDevice::AssignableDevice,
CpuTopology::CpuTopology,
DiskImage::DiskImage,
+ InputDevice::InputDevice,
IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
IVirtualMachineCallback::IVirtualMachineCallback,
IVirtualizationService::IVirtualizationService,
@@ -581,13 +582,27 @@
} else {
(vec![], None)
};
+ let display_config = if cfg!(paravirtualized_devices) {
+ config
+ .displayConfig
+ .as_ref()
+ .map(DisplayConfig::new)
+ .transpose()
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
+ } else {
+ None
+ };
- let display_config = config
- .displayConfig
- .as_ref()
- .map(DisplayConfig::new)
- .transpose()
- .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?;
+ let input_device_options = if cfg!(paravirtualized_devices) {
+ config
+ .inputDevices
+ .iter()
+ .map(to_input_device_option_from)
+ .collect::<Result<Vec<InputDeviceOption>, _>>()
+ .or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT)?
+ } else {
+ vec![]
+ };
// Actually start the VM.
let crosvm_config = CrosvmConfig {
@@ -615,6 +630,7 @@
dtbo,
device_tree_overlay,
display_config,
+ input_device_options,
};
let instance = Arc::new(
VmInstance::new(
@@ -720,6 +736,23 @@
(result / granularity) * granularity
}
+fn to_input_device_option_from(input_device: &InputDevice) -> Result<InputDeviceOption> {
+ Ok(match input_device {
+ InputDevice::SingleTouch(single_touch) => InputDeviceOption::SingleTouch {
+ file: clone_file(single_touch.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?)?,
+ height: u32::try_from(single_touch.height)?,
+ width: u32::try_from(single_touch.width)?,
+ name: if !single_touch.name.is_empty() {
+ Some(single_touch.name.clone())
+ } else {
+ None
+ },
+ },
+ InputDevice::EvDev(evdev) => InputDeviceOption::EvDev(clone_file(
+ evdev.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
+ )?),
+ })
+}
/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
///
/// This may involve assembling a composite disk from a set of partition images.
@@ -1410,13 +1443,11 @@
return Ok(None);
};
- let (raw_read_fd, raw_write_fd) =
+ let (read_fd, write_fd) =
pipe().context("Failed to create pipe").or_service_specific_exception(-1)?;
- // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
- let mut reader = BufReader::new(unsafe { File::from_raw_fd(raw_read_fd) });
- // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
- let write_fd = unsafe { File::from_raw_fd(raw_write_fd) };
+ let mut reader = BufReader::new(File::from(read_fd));
+ let write_fd = File::from(write_fd);
std::thread::spawn(move || loop {
let mut buf = vec![];
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 86c9af3..4be48a5 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -34,7 +34,7 @@
use std::io::{self, Read};
use std::mem;
use std::num::{NonZeroU16, NonZeroU32};
-use std::os::unix::io::{AsRawFd, RawFd, FromRawFd};
+use std::os::unix::io::{AsRawFd, RawFd};
use std::os::unix::process::ExitStatusExt;
use std::path::{Path, PathBuf};
use std::process::{Command, ExitStatus};
@@ -120,6 +120,7 @@
pub dtbo: Option<File>,
pub device_tree_overlay: Option<File>,
pub display_config: Option<DisplayConfig>,
+ pub input_device_options: Vec<InputDeviceOption>,
}
#[derive(Debug)]
@@ -154,6 +155,14 @@
pub writable: bool,
}
+/// virtio-input device configuration from `external/crosvm/src/crosvm/config.rs`
+#[derive(Debug)]
+#[allow(dead_code)]
+pub enum InputDeviceOption {
+ EvDev(File),
+ SingleTouch { file: File, width: u32, height: u32, name: Option<String> },
+}
+
type VfioDevice = Strong<dyn IBoundDevice>;
/// The lifecycle state which the payload in the VM has reported itself to be in.
@@ -955,14 +964,34 @@
if let Some(dt_overlay) = &config.device_tree_overlay {
command.arg("--device-tree-overlay").arg(add_preserved_fd(&mut preserved_fds, dt_overlay));
}
- if let Some(display_config) = &config.display_config {
- command.arg("--gpu")
- // TODO(b/331708504): support backend config as well
- .arg("backend=virglrenderer,context-types=virgl2,egl=true,surfaceless=true,glx=false,gles=true")
- .arg(format!("--gpu-display=mode=windowed[{},{}],dpi=[{},{}],refresh-rate={}", display_config.width, display_config.height, display_config.horizontal_dpi, display_config.vertical_dpi, display_config.refresh_rate))
- .arg(format!("--android-display-service={}", config.name));
+
+ if cfg!(paravirtualized_devices) {
+ if let Some(display_config) = &config.display_config {
+ command.arg("--gpu")
+ // TODO(b/331708504): support backend config as well
+ .arg("backend=virglrenderer,context-types=virgl2,egl=true,surfaceless=true,glx=false,gles=true")
+ .arg(format!("--gpu-display=mode=windowed[{},{}],dpi=[{},{}],refresh-rate={}", display_config.width, display_config.height, display_config.horizontal_dpi, display_config.vertical_dpi, display_config.refresh_rate))
+ .arg(format!("--android-display-service={}", config.name));
+ }
}
+ if cfg!(paravirtualized_devices) {
+ for input_device_option in config.input_device_options.iter() {
+ command.arg("--input");
+ command.arg(match input_device_option {
+ InputDeviceOption::EvDev(file) => {
+ format!("evdev[path={}]", add_preserved_fd(&mut preserved_fds, file))
+ }
+ InputDeviceOption::SingleTouch { file, width, height, name } => format!(
+ "single-touch[path={},width={},height={}{}]",
+ add_preserved_fd(&mut preserved_fds, file),
+ width,
+ height,
+ name.as_ref().map_or("".into(), |n| format!(",name={}", n))
+ ),
+ });
+ }
+ }
append_platform_devices(&mut command, &mut preserved_fds, &config)?;
debug!("Preserving FDs {:?}", preserved_fds);
@@ -1041,10 +1070,6 @@
/// Creates a new pipe with the `O_CLOEXEC` flag set, and returns the read side and write side.
fn create_pipe() -> Result<(File, File), Error> {
- let (raw_read, raw_write) = pipe2(OFlag::O_CLOEXEC)?;
- // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
- let read_fd = unsafe { File::from_raw_fd(raw_read) };
- // SAFETY: We are the sole owner of this FD as we just created it, and it is valid and open.
- let write_fd = unsafe { File::from_raw_fd(raw_write) };
- Ok((read_fd, write_fd))
+ let (read_fd, write_fd) = pipe2(OFlag::O_CLOEXEC)?;
+ Ok((read_fd.into(), write_fd.into()))
}
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index c479691..fb89772 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -48,7 +48,7 @@
java: {
sdk_version: "module_current",
apex_available: [
- "//apex_available:platform",
+ "com.android.virt",
],
},
rust: {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
new file mode 100644
index 0000000..fe12291
--- /dev/null
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright 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 android.system.virtualizationservice;
+
+// Refer to https://crosvm.dev/book/devices/input.html
+union InputDevice {
+ // Add a single-touch touchscreen virtio-input device.
+ parcelable SingleTouch {
+ ParcelFileDescriptor pfd;
+ // Default values come from https://crosvm.dev/book/devices/input.html#single-touch
+ int width = 1280;
+ int height = 1080;
+ @utf8InCpp String name = "";
+ }
+ // Passes an event device node into the VM. The device will be grabbed (unusable from the host)
+ // and made available to the guest with the same configuration it shows on the host.
+ parcelable EvDev {
+ ParcelFileDescriptor pfd;
+ }
+
+ SingleTouch singleTouch;
+ EvDev evDev;
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
index 1a18bf8..86e26da 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/VirtualMachineRawConfig.aidl
@@ -18,6 +18,7 @@
import android.system.virtualizationservice.CpuTopology;
import android.system.virtualizationservice.DiskImage;
import android.system.virtualizationservice.DisplayConfig;
+import android.system.virtualizationservice.InputDevice;
/** Raw configuration for running a VM. */
parcelable VirtualMachineRawConfig {
@@ -73,4 +74,7 @@
String[] devices;
@nullable DisplayConfig displayConfig;
+
+ /** List of input devices to the VM */
+ InputDevice[] inputDevices;
}
diff --git a/virtualizationservice/src/maintenance.rs b/virtualizationservice/src/maintenance.rs
index f950db9..8efc58d 100644
--- a/virtualizationservice/src/maintenance.rs
+++ b/virtualizationservice/src/maintenance.rs
@@ -40,6 +40,9 @@
/// parcel fits within max AIDL message size.
const DELETE_MAX_BATCH_SIZE: usize = 100;
+/// Maximum number of VM IDs that a single app can have.
+const MAX_VM_IDS_PER_APP: usize = 400;
+
/// State related to VM secrets.
pub struct State {
sk: binder::Strong<dyn ISecretkeeper>,
@@ -101,6 +104,24 @@
pub fn add_id(&mut self, vm_id: &VmId, user_id: u32, app_id: u32) -> Result<()> {
let user_id: i32 = user_id.try_into().context(format!("user_id {user_id} out of range"))?;
let app_id: i32 = app_id.try_into().context(format!("app_id {app_id} out of range"))?;
+
+ // To prevent unbounded growth of VM IDs (and the associated state) for an app, limit the
+ // number of VM IDs per app.
+ let count = self
+ .vm_id_db
+ .count_vm_ids_for_app(user_id, app_id)
+ .context("failed to determine VM count")?;
+ if count >= MAX_VM_IDS_PER_APP {
+ // The owner has too many VM IDs, so delete the oldest IDs so that the new VM ID
+ // creation can progress/succeed.
+ let purge = 1 + count - MAX_VM_IDS_PER_APP;
+ let old_vm_ids = self
+ .vm_id_db
+ .oldest_vm_ids_for_app(user_id, app_id, purge)
+ .context("failed to find oldest VM IDs")?;
+ error!("Deleting {purge} of {count} VM IDs for user_id={user_id}, app_id={app_id}");
+ self.delete_ids(&old_vm_ids);
+ }
self.vm_id_db.add_vm_id(vm_id, user_id, app_id)
}
@@ -396,6 +417,39 @@
assert_eq!(vec![VM_ID5], sk_state.vm_id_db.vm_ids_for_user(USER3).unwrap());
}
+ #[test]
+ fn test_sk_state_too_many_vms() {
+ let history = Arc::new(Mutex::new(Vec::new()));
+ let mut sk_state = new_test_state(history.clone(), 20);
+
+ // Every VM ID added up to the limit is kept.
+ for idx in 0..MAX_VM_IDS_PER_APP {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ sk_state.add_id(&vm_id, USER1 as u32, APP_A as u32).unwrap();
+ assert_eq!(idx + 1, sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap());
+ }
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+
+ // Beyond the limit it's one in, one out.
+ for idx in MAX_VM_IDS_PER_APP..MAX_VM_IDS_PER_APP + 10 {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ sk_state.add_id(&vm_id, USER1 as u32, APP_A as u32).unwrap();
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+ }
+ assert_eq!(
+ MAX_VM_IDS_PER_APP,
+ sk_state.vm_id_db.count_vm_ids_for_app(USER1, APP_A).unwrap()
+ );
+ }
+
struct Irreconcilable;
impl IVirtualizationReconciliationCallback for Irreconcilable {
diff --git a/virtualizationservice/src/maintenance/vmdb.rs b/virtualizationservice/src/maintenance/vmdb.rs
index 47704bc..273f340 100644
--- a/virtualizationservice/src/maintenance/vmdb.rs
+++ b/virtualizationservice/src/maintenance/vmdb.rs
@@ -272,6 +272,34 @@
Ok(vm_ids)
}
+ /// Determine the number of VM IDs associated with `(user_id, app_id)`.
+ pub fn count_vm_ids_for_app(&mut self, user_id: i32, app_id: i32) -> Result<usize> {
+ let mut stmt = self
+ .conn
+ .prepare("SELECT COUNT(vm_id) FROM main.vmids WHERE user_id = ? AND app_id = ?;")
+ .context("failed to prepare SELECT stmt")?;
+ stmt.query_row(params![user_id, app_id], |row| row.get(0)).context("query failed")
+ }
+
+ /// Return the `count` oldest VM IDs associated with `(user_id, app_id)`.
+ pub fn oldest_vm_ids_for_app(
+ &mut self,
+ user_id: i32,
+ app_id: i32,
+ count: usize,
+ ) -> Result<Vec<VmId>> {
+ // SQLite considers NULL columns to be smaller than values, so rows left over from a v0
+ // database will be listed first.
+ let mut stmt = self
+ .conn
+ .prepare(
+ "SELECT vm_id FROM main.vmids WHERE user_id = ? AND app_id = ? ORDER BY created LIMIT ?;",
+ )
+ .context("failed to prepare SELECT stmt")?;
+ let rows = stmt.query(params![user_id, app_id, count]).context("query failed")?;
+ Self::vm_ids_from_rows(rows)
+ }
+
/// Return all of the `(user_id, app_id)` pairs present in the database.
pub fn get_all_owners(&mut self) -> Result<Vec<(i32, i32)>> {
let mut stmt = self
@@ -344,6 +372,19 @@
fn show_contents(db: &VmIdDb) {
let mut stmt = db.conn.prepare("SELECT * FROM main.vmids;").unwrap();
let mut rows = stmt.query(()).unwrap();
+ println!("DB contents:");
+ while let Some(row) = rows.next().unwrap() {
+ println!(" {row:?}");
+ }
+ }
+
+ fn show_contents_for_app(db: &VmIdDb, user_id: i32, app_id: i32, count: usize) {
+ let mut stmt = db
+ .conn
+ .prepare("SELECT vm_id, created FROM main.vmids WHERE user_id = ? AND app_id = ? ORDER BY created LIMIT ?;")
+ .unwrap();
+ let mut rows = stmt.query(params![user_id, app_id, count]).unwrap();
+ println!("First (by created) {count} rows for app_id={app_id}");
while let Some(row) = rows.next().unwrap() {
println!(" {row:?}");
}
@@ -457,31 +498,39 @@
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(3, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER2, APP_B).unwrap());
assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
assert_eq!(empty, db.vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
+ assert_eq!(0, db.count_vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
// OK to delete things that don't exist.
db.delete_vm_ids(&[VM_ID2, VM_ID3]).unwrap();
assert_eq!(vec![VM_ID1], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
db.add_vm_id(&VM_ID2, USER1, APP_A).unwrap();
db.add_vm_id(&VM_ID3, USER1, APP_A).unwrap();
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_app(USER1, APP_A).unwrap());
+ assert_eq!(3, db.count_vm_ids_for_app(USER1, APP_A).unwrap());
assert_eq!(vec![VM_ID4], db.vm_ids_for_app(USER2, APP_B).unwrap());
+ assert_eq!(1, db.count_vm_ids_for_app(USER2, APP_B).unwrap());
assert_eq!(vec![VM_ID5], db.vm_ids_for_user(USER3).unwrap());
assert_eq!(empty, db.vm_ids_for_user(USER_UNKNOWN).unwrap());
assert_eq!(empty, db.vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
+ assert_eq!(0, db.count_vm_ids_for_app(USER1, APP_UNKNOWN).unwrap());
assert_eq!(
vec![(USER1, APP_A), (USER2, APP_B), (USER3, APP_C)],
@@ -513,4 +562,47 @@
assert_eq!(vec![VM_ID1, VM_ID2, VM_ID3], db.vm_ids_for_user(USER1).unwrap());
show_contents(&db);
}
+
+ #[test]
+ fn test_remove_oldest_with_upgrade() {
+ let mut db = new_test_db_version(0);
+ let version = db.schema_version().unwrap();
+ assert_eq!(0, version);
+
+ let remove_count = 10;
+ let mut want = vec![];
+
+ // Manually insert rows before upgrade.
+ const V0_COUNT: usize = 5;
+ for idx in 0..V0_COUNT {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ if want.len() < remove_count {
+ want.push(vm_id);
+ }
+ db.conn
+ .execute(
+ "REPLACE INTO main.vmids (vm_id, user_id, app_id) VALUES (?1, ?2, ?3);",
+ params![&vm_id, &USER1, APP_A],
+ )
+ .unwrap();
+ }
+
+ // Now move to v1.
+ db.upgrade_tables_v0_v1().unwrap();
+ let version = db.schema_version().unwrap();
+ assert_eq!(1, version);
+
+ for idx in V0_COUNT..40 {
+ let mut vm_id = [0u8; 64];
+ vm_id[0..8].copy_from_slice(&(idx as u64).to_be_bytes());
+ if want.len() < remove_count {
+ want.push(vm_id);
+ }
+ db.add_vm_id(&vm_id, USER1, APP_A).unwrap();
+ }
+ show_contents_for_app(&db, USER1, APP_A, 10);
+ let got = db.oldest_vm_ids_for_app(USER1, APP_A, 10).unwrap();
+ assert_eq!(got, want);
+ }
}
diff --git a/vm_payload/README.md b/vm_payload/README.md
index 419d854..4b1e6f3 100644
--- a/vm_payload/README.md
+++ b/vm_payload/README.md
@@ -2,7 +2,7 @@
This directory contains the definition of the VM Payload API. This is a native
API, exposed as a set of C functions, available to payload code running inside a
-[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/microdroid/README.md)
+[Microdroid](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/microdroid/README.md)
VM.
Note that only native code is supported in Microdroid, so no Java APIs are
@@ -17,7 +17,7 @@
under the `lib/<ABI>` directory, like other JNI code.
The primary .so, which is specified as part of the VM configuration via
-[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java),
+[VirtualMachineConfig.Builder#setPayloadBinaryPath](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java),
must define the entry point for the payload.
This entry point is a C function called `AVmPayload_main()`, as declared in
@@ -36,7 +36,7 @@
runtime from the real `libvm_payload.so` hosted within the Microdroid VM.
See `MicrodroidTestNativeLib` in the [test
-APK](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/master/tests/testapk/Android.bp)
+APK](https://android.googlesource.com/platform/packages/modules/Virtualization/+/refs/heads/main/tests/testapk/Android.bp)
for an example.
In other build systems a similar stub `libvm_payload.so` can be built using
diff --git a/vmbase/example/src/main.rs b/vmbase/example/src/main.rs
index 48b24be..da82b17 100644
--- a/vmbase/example/src/main.rs
+++ b/vmbase/example/src/main.rs
@@ -28,6 +28,7 @@
use aarch64_paging::paging::MemoryRegion;
use aarch64_paging::MapError;
use alloc::{vec, vec::Vec};
+use core::ptr::addr_of_mut;
use cstr::cstr;
use fdtpci::PciInfo;
use libfdt::Fdt;
@@ -138,14 +139,15 @@
// SAFETY: Nowhere else in the program accesses this static mutable variable, so there is no
// chance of concurrent access.
- let zeroed_data = unsafe { &mut ZEROED_DATA };
+ let zeroed_data = unsafe { &mut *addr_of_mut!(ZEROED_DATA) };
// SAFETY: Nowhere else in the program accesses this static mutable variable, so there is no
// chance of concurrent access.
- let mutable_data = unsafe { &mut MUTABLE_DATA };
+ let mutable_data = unsafe { &mut *addr_of_mut!(MUTABLE_DATA) };
for element in zeroed_data.iter() {
assert_eq!(*element, 0);
}
+
zeroed_data[0] = 13;
assert_eq!(zeroed_data[0], 13);
zeroed_data[0] = 0;
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index 2df5a80..4dc6aec 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -27,7 +27,6 @@
collections::{HashSet, VecDeque},
fs::File,
io::{self, BufRead, BufReader, Read, Write},
- os::unix::io::FromRawFd,
panic, thread,
};
use vmclient::{DeathReason, VmInstance};
@@ -142,13 +141,7 @@
fn pipe() -> io::Result<(File, File)> {
let (reader_fd, writer_fd) = nix::unistd::pipe()?;
-
- // SAFETY: These are new FDs with no previous owner.
- let reader = unsafe { File::from_raw_fd(reader_fd) };
- // SAFETY: These are new FDs with no previous owner.
- let writer = unsafe { File::from_raw_fd(writer_fd) };
-
- Ok((reader, writer))
+ Ok((reader_fd.into(), writer_fd.into()))
}
struct VmLogProcessor {
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index a2a88d8..88072a7 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -48,7 +48,7 @@
use std::{
fmt::{self, Debug, Formatter},
fs::File,
- os::unix::io::{AsFd, AsRawFd, FromRawFd, IntoRawFd, OwnedFd},
+ os::unix::io::{AsFd, AsRawFd, IntoRawFd, OwnedFd},
sync::Arc,
time::Duration,
};
@@ -62,10 +62,7 @@
// Create new POSIX pipe. Make it O_CLOEXEC to align with how Rust creates
// file descriptors (expected by SharedChild).
- let (raw1, raw2) = pipe2(OFlag::O_CLOEXEC)?;
-
- // SAFETY: Taking ownership of brand new FDs.
- unsafe { Ok((OwnedFd::from_raw_fd(raw1), OwnedFd::from_raw_fd(raw2))) }
+ Ok(pipe2(OFlag::O_CLOEXEC)?)
}
fn posix_socketpair() -> Result<(OwnedFd, OwnedFd), io::Error> {
diff --git a/vmlauncher_app/Android.bp b/vmlauncher_app/Android.bp
index 06dcf7a..f9c325c 100644
--- a/vmlauncher_app/Android.bp
+++ b/vmlauncher_app/Android.bp
@@ -21,4 +21,7 @@
],
platform_apis: true,
privileged: true,
+ apex_available: [
+ "com.android.virt",
+ ],
}
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index 860c03f..607a895 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -8,6 +8,7 @@
<application
android:label="VmLauncherApp">
<activity android:name=".MainActivity"
+ android:enabled="false"
android:screenOrientation="landscape"
android:configChanges="orientation|screenSize|keyboard|keyboardHidden|navigation|uiMode"
android:theme="@style/MyTheme"
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index b5995b8..450f4ed 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -126,6 +126,8 @@
}
customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
+ customImageConfigBuilder.useTouch(true);
+
configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
} catch (JSONException | IOException e) {
@@ -224,6 +226,13 @@
}
SurfaceView surfaceView = findViewById(R.id.surface_view);
+ surfaceView.setOnTouchListener(
+ (v, event) -> {
+ if (mVirtualMachine == null) {
+ return false;
+ }
+ return mVirtualMachine.sendSingleTouchEvent(event);
+ });
surfaceView
.getHolder()
.addCallback(