Implement missing APIs
Bug: 183496040
Test: atest MicrodroidHostTest
Test: run MicrodroidDemoApp
Change-Id: I1ee057726b0c83f8fa24bc6fabaa4c7b6ae851d2
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 6373b55..baf0242 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -119,7 +119,7 @@
.debugMode(debug);
VirtualMachineConfig config = builder.build();
VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
- mVirtualMachine = vmm.create("demo_vm", config);
+ mVirtualMachine = vmm.getOrCreate("demo_vm", config);
mVirtualMachine.run();
mStatus.postValue(mVirtualMachine.getStatus());
} catch (VirtualMachineException e) {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index 8089d85..53d6864 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -25,9 +25,11 @@
import java.io.File;
import java.io.FileInputStream;
+import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.Files;
import java.util.Optional;
@@ -105,16 +107,22 @@
/* package */ static VirtualMachine create(
Context context, String name, VirtualMachineConfig config)
throws VirtualMachineException {
- // TODO(jiyong): trigger an error if the VM having 'name' already exists.
VirtualMachine vm = new VirtualMachine(context, name, config);
try {
- final File vmRoot = vm.mConfigFilePath.getParentFile();
- Files.createDirectories(vmRoot.toPath());
+ final File thisVmDir = vm.mConfigFilePath.getParentFile();
+ Files.createDirectories(thisVmDir.getParentFile().toPath());
- FileOutputStream output = new FileOutputStream(vm.mConfigFilePath);
- vm.mConfig.serialize(output);
- output.close();
+ // The checking of the existence of this directory and the creation of it is done
+ // atomically. If the directory already exists (i.e. the VM with the same name was
+ // already created), FileAlreadyExistsException is thrown
+ Files.createDirectory(thisVmDir.toPath());
+
+ try (FileOutputStream output = new FileOutputStream(vm.mConfigFilePath)) {
+ vm.mConfig.serialize(output);
+ }
+ } catch (FileAlreadyExistsException e) {
+ throw new VirtualMachineException("virtual machine already exists", e);
} catch (IOException e) {
throw new VirtualMachineException(e);
}
@@ -126,7 +134,6 @@
/** Loads a virtual machine that is already created before. */
/* package */ static VirtualMachine load(Context context, String name)
throws VirtualMachineException {
- // TODO(jiyong): return null if the VM having the 'name' doesn't exist.
VirtualMachine vm = new VirtualMachine(context, name, /* config */ null);
try {
@@ -134,6 +141,9 @@
VirtualMachineConfig config = VirtualMachineConfig.from(input);
input.close();
vm.mConfig = config;
+ } catch (FileNotFoundException e) {
+ // The VM doesn't exist.
+ return null;
} catch (IOException e) {
throw new VirtualMachineException(e);
}
@@ -265,8 +275,21 @@
*/
public VirtualMachineConfig setConfig(VirtualMachineConfig newConfig)
throws VirtualMachineException {
- // TODO(jiyong): implement this
- throw new VirtualMachineException("Not implemented");
+ final VirtualMachineConfig oldConfig = getConfig();
+ if (!oldConfig.isCompatibleWith(newConfig)) {
+ throw new VirtualMachineException("incompatible config");
+ }
+
+ try {
+ FileOutputStream output = new FileOutputStream(mConfigFilePath);
+ newConfig.serialize(output);
+ output.close();
+ } catch (IOException e) {
+ throw new VirtualMachineException(e);
+ }
+ mConfig = newConfig;
+
+ return oldConfig;
}
@Override
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index b5f04a2..f0e1ce6 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -19,6 +19,8 @@
import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.Signature; // This actually is certificate!
import android.os.ParcelFileDescriptor;
import android.os.PersistableBundle;
import android.system.virtualizationservice.VirtualMachineAppConfig;
@@ -28,6 +30,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
/**
* Represents a configuration of a virtual machine. A configuration consists of hardware
@@ -40,6 +45,7 @@
// These defines the schema of the config file persisted on disk.
private static final int VERSION = 1;
private static final String KEY_VERSION = "version";
+ private static final String KEY_CERTS = "certs";
private static final String KEY_APKPATH = "apkPath";
private static final String KEY_IDSIGPATH = "idsigPath";
private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
@@ -47,6 +53,7 @@
// Paths to the APK and its idsig file of this application.
private final String mApkPath;
+ private final Signature[] mCerts;
private final String mIdsigPath;
private final boolean mDebugMode;
@@ -58,8 +65,13 @@
// TODO(jiyong): add more items like # of cpu, size of ram, debuggability, etc.
private VirtualMachineConfig(
- String apkPath, String idsigPath, String payloadConfigPath, boolean debugMode) {
+ String apkPath,
+ Signature[] certs,
+ String idsigPath,
+ String payloadConfigPath,
+ boolean debugMode) {
mApkPath = apkPath;
+ mCerts = certs;
mIdsigPath = idsigPath;
mPayloadConfigPath = payloadConfigPath;
mDebugMode = debugMode;
@@ -77,6 +89,15 @@
if (apkPath == null) {
throw new VirtualMachineException("No apkPath");
}
+ final String[] certStrings = b.getStringArray(KEY_CERTS);
+ if (certStrings == null || certStrings.length == 0) {
+ throw new VirtualMachineException("No certs");
+ }
+ List<Signature> certList = new ArrayList<>();
+ for (String s : certStrings) {
+ certList.add(new Signature(s));
+ }
+ Signature[] certs = certList.toArray(new Signature[0]);
final String idsigPath = b.getString(KEY_IDSIGPATH);
if (idsigPath == null) {
throw new VirtualMachineException("No idsigPath");
@@ -86,7 +107,7 @@
throw new VirtualMachineException("No payloadConfigPath");
}
final boolean debugMode = b.getBoolean(KEY_DEBUGMODE);
- return new VirtualMachineConfig(apkPath, idsigPath, payloadConfigPath, debugMode);
+ return new VirtualMachineConfig(apkPath, certs, idsigPath, payloadConfigPath, debugMode);
}
/** Persists this config to a stream, for example a file. */
@@ -94,6 +115,12 @@
PersistableBundle b = new PersistableBundle();
b.putInt(KEY_VERSION, VERSION);
b.putString(KEY_APKPATH, mApkPath);
+ List<String> certList = new ArrayList<>();
+ for (Signature cert : mCerts) {
+ certList.add(cert.toCharsString());
+ }
+ String[] certs = certList.toArray(new String[0]);
+ b.putStringArray(KEY_CERTS, certs);
b.putString(KEY_IDSIGPATH, mIdsigPath);
b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
b.putBoolean(KEY_DEBUGMODE, mDebugMode);
@@ -106,6 +133,23 @@
}
/**
+ * Tests if this config is compatible with other config. Being compatible means that the configs
+ * can be interchangeably used for the same virtual machine. Compatible changes includes the
+ * number of CPUs and the size of the RAM, and change of the payload as long as the payload is
+ * signed by the same signer. All other changes (e.g. using a payload from a different signer,
+ * change of the debug mode, etc.) are considered as incompatible.
+ */
+ public boolean isCompatibleWith(VirtualMachineConfig other) {
+ if (!Arrays.equals(this.mCerts, other.mCerts)) {
+ return false;
+ }
+ if (this.mDebugMode != other.mDebugMode) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Converts this config object into a parcel. Used when creating a VM via the virtualization
* service. Notice that the files are not passed as paths, but as file descriptors because the
* service doesn't accept paths as it might not have permission to open app-owned files and that
@@ -152,7 +196,22 @@
/** Builds an immutable {@link VirtualMachineConfig} */
public VirtualMachineConfig build() {
final String apkPath = mContext.getPackageCodePath();
- return new VirtualMachineConfig(apkPath, mIdsigPath, mPayloadConfigPath, mDebugMode);
+ final String packageName = mContext.getPackageName();
+ Signature[] certs;
+ try {
+ certs =
+ mContext.getPackageManager()
+ .getPackageInfo(
+ packageName, PackageManager.GET_SIGNING_CERTIFICATES)
+ .signingInfo
+ .getSigningCertificateHistory();
+ } catch (PackageManager.NameNotFoundException e) {
+ // This cannot happen as `packageName` is from this app.
+ throw new RuntimeException(e);
+ }
+
+ return new VirtualMachineConfig(
+ apkPath, certs, mIdsigPath, mPayloadConfigPath, mDebugMode);
}
}
}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index dfa4f0b..317caee 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -74,7 +74,12 @@
return VirtualMachine.load(mContext, name);
}
- /** Returns an existing {@link VirtualMachine} if it exists, or create a new one. */
+ /**
+ * Returns an existing {@link VirtualMachine} if it exists, or create a new one. If the virtual
+ * machine exists, and config is not null, the virtual machine is re-configured with the new
+ * config. However, if the config is not compatible with the original config of the virtual
+ * machine, exception is thrown.
+ */
public VirtualMachine getOrCreate(String name, VirtualMachineConfig config)
throws VirtualMachineException {
VirtualMachine vm;
@@ -85,10 +90,11 @@
}
}
- if (vm.getConfig().equals(config)) {
- return vm;
- } else {
- throw new VirtualMachineException("Incompatible config");
+ if (config != null) {
+ // Can throw VirtualMachineException is the new config is not compatible with the
+ // old config.
+ vm.setConfig(config);
}
+ return vm;
}
}