Merge "Allow a new owner to claim a VM ID" into main
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index a5c8062..f9ca2f2 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -71,6 +71,7 @@
import android.system.virtualizationservice.MemoryTrimLevel;
import android.system.virtualizationservice.PartitionType;
import android.system.virtualizationservice.VirtualMachineAppConfig;
+import android.system.virtualizationservice.VirtualMachineRawConfig;
import android.system.virtualizationservice.VirtualMachineState;
import android.util.JsonReader;
import android.util.Log;
@@ -810,6 +811,58 @@
}
}
+ private android.system.virtualizationservice.VirtualMachineConfig
+ createVirtualMachineConfigForRawFrom(VirtualMachineConfig vmConfig)
+ throws IllegalStateException {
+ VirtualMachineRawConfig rawConfig = vmConfig.toVsRawConfig();
+ return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig);
+ }
+
+ private android.system.virtualizationservice.VirtualMachineConfig
+ createVirtualMachineConfigForAppFrom(
+ VirtualMachineConfig vmConfig, IVirtualizationService service)
+ throws RemoteException, IOException, VirtualMachineException {
+ VirtualMachineAppConfig appConfig = vmConfig.toVsConfig(mContext.getPackageManager());
+ appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
+ appConfig.name = mName;
+ if (mInstanceIdPath != null) {
+ appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
+ } else {
+ // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used.
+ appConfig.instanceId = new byte[64];
+ }
+ if (mEncryptedStoreFilePath != null) {
+ appConfig.encryptedStorageImage =
+ ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
+ }
+
+ if (!vmConfig.getExtraApks().isEmpty()) {
+ // Extra APKs were specified directly, rather than via config file.
+ // We've already populated the file names for the extra APKs and IDSigs
+ // (via setupExtraApks). But we also need to open the APK files and add
+ // fds for them to the payload config.
+ // This isn't needed when the extra APKs are specified in a config file -
+ // then
+ // Virtualization Manager opens them itself.
+ List<ParcelFileDescriptor> extraApkFiles = new ArrayList<>(mExtraApks.size());
+ for (ExtraApkSpec extraApk : mExtraApks) {
+ try {
+ extraApkFiles.add(ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY));
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("Failed to open extra APK", e);
+ }
+ }
+ appConfig.payload.getPayloadConfig().extraApks = extraApkFiles;
+ }
+
+ try {
+ createIdSigsAndUpdateConfig(service, appConfig);
+ } catch (FileNotFoundException e) {
+ throw new VirtualMachineException("Failed to generate APK signature", e);
+ }
+ return android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
+ }
+
/**
* Runs this virtual machine. The returning of this method however doesn't mean that the VM has
* actually started running or the OS has booted there. Such events can be notified by
@@ -850,50 +903,10 @@
}
VirtualMachineConfig vmConfig = getConfig();
- VirtualMachineAppConfig appConfig =
- vmConfig.toVsConfig(mContext.getPackageManager());
- appConfig.instanceImage =
- ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
- appConfig.name = mName;
- if (mInstanceIdPath != null) {
- appConfig.instanceId = Files.readAllBytes(mInstanceIdPath.toPath());
- } else {
- // FEATURE_LLPVM_CHANGES is disabled, instance_id is not used.
- appConfig.instanceId = new byte[64];
- }
- if (mEncryptedStoreFilePath != null) {
- appConfig.encryptedStorageImage =
- ParcelFileDescriptor.open(mEncryptedStoreFilePath, MODE_READ_WRITE);
- }
-
- if (!vmConfig.getExtraApks().isEmpty()) {
- // Extra APKs were specified directly, rather than via config file.
- // We've already populated the file names for the extra APKs and IDSigs
- // (via setupExtraApks). But we also need to open the APK files and add
- // fds for them to the payload config.
- // This isn't needed when the extra APKs are specified in a config file - then
- // Virtualization Manager opens them itself.
- List<ParcelFileDescriptor> extraApkFiles = new ArrayList<>(mExtraApks.size());
- for (ExtraApkSpec extraApk : mExtraApks) {
- try {
- extraApkFiles.add(
- ParcelFileDescriptor.open(extraApk.apk, MODE_READ_ONLY));
- } catch (FileNotFoundException e) {
- throw new VirtualMachineException("Failed to open extra APK", e);
- }
- }
- appConfig.payload.getPayloadConfig().extraApks = extraApkFiles;
- }
-
- try {
- createIdSigsAndUpdateConfig(service, appConfig);
- } catch (FileNotFoundException e) {
- throw new VirtualMachineException("Failed to generate APK signature", e);
- }
-
android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
- android.system.virtualizationservice.VirtualMachineConfig.appConfig(
- appConfig);
+ vmConfig.getRawConfigPath() != null
+ ? createVirtualMachineConfigForRawFrom(vmConfig)
+ : createVirtualMachineConfigForAppFrom(vmConfig, service);
mVirtualMachine =
service.createVm(
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
index 054d73c..99c3d05 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -18,6 +18,7 @@
import static android.os.ParcelFileDescriptor.AutoCloseInputStream;
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
+import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
import static java.util.Objects.requireNonNull;
@@ -37,12 +38,19 @@
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.sysprop.HypervisorProperties;
+import android.system.virtualizationservice.DiskImage;
+import android.system.virtualizationservice.Partition;
import android.system.virtualizationservice.VirtualMachineAppConfig;
import android.system.virtualizationservice.VirtualMachinePayloadConfig;
+import android.system.virtualizationservice.VirtualMachineRawConfig;
import android.util.Log;
import com.android.system.virtualmachine.flags.Flags;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -52,6 +60,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;
@@ -78,6 +88,7 @@
private static final String KEY_PACKAGENAME = "packageName";
private static final String KEY_APKPATH = "apkPath";
private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
+ private static final String KEY_RAWCONFIGPATH = "rawConfigPath";
private static final String KEY_PAYLOADBINARYNAME = "payloadBinaryPath";
private static final String KEY_DEBUGLEVEL = "debugLevel";
private static final String KEY_PROTECTED_VM = "protectedVm";
@@ -173,6 +184,9 @@
/** Name of the payload binary file within the APK that will be executed within the VM. */
@Nullable private final String mPayloadBinaryName;
+ /** Path within the raw config file to launch the VM. */
+ @Nullable private final String mRawConfigPath;
+
/** The size of storage in bytes. 0 indicates that encryptedStorage is not required */
private final long mEncryptedStorageBytes;
@@ -210,6 +224,7 @@
List<String> extraApks,
@Nullable String payloadConfigPath,
@Nullable String payloadBinaryName,
+ @Nullable String rawConfigPath,
@DebugLevel int debugLevel,
boolean protectedVm,
long memoryBytes,
@@ -229,6 +244,7 @@
Arrays.asList(extraApks.toArray(new String[0])));
mPayloadConfigPath = payloadConfigPath;
mPayloadBinaryName = payloadBinaryName;
+ mRawConfigPath = rawConfigPath;
mDebugLevel = debugLevel;
mProtectedVm = protectedVm;
mMemoryBytes = memoryBytes;
@@ -290,10 +306,14 @@
}
String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
- if (payloadConfigPath == null) {
- builder.setPayloadBinaryName(b.getString(KEY_PAYLOADBINARYNAME));
- } else {
+ String payloadBinaryName = b.getString(KEY_PAYLOADBINARYNAME);
+ String rawConfigPath = b.getString(KEY_RAWCONFIGPATH);
+ if (rawConfigPath != null) {
+ builder.setRawConfigPath(rawConfigPath);
+ } else if (payloadConfigPath != null) {
builder.setPayloadConfigPath(payloadConfigPath);
+ } else {
+ builder.setPayloadBinaryName(payloadBinaryName);
}
@DebugLevel int debugLevel = b.getInt(KEY_DEBUGLEVEL);
@@ -352,6 +372,7 @@
}
b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
b.putString(KEY_PAYLOADBINARYNAME, mPayloadBinaryName);
+ b.putString(KEY_RAWCONFIGPATH, mRawConfigPath);
b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
b.putInt(KEY_CPU_TOPOLOGY, mCpuTopology);
@@ -412,6 +433,16 @@
}
/**
+ * Returns the path within the raw config file to launch the VM.
+ *
+ * @hide
+ */
+ @Nullable
+ public String getRawConfigPath() {
+ return mRawConfigPath;
+ }
+
+ /**
* Returns the name of the payload binary file, in the {@code lib/<ABI>} directory of the APK,
* that will be executed within the VM.
*
@@ -554,6 +585,99 @@
&& Objects.equals(this.mExtraApks, other.mExtraApks);
}
+ // Needs to sync with packages/modules/Virtualization/libs/vmconfig/src/lib.rs
+ VirtualMachineRawConfig toVsRawConfig() throws IllegalStateException {
+ VirtualMachineRawConfig config = new VirtualMachineRawConfig();
+
+ try {
+ String rawJson = new String(Files.readAllBytes(Path.of(mRawConfigPath)));
+ JSONObject json = new JSONObject(rawJson);
+ config.name = json.optString("name", "");
+ config.instanceId = new byte[64];
+ if (json.has("kernel")) {
+ config.kernel =
+ ParcelFileDescriptor.open(
+ new File(json.getString("kernel")), MODE_READ_ONLY);
+ }
+ if (json.has("initrd")) {
+ config.initrd =
+ ParcelFileDescriptor.open(
+ new File(json.getString("initrd")), MODE_READ_ONLY);
+ }
+ if (json.has("params")) {
+ config.params = json.getString("params");
+ }
+ if (json.has("bootloader")) {
+ config.bootloader =
+ ParcelFileDescriptor.open(
+ new File(json.getString("bootloader")), MODE_READ_ONLY);
+ }
+ if (json.has("disks")) {
+ JSONArray diskArr = json.getJSONArray("disks");
+ config.disks = new DiskImage[diskArr.length()];
+ for (int i = 0; i < diskArr.length(); i++) {
+ config.disks[i] = new DiskImage();
+ JSONObject item = diskArr.getJSONObject(i);
+ config.disks[i].writable = item.optBoolean("writable", false);
+ if (item.has("image")) {
+ config.disks[i].image =
+ ParcelFileDescriptor.open(
+ new File(item.getString("image")),
+ config.disks[i].writable
+ ? MODE_READ_WRITE
+ : MODE_READ_ONLY);
+ }
+ if (item.has("partition")) {
+ JSONArray partitionArr = json.getJSONArray("partition");
+ config.disks[i].partitions = new Partition[partitionArr.length()];
+ for (int j = 0; j < partitionArr.length(); j++) {
+ config.disks[i].partitions[j] = new Partition();
+ JSONObject partitionItem = partitionArr.getJSONObject(j);
+ config.disks[i].partitions[j].writable =
+ partitionItem.optBoolean("writable", false);
+ config.disks[i].partitions[j].label = partitionItem.getString("label");
+ config.disks[i].partitions[j].image =
+ ParcelFileDescriptor.open(
+ new File(partitionItem.getString("image")),
+ config.disks[i].partitions[j].writable
+ ? MODE_READ_WRITE
+ : MODE_READ_ONLY);
+ }
+ } else {
+ config.disks[i].partitions = new Partition[0];
+ }
+ }
+ } else {
+ config.disks = new DiskImage[0];
+ }
+ // The value which is set by setProtectedVm is used.
+ if (json.has("protected")) {
+ Log.d(TAG, "'protected' field is ignored, the value from setProtectedVm is used");
+ }
+ config.protectedVm = this.mProtectedVm;
+ config.memoryMib = json.optInt("memory_mib", 0);
+ if (json.optString("cpu_topology", "one_cpu").equals("match_host")) {
+ config.cpuTopology = CPU_TOPOLOGY_MATCH_HOST;
+ } else {
+ config.cpuTopology = CPU_TOPOLOGY_ONE_CPU;
+ }
+ config.platformVersion = json.getString("platform_version");
+ if (json.has("devices")) {
+ JSONArray arr = json.getJSONArray("devices");
+ config.devices = new String[arr.length()];
+ for (int i = 0; i < arr.length(); i++) {
+ config.devices[i] = arr.getString(i);
+ }
+ } else {
+ config.devices = new String[0];
+ }
+
+ } catch (JSONException | IOException e) {
+ throw new IllegalStateException("malformed input", e);
+ }
+ return config;
+ }
+
/**
* Converts this config object into the parcelable type used when creating a VM via the
* virtualization service. Notice that the files are not passed as paths, but as file
@@ -680,6 +804,7 @@
@Nullable private String mApkPath;
private final List<String> mExtraApks = new ArrayList<>();
@Nullable private String mPayloadConfigPath;
+ @Nullable private String mRawConfigPath;
@Nullable private String mPayloadBinaryName;
@DebugLevel private int mDebugLevel = DEBUG_LEVEL_NONE;
private boolean mProtectedVm;
@@ -729,8 +854,13 @@
// This should never happen, unless we're deserializing a bad config
throw new IllegalStateException("apkPath or packageName must be specified");
}
-
- if (mPayloadBinaryName == null) {
+ if (mRawConfigPath != null) {
+ if (mPayloadBinaryName != null || mPayloadConfigPath != null) {
+ throw new IllegalStateException(
+ "setRawConfigPath and (setPayloadBinaryName or setPayloadConfigPath)"
+ + " may not both be called");
+ }
+ } else if (mPayloadBinaryName == null) {
if (mPayloadConfigPath == null) {
throw new IllegalStateException("setPayloadBinaryName must be called");
}
@@ -763,6 +893,7 @@
mExtraApks,
mPayloadConfigPath,
mPayloadBinaryName,
+ mRawConfigPath,
mDebugLevel,
mProtectedVm,
mMemoryBytes,
@@ -824,6 +955,29 @@
}
/**
+ * Sets the path within the raw config file to launch the VM. The file is a JSON file; see
+ * packages/modules/Virtualization/libs/vmconfig/src/lib.rs for the format.
+ *
+ * @hide
+ */
+ @RequiresPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION)
+ @NonNull
+ public Builder setRawConfigPath(@NonNull String rawConfigPath) {
+ // TODO: This method will be removed when the builder support more structured methods
+ // for that like below.
+ // builder
+ // .setLinuxKernelConfig(new LinuxKernelConfig()
+ // .setKernelPath(...)
+ // .setInitRamdiskPath(...)
+ // .setKernelCommandLine(...))
+ // .setDiskConfig(new DiskConfig()
+ // .addDisk(...)
+ // .addDisk(...))
+ mRawConfigPath = requireNonNull(rawConfigPath, "rawConfigPath must not be null");
+ return this;
+ }
+
+ /**
* Sets the name of the payload binary file that will be executed within the VM, e.g.
* "payload.so". The file must reside in the {@code lib/<ABI>} directory of the APK.
*
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index ad51b61..686abf9 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -15,6 +15,7 @@
//! Safe wrappers around the BoringSSL API.
#![cfg_attr(not(feature = "std"), no_std)]
+#![warn(clippy::or_fun_call)]
extern crate alloc;
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index 41d244d..068d8f9 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -38,8 +38,6 @@
"lz4",
"sign_virt_apex",
"simg2img",
- "dtdiff",
- "dtc", // for dtdiff
],
// java_test_host doesn't have data_native_libs but jni_libs can be used to put
// native modules under ./lib directory.
@@ -54,6 +52,5 @@
"liblp",
"libsparse",
"libz",
- "libfdt", // for dtc
],
}
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 14cc0ae..007f38c 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -35,6 +35,7 @@
import com.android.tradefed.util.RunUtil;
import org.json.JSONArray;
+import org.json.JSONObject;
import java.io.File;
import java.io.IOException;
@@ -65,6 +66,17 @@
protected static final Set<String> SUPPORTED_GKI_VERSIONS =
Collections.unmodifiableSet(new HashSet(Arrays.asList("android14-6.1")));
+ /* Keep this sync with AssignableDevice.aidl */
+ public static final class AssignableDevice {
+ public final String node;
+ public final String dtbo_label;
+
+ public AssignableDevice(String node, String dtbo_label) {
+ this.node = node;
+ this.dtbo_label = dtbo_label;
+ }
+ }
+
public static void prepareVirtualizationTestSetup(ITestDevice androidDevice)
throws DeviceNotAvailableException {
CommandRunner android = new CommandRunner(androidDevice);
@@ -186,18 +198,26 @@
return pathLine.substring("package:".length());
}
- public List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception {
+ public String parseFieldFromVmInfo(String header) throws Exception {
CommandRunner android = new CommandRunner(getDevice());
String result = android.run("/apex/com.android.virt/bin/vm", "info");
- List<String> ret = new ArrayList<>();
for (String line : result.split("\n")) {
if (!line.startsWith(header)) continue;
- JSONArray jsonArray = new JSONArray(line.substring(header.length()));
+ return line.substring(header.length());
+ }
+ return "";
+ }
+
+ public List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception {
+ String field = parseFieldFromVmInfo(header);
+
+ List<String> ret = new ArrayList<>();
+ if (!field.isEmpty()) {
+ JSONArray jsonArray = new JSONArray(field);
for (int i = 0; i < jsonArray.length(); i++) {
ret.add(jsonArray.getString(i));
}
- break;
}
return ret;
}
@@ -208,8 +228,20 @@
return result.contains("enabled");
}
- public List<String> getAssignableDevices() throws Exception {
- return parseStringArrayFieldsFromVmInfo("Assignable devices: ");
+ public List<AssignableDevice> getAssignableDevices() throws Exception {
+ String field = parseFieldFromVmInfo("Assignable devices: ");
+
+ List<AssignableDevice> ret = new ArrayList<>();
+ if (!field.isEmpty()) {
+ JSONArray jsonArray = new JSONArray(field);
+ for (int i = 0; i < jsonArray.length(); i++) {
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
+ ret.add(
+ new AssignableDevice(
+ jsonObject.getString("node"), jsonObject.getString("dtbo_label")));
+ }
+ }
+ return ret;
}
public List<String> getSupportedOSList() throws Exception {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index a51bebe..06806ec 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -82,6 +82,7 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import java.util.Objects;
@RunWith(DeviceJUnit4Parameterized.class)
@UseParametersRunnerFactory(DeviceJUnit4ClassRunnerWithParameters.RunnerFactory.class)
@@ -1041,66 +1042,48 @@
// Check for preconditions
assumeVfioPlatformSupported();
- List<String> devices = getAssignableDevices();
+ List<AssignableDevice> devices = getAssignableDevices();
assumeFalse("no assignable devices", devices.isEmpty());
- String vmFdtPath = "/sys/firmware/fdt";
- File testDir = FileUtil.createTempDir("device_assignment_test");
- File baseFdtFile = new File(testDir, "base_fdt.dtb");
- File fdtFile = new File(testDir, "fdt.dtb");
-
- // Generates baseline DT
- launchWithDeviceAssignment(/* device= */ null);
- assertThat(mMicrodroidDevice.pullFile(vmFdtPath, baseFdtFile)).isTrue();
- getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice);
-
- // Prepares to run dtdiff. It requires dtc.
- File dtdiff = findTestFile("dtdiff");
- RunUtil runner = new RunUtil();
- String separator = System.getProperty("path.separator");
- String path = dtdiff.getParent() + separator + System.getenv("PATH");
- runner.setEnvVariable("PATH", path);
+ String dtSysfsPath = "/proc/device-tree/";
// Try assign devices one by one
- for (String device : devices) {
- assertThat(device).isNotNull();
- launchWithDeviceAssignment(device);
- assertThat(mMicrodroidDevice.pullFile(vmFdtPath, fdtFile)).isTrue();
+ for (AssignableDevice device : devices) {
+ launchWithDeviceAssignment(device.node);
+
+ String dtPath =
+ new CommandRunner(mMicrodroidDevice)
+ .run("cat", dtSysfsPath + "__symbols__/" + device.dtbo_label);
+ assertThat(dtPath).isNotEmpty();
+
+ String resolvedDtPath =
+ new CommandRunner(mMicrodroidDevice)
+ .run("readlink", "-e", dtSysfsPath + dtPath);
+ assertThat(resolvedDtPath).isNotEmpty();
+
+ String allDevices =
+ new CommandRunner(mMicrodroidDevice)
+ .run("readlink", "-e", "/sys/bus/platform/devices/*/of_node");
+ assertThat(allDevices.split("\n")).asList().contains(resolvedDtPath);
+
getAndroidDevice().shutdownMicrodroid(mMicrodroidDevice);
-
- CommandResult result =
- runner.runTimedCmd(
- 500,
- dtdiff.getAbsolutePath(),
- baseFdtFile.getPath(),
- fdtFile.getPath());
-
- assertWithMessage(
- "VM's device tree hasn't changed when assigning "
- + device
- + ", result="
- + result)
- .that(result.getStatus())
- .isNotEqualTo(CommandStatus.SUCCESS);
+ mMicrodroidDevice = null;
}
-
- mMicrodroidDevice = null;
}
private void launchWithDeviceAssignment(String device) throws Exception {
+ Objects.requireNonNull(device);
final String configPath = "assets/vm_config.json";
- MicrodroidBuilder builder =
+ mMicrodroidDevice =
MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
.debugLevel("full")
.memoryMib(minMemorySize())
.cpuTopology("match_host")
.protectedVm(mProtectedVm)
- .gki(mGki);
- if (device != null) {
- builder.addAssignableDevice(device);
- }
- mMicrodroidDevice = builder.build(getAndroidDevice());
+ .gki(mGki)
+ .addAssignableDevice(device)
+ .build(getAndroidDevice());
assertThat(mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT)).isTrue();
assertThat(mMicrodroidDevice.enableAdbRoot()).isTrue();
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index a72f724..b8c6315 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -1521,11 +1521,8 @@
}
fn is_secretkeeper_supported() -> bool {
- // TODO(b/327526008): Session establishment wth secretkeeper is failing.
- // Re-enable this when fixed.
- let _sk_supported = binder::is_declared(SECRETKEEPER_IDENTIFIER)
- .expect("Could not check for declared Secretkeeper interface");
- false
+ binder::is_declared(SECRETKEEPER_IDENTIFIER)
+ .expect("Could not check for declared Secretkeeper interface")
}
impl VirtualMachineService {
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
index 014d78c..20114d7 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/AssignableDevice.aidl
@@ -21,6 +21,6 @@
/** Path to SysFS node of the device. */
String node;
- /** Kind of the device. */
- String kind;
+ /** DTBO label. */
+ String dtbo_label;
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 5c1c079..c6150b2 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -372,7 +372,7 @@
Ok(get_assignable_devices()?
.device
.into_iter()
- .map(|x| AssignableDevice { node: x.sysfs_path, kind: x.kind })
+ .map(|x| AssignableDevice { node: x.sysfs_path, dtbo_label: x.dtbo_label })
.collect::<Vec<_>>())
}
@@ -498,10 +498,8 @@
}
}
-// KEEP IN SYNC WITH assignable_devices.xsd
#[derive(Debug, Deserialize)]
struct Device {
- kind: String,
dtbo_label: String,
sysfs_path: String,
}
diff --git a/vm/src/main.rs b/vm/src/main.rs
index bc05ec3..b60f2db 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -30,6 +30,7 @@
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
use run::{command_run, command_run_app, command_run_microdroid};
+use serde::Serialize;
use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
@@ -402,8 +403,17 @@
println!("VFIO-platform is not supported.");
}
+ #[derive(Serialize)]
+ struct AssignableDevice {
+ node: String,
+ dtbo_label: String,
+ }
+
let devices = get_service()?.getAssignableDevices()?;
- let devices = devices.into_iter().map(|x| x.node).collect::<Vec<_>>();
+ let devices: Vec<_> = devices
+ .into_iter()
+ .map(|device| AssignableDevice { node: device.node, dtbo_label: device.dtbo_label })
+ .collect();
println!("Assignable devices: {}", serde_json::to_string(&devices)?);
let os_list = get_service()?.getSupportedOSList()?;
diff --git a/vmlauncher_app/Android.bp b/vmlauncher_app/Android.bp
new file mode 100644
index 0000000..cd40448
--- /dev/null
+++ b/vmlauncher_app/Android.bp
@@ -0,0 +1,20 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+ name: "VmLauncherApp",
+ srcs: ["java/**/*.java"],
+ resource_dirs: ["res"],
+ static_libs: [
+ "androidx-constraintlayout_constraintlayout",
+ "androidx.appcompat_appcompat",
+ "com.google.android.material_material",
+ ],
+ libs: [
+ "framework-virtualization.impl",
+ "framework-annotations-lib",
+ ],
+ platform_apis: true,
+ privileged: true,
+}
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
new file mode 100644
index 0000000..de9d094
--- /dev/null
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.virtualization.vmlauncher" >
+
+ <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+ <uses-feature android:name="android.software.virtualization_framework" android:required="true" />
+ <application
+ android:label="VmLauncherApp">
+ <activity android:name=".MainActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN" />
+ <category android:name="android.intent.category.LAUNCHER" />
+ </intent-filter>
+ </activity>
+ </application>
+
+</manifest>
diff --git a/vmlauncher_app/README.md b/vmlauncher_app/README.md
new file mode 100644
index 0000000..9175e57
--- /dev/null
+++ b/vmlauncher_app/README.md
@@ -0,0 +1,16 @@
+# VM launcher app
+
+## Building & Installing
+
+Add `VmLauncherApp` into `PRODUCT_PACKAGES` and then `m`
+
+You can also explicitly grant or revoke the permission, e.g.
+```
+adb shell pm grant com.android.virtualization.vmlauncher android.permission.USE_CUSTOM_VIRTUAL_MACHINE
+adb shell pm grant com.android.virtualization.vmlauncher android.permission.MANAGE_VIRTUAL_MACHINE
+```
+
+## Running
+
+Copy vm config json file to /data/local/tmp/vm_config.json.
+And then, run the app, check log meesage.
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
new file mode 100644
index 0000000..90d7fcc
--- /dev/null
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virtualization.vmlauncher;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.util.Log;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+public class MainActivity extends Activity {
+ private static final String TAG = "VmLauncherApp";
+ private static final String VM_NAME = "my_custom_vm";
+ private static final boolean DEBUG = true;
+ private final ExecutorService mExecutorService = Executors.newFixedThreadPool(4);
+ private VirtualMachine mVirtualMachine;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.activity_main);
+ VirtualMachineCallback callback =
+ new VirtualMachineCallback() {
+ // store reference to ExecutorService to avoid race condition
+ private final ExecutorService mService = mExecutorService;
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm) {
+ Log.e(TAG, "payload start");
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ // This check doesn't 100% prevent race condition or UI hang.
+ // However, it's fine for demo.
+ if (mService.isShutdown()) {
+ return;
+ }
+ Log.d(TAG, "(Payload is ready. Testing VM service...)");
+ }
+
+ @Override
+ public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+ // This check doesn't 100% prevent race condition, but is fine for demo.
+ if (!mService.isShutdown()) {
+ Log.d(
+ TAG,
+ String.format("(Payload finished. exit code: %d)", exitCode));
+ }
+ }
+
+ @Override
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ Log.d(
+ TAG,
+ String.format(
+ "(Error occurred. code: %d, message: %s)",
+ errorCode, message));
+ }
+
+ @Override
+ public void onStopped(VirtualMachine vm, int reason) {
+ Log.e(TAG, "vm stop");
+ }
+ };
+
+ try {
+ VirtualMachineConfig.Builder builder =
+ new VirtualMachineConfig.Builder(getApplication());
+ builder.setRawConfigPath("/data/local/tmp/vm_config.json");
+ builder.setProtectedVm(false);
+ if (DEBUG) {
+ builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
+ builder.setVmOutputCaptured(true);
+ }
+ VirtualMachineConfig config = builder.build();
+ VirtualMachineManager vmm =
+ getApplication().getSystemService(VirtualMachineManager.class);
+ if (vmm == null) {
+ Log.e(TAG, "vmm is null");
+ return;
+ }
+ mVirtualMachine = vmm.getOrCreate(VM_NAME, config);
+ try {
+ mVirtualMachine.setConfig(config);
+ } catch (VirtualMachineException e) {
+ vmm.delete(VM_NAME);
+ mVirtualMachine = vmm.create(VM_NAME, config);
+ Log.e(TAG, "error" + e);
+ }
+
+ Log.d(TAG, "vm start");
+ mVirtualMachine.run();
+ mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback);
+ if (DEBUG) {
+ InputStream console = mVirtualMachine.getConsoleOutput();
+ InputStream log = mVirtualMachine.getLogOutput();
+ mExecutorService.execute(new Reader("console", console));
+ mExecutorService.execute(new Reader("log", log));
+ }
+ } catch (VirtualMachineException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Reads data from an input stream and posts it to the output data */
+ static class Reader implements Runnable {
+ private final String mName;
+ private final InputStream mStream;
+
+ Reader(String name, InputStream stream) {
+ mName = name;
+ mStream = stream;
+ }
+
+ @Override
+ public void run() {
+ try {
+ BufferedReader reader = new BufferedReader(new InputStreamReader(mStream));
+ String line;
+ while ((line = reader.readLine()) != null && !Thread.interrupted()) {
+ Log.d(TAG, mName + ": " + line);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "Exception while posting " + mName + " output: " + e.getMessage());
+ }
+ }
+ }
+}
diff --git a/vmlauncher_app/res/layout/activity_main.xml b/vmlauncher_app/res/layout/activity_main.xml
new file mode 100644
index 0000000..5cbda78
--- /dev/null
+++ b/vmlauncher_app/res/layout/activity_main.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbars="horizontal|vertical"
+ android:textAlignment="textStart"
+ tools:context=".MainActivity">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ </LinearLayout>
+
+</androidx.constraintlayout.widget.ConstraintLayout>