VirtualMachine API changes pre-@SystemApi

This is the set of code changes proposed in
https://r.android.com/2192077, except for the migration to @SystemApi:
- Replace enums with integer constants. Map the AIDL constants to the
  API constants. (Some of this was already done in
  https://r.android.com/2203781.)
- Make connectToVsockServer synchronous rather than using Future.
- Make VirtualMachine AutoCloseable (close() == stop()).
- Add VirtualMachine getters to match the builder's setters.
- Lots of nullability declarations.
- Added @RequiresPermission as appropriate.
- Rename onDied, DeathReason to onStopped, StopReason.
- Assorted documentation tweaks.

Also modify the clients to match.

I want to do this now separately because that CL is going to take a
while to get in, and I want to submit some further changes that modify
VirtualMachineConfig (b/243513572).

Bug: 243512115
Bug: 236811123
Test: atest MicrodroidTests MicrodroidHostTestCases
Test: Install & run demo app
Change-Id: I4bee00ffd23732bc9dfdca8c4c104b487f24907c
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 6266c18..1fdce03 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -24,7 +24,6 @@
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
 import android.system.virtualmachine.VirtualMachineException;
 import android.system.virtualmachine.VirtualMachineManager;
 import android.util.Log;
@@ -49,7 +48,6 @@
 import java.io.InputStreamReader;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 
 /**
  * This app is to demonstrate the use of APIs in the android.system.virtualmachine library.
@@ -75,10 +73,11 @@
         // When the button is clicked, run or stop the VM
         runStopButton.setOnClickListener(
                 v -> {
-                    if (model.getStatus().getValue() == VirtualMachine.Status.RUNNING) {
+                    Integer status = model.getStatus().getValue();
+                    if (status != null && status == VirtualMachine.STATUS_RUNNING) {
                         model.stop();
                     } else {
-                        CheckBox debugModeCheckBox = (CheckBox) findViewById(R.id.debugMode);
+                        CheckBox debugModeCheckBox = findViewById(R.id.debugMode);
                         final boolean debug = debugModeCheckBox.isChecked();
                         model.run(debug);
                     }
@@ -88,7 +87,7 @@
         model.getStatus()
                 .observeForever(
                         status -> {
-                            if (status == VirtualMachine.Status.RUNNING) {
+                            if (status == VirtualMachine.STATUS_RUNNING) {
                                 runStopButton.setText("Stop");
                                 // Clear the outputs from the previous run
                                 consoleView.setText("");
@@ -150,12 +149,12 @@
         private final MutableLiveData<String> mConsoleOutput = new MutableLiveData<>();
         private final MutableLiveData<String> mLogOutput = new MutableLiveData<>();
         private final MutableLiveData<String> mPayloadOutput = new MutableLiveData<>();
-        private final MutableLiveData<VirtualMachine.Status> mStatus = new MutableLiveData<>();
+        private final MutableLiveData<Integer> mStatus = new MutableLiveData<>();
         private ExecutorService mExecutorService;
 
         public VirtualMachineModel(Application app) {
             super(app);
-            mStatus.setValue(VirtualMachine.Status.DELETED);
+            mStatus.setValue(VirtualMachine.STATUS_DELETED);
         }
 
         /** Runs a VM */
@@ -169,8 +168,8 @@
                         private final ExecutorService mService = mExecutorService;
 
                         @Override
-                        public void onPayloadStarted(
-                                VirtualMachine vm, ParcelFileDescriptor stream) {
+                        public void onPayloadStarted(VirtualMachine vm,
+                                ParcelFileDescriptor stream) {
                             if (stream == null) {
                                 mPayloadOutput.postValue("(no output available)");
                                 return;
@@ -189,25 +188,13 @@
                             }
                             mPayloadOutput.postValue("(Payload is ready. Testing VM service...)");
 
-                            Future<IBinder> service;
-                            try {
-                                service = vm.connectToVsockServer(ITestService.SERVICE_PORT);
-                            } catch (VirtualMachineException e) {
-                                mPayloadOutput.postValue(
-                                        String.format(
-                                                "(Exception while connecting VM's binder"
-                                                        + " service: %s)",
-                                                e.getMessage()));
-                                return;
-                            }
-
-                            mService.execute(() -> testVMService(service));
+                            mService.execute(() -> testVmService(vm));
                         }
 
-                        private void testVMService(Future<IBinder> service) {
+                        private void testVmService(VirtualMachine vm) {
                             IBinder binder;
                             try {
-                                binder = service.get();
+                                binder = vm.connectToVsockServer(ITestService.SERVICE_PORT);
                             } catch (Exception e) {
                                 if (!Thread.interrupted()) {
                                     mPayloadOutput.postValue(
@@ -256,9 +243,9 @@
                         }
 
                         @Override
-                        public void onDied(VirtualMachine vm, int reason) {
+                        public void onStopped(VirtualMachine vm, int reason) {
                             mService.shutdownNow();
-                            mStatus.postValue(VirtualMachine.Status.STOPPED);
+                            mStatus.postValue(VirtualMachine.STATUS_STOPPED);
                         }
 
                         @Override
@@ -273,7 +260,7 @@
                 VirtualMachineConfig.Builder builder =
                         new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json");
                 if (debug) {
-                    builder.debugLevel(DebugLevel.FULL);
+                    builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
                 }
                 VirtualMachineConfig config = builder.build();
                 VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
@@ -306,7 +293,7 @@
             }
             mVirtualMachine = null;
             mExecutorService.shutdownNow();
-            mStatus.postValue(VirtualMachine.Status.STOPPED);
+            mStatus.postValue(VirtualMachine.STATUS_STOPPED);
         }
 
         /** Returns the console output from the VM */
@@ -325,7 +312,7 @@
         }
 
         /** Returns the status of the VM */
-        public LiveData<VirtualMachine.Status> getStatus() {
+        public LiveData<Integer> getStatus() {
             return mStatus;
         }
     }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index cc99006..2b4d185 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -18,16 +18,43 @@
 
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_INVALID_CONFIG;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_PAYLOAD_VERIFICATION_FAILED;
+import static android.system.virtualmachine.VirtualMachineCallback.ERROR_UNKNOWN;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_CRASH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_HANGUP;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_INFRASTRUCTURE_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_KILLED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_REBOOT;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_SHUTDOWN;
+import static android.system.virtualmachine.VirtualMachineCallback.STOP_REASON_UNKNOWN;
+
+import static java.util.Objects.requireNonNull;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.system.virtualizationcommon.ErrorCode;
+import android.system.virtualizationservice.DeathReason;
 import android.system.virtualizationservice.IVirtualMachine;
 import android.system.virtualizationservice.IVirtualMachineCallback;
 import android.system.virtualizationservice.IVirtualizationService;
@@ -45,6 +72,8 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.lang.ref.WeakReference;
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
@@ -55,9 +84,6 @@
 import java.util.Optional;
 import java.util.WeakHashMap;
 import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.zip.ZipFile;
@@ -68,7 +94,7 @@
  *
  * @hide
  */
-public class VirtualMachine {
+public class VirtualMachine implements AutoCloseable {
     private static final Map<Context, Map<String, WeakReference<VirtualMachine>>> sInstances =
             new WeakHashMap<>();
 
@@ -92,18 +118,35 @@
     /** Name of the virtualization service. */
     private static final String SERVICE_NAME = "android.system.virtualizationservice";
 
-    /** Status of a virtual machine */
-    public enum Status {
-        /** The virtual machine has just been created, or {@link #stop()} was called on it. */
-        STOPPED,
-        /** The virtual machine is running. */
-        RUNNING,
-        /**
-         * The virtual machine is deleted. This is a irreversable state. Once a virtual machine is
-         * deleted, it can never be undone, which means all its secrets are permanently lost.
-         */
-        DELETED,
-    }
+    /** The permission needed to create or run a virtual machine. */
+    public static final String MANAGE_VIRTUAL_MACHINE_PERMISSION =
+            "android.permission.MANAGE_VIRTUAL_MACHINE";
+
+    /**
+     * Status of a virtual machine
+     *
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            STATUS_STOPPED,
+            STATUS_RUNNING,
+            STATUS_DELETED
+    })
+    public @interface Status {}
+
+     /** The virtual machine has just been created, or {@link #stop()} was called on it. */
+    public static final int STATUS_STOPPED = 0;
+
+    /** The virtual machine is running. */
+    public static final int STATUS_RUNNING = 1;
+
+    /**
+     * The virtual machine has been deleted. This is an irreversible state. Once a virtual machine
+     * is deleted all its secrets are permanently lost, and it cannot be run. A new virtual machine
+     * with the same name and config may be created, with new and different secrets.
+     */
+    public static final int STATUS_DELETED = 2;
 
     /** Lock for internal synchronization. */
     private final Object mLock = new Object();
@@ -166,8 +209,6 @@
     @Nullable private ParcelFileDescriptor mLogReader;
     @Nullable private ParcelFileDescriptor mLogWriter;
 
-    private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
-
     @NonNull private final Context mContext;
 
     static {
@@ -179,8 +220,8 @@
             throws VirtualMachineException {
         mContext = context;
         mPackageName = context.getPackageName();
-        mName = name;
-        mConfig = config;
+        mName = requireNonNull(name, "Name must not be null");
+        mConfig = requireNonNull(config, "Config must not be null");
         mConfigFilePath = getConfigFilePath(context, name);
 
         final File vmRoot = new File(context.getFilesDir(), VM_DIR);
@@ -193,15 +234,12 @@
     /**
      * Creates a virtual machine with the given name and config. Once a virtual machine is created
      * it is persisted until it is deleted by calling {@link #delete()}. The created virtual machine
-     * is in {@link Status#STOPPED} state. To run the VM, call {@link #run()}.
+     * is in {@link #STATUS_STOPPED} state. To run the VM, call {@link #run()}.
      */
     @NonNull
     static VirtualMachine create(
             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
-        if (config == null) {
-            throw new VirtualMachineException("null config");
-        }
         VirtualMachine vm = new VirtualMachine(context, name, config);
 
         try {
@@ -332,29 +370,29 @@
      *
      * @hide
      */
-    @NonNull
-    public Status getStatus() throws VirtualMachineException {
+    @Status
+    public int getStatus() throws VirtualMachineException {
         try {
             if (mVirtualMachine != null) {
                 switch (mVirtualMachine.getState()) {
                     case VirtualMachineState.NOT_STARTED:
-                        return Status.STOPPED;
+                        return STATUS_STOPPED;
                     case VirtualMachineState.STARTING:
                     case VirtualMachineState.STARTED:
                     case VirtualMachineState.READY:
                     case VirtualMachineState.FINISHED:
-                        return Status.RUNNING;
+                        return STATUS_RUNNING;
                     case VirtualMachineState.DEAD:
-                        return Status.STOPPED;
+                        return STATUS_STOPPED;
                 }
             }
         } catch (RemoteException e) {
             throw new VirtualMachineException(e);
         }
         if (!mConfigFilePath.exists()) {
-            return Status.DELETED;
+            return STATUS_DELETED;
         }
-        return Status.STOPPED;
+        return STATUS_STOPPED;
     }
 
     /**
@@ -363,8 +401,7 @@
      *
      * @hide
      */
-    public void setCallback(
-            @NonNull @CallbackExecutor Executor executor,
+    public void setCallback(@NonNull @CallbackExecutor Executor executor,
             @NonNull VirtualMachineCallback callback) {
         synchronized (mLock) {
             mCallback = callback;
@@ -406,12 +443,13 @@
     /**
      * 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
-     * registering a callback object (not implemented currently).
+     * registering a callback using {@link #setCallback(Executor, VirtualMachineCallback)}.
      *
      * @hide
      */
+    @RequiresPermission(MANAGE_VIRTUAL_MACHINE_PERMISSION)
     public void run() throws VirtualMachineException {
-        if (getStatus() != Status.STOPPED) {
+        if (getStatus() != STATUS_STOPPED) {
             throw new VirtualMachineException(this + " is not in stopped state");
         }
 
@@ -472,8 +510,8 @@
 
             IBinder.DeathRecipient deathRecipient = () -> {
                 if (onDiedCalled.compareAndSet(false, true)) {
-                    executeCallback((cb) -> cb.onDied(VirtualMachine.this,
-                            VirtualMachineCallback.DEATH_REASON_VIRTUALIZATIONSERVICE_DIED));
+                    executeCallback((cb) -> cb.onStopped(VirtualMachine.this,
+                            VirtualMachineCallback.STOP_REASON_VIRTUALIZATION_SERVICE_DIED));
                 }
             };
 
@@ -485,28 +523,37 @@
                             executeCallback(
                                     (cb) -> cb.onPayloadStarted(VirtualMachine.this, stream));
                         }
+
                         @Override
                         public void onPayloadReady(int cid) {
                             executeCallback((cb) -> cb.onPayloadReady(VirtualMachine.this));
                         }
+
                         @Override
                         public void onPayloadFinished(int cid, int exitCode) {
                             executeCallback(
                                     (cb) -> cb.onPayloadFinished(VirtualMachine.this, exitCode));
                         }
+
                         @Override
                         public void onError(int cid, int errorCode, String message) {
+                            int translatedError = getTranslatedError(errorCode);
                             executeCallback(
-                                    (cb) -> cb.onError(VirtualMachine.this, errorCode, message));
+                                    (cb) -> cb.onError(VirtualMachine.this, translatedError,
+                                            message));
                         }
+
                         @Override
                         public void onDied(int cid, int reason) {
-                            // TODO(b/236811123) translate `reason` into a stable reason numbers
                             service.asBinder().unlinkToDeath(deathRecipient, 0);
+                            int translatedReason = getTranslatedReason(reason);
                             if (onDiedCalled.compareAndSet(false, true)) {
-                                executeCallback((cb) -> cb.onDied(VirtualMachine.this, reason));
+                                executeCallback(
+                                        (cb) -> cb.onStopped(VirtualMachine.this,
+                                                translatedReason));
                             }
                         }
+
                         @Override
                         public void onRamdump(int cid, ParcelFileDescriptor ramdump) {
                             executeCallback(
@@ -521,6 +568,60 @@
         }
     }
 
+    @VirtualMachineCallback.ErrorCode
+    private int getTranslatedError(int reason) {
+        switch (reason) {
+            case ErrorCode.PAYLOAD_VERIFICATION_FAILED:
+                return ERROR_PAYLOAD_VERIFICATION_FAILED;
+            case ErrorCode.PAYLOAD_CHANGED:
+                return ERROR_PAYLOAD_CHANGED;
+            case ErrorCode.PAYLOAD_CONFIG_INVALID:
+                return ERROR_PAYLOAD_INVALID_CONFIG;
+            default:
+                return ERROR_UNKNOWN;
+        }
+    }
+
+    @VirtualMachineCallback.StopReason
+    private int getTranslatedReason(int reason) {
+        switch (reason) {
+            case DeathReason.INFRASTRUCTURE_ERROR:
+                return STOP_REASON_INFRASTRUCTURE_ERROR;
+            case DeathReason.KILLED:
+                return STOP_REASON_KILLED;
+            case DeathReason.SHUTDOWN:
+                return STOP_REASON_SHUTDOWN;
+            case DeathReason.ERROR:
+                return STOP_REASON_ERROR;
+            case DeathReason.REBOOT:
+                return STOP_REASON_REBOOT;
+            case DeathReason.CRASH:
+                return STOP_REASON_CRASH;
+            case DeathReason.PVM_FIRMWARE_PUBLIC_KEY_MISMATCH:
+                return STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH;
+            case DeathReason.PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED:
+                return STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED;
+            case DeathReason.BOOTLOADER_PUBLIC_KEY_MISMATCH:
+                return STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH;
+            case DeathReason.BOOTLOADER_INSTANCE_IMAGE_CHANGED:
+                return STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED;
+            case DeathReason.MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE:
+                return STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE;
+            case DeathReason.MICRODROID_PAYLOAD_HAS_CHANGED:
+                return STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED;
+            case DeathReason.MICRODROID_PAYLOAD_VERIFICATION_FAILED:
+                return STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED;
+            case DeathReason.MICRODROID_INVALID_PAYLOAD_CONFIG:
+                return STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG;
+            case DeathReason.MICRODROID_UNKNOWN_RUNTIME_ERROR:
+                return STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR;
+            case DeathReason.HANGUP:
+                return STOP_REASON_HANGUP;
+            default:
+                return STOP_REASON_UNKNOWN;
+        }
+    }
+
     /**
      * Returns the stream object representing the console output from the virtual machine.
      *
@@ -550,7 +651,7 @@
     /**
      * Stops this virtual machine. Stopping a virtual machine is like pulling the plug on a real
      * computer; the machine halts immediately. Software running on the virtual machine is not
-     * notified with the event. A stopped virtual machine can be re-started by calling {@link
+     * notified of the event. A stopped virtual machine can be re-started by calling {@link
      * #run()}.
      *
      * @hide
@@ -566,15 +667,25 @@
     }
 
     /**
+     * Stops this virtual machine. See {@link #stop()}.
+     *
+     * @hide
+     */
+    @Override
+    public void close() throws VirtualMachineException {
+        stop();
+    }
+
+    /**
      * Deletes this virtual machine. Deleting a virtual machine means deleting any persisted data
-     * associated with it including the per-VM secret. This is an irreversable action. A virtual
+     * associated with it including the per-VM secret. This is an irreversible action. A virtual
      * machine once deleted can never be restored. A new virtual machine created with the same name
      * and the same config is different from an already deleted virtual machine.
      *
      * @hide
      */
     public void delete() throws VirtualMachineException {
-        if (getStatus() != Status.STOPPED) {
+        if (getStatus() != STATUS_STOPPED) {
             throw new VirtualMachineException("Virtual machine is not stopped");
         }
         final File vmRootDir = mConfigFilePath.getParentFile();
@@ -599,7 +710,7 @@
      */
     @NonNull
     public Optional<Integer> getCid() throws VirtualMachineException {
-        if (getStatus() != Status.RUNNING) {
+        if (getStatus() != STATUS_RUNNING) {
             return Optional.empty();
         }
         try {
@@ -629,7 +740,7 @@
         if (!oldConfig.isCompatibleWith(newConfig)) {
             throw new VirtualMachineException("incompatible config");
         }
-        if (getStatus() != Status.STOPPED) {
+        if (getStatus() != STATUS_STOPPED) {
             throw new VirtualMachineException(
                     "can't change config while virtual machine is not stopped");
         }
@@ -649,19 +760,19 @@
     private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
 
     /**
-     * Connects to a VM's RPC server via vsock, and returns a root IBinder object. Guest VMs are
+     * Connect to a VM's binder service via vsock and return the root IBinder object. Guest VMs are
      * expected to set up vsock servers in their payload. After the host app receives the {@link
      * VirtualMachineCallback#onPayloadReady(VirtualMachine)}, it can use this method to
-     * establish an RPC session to the guest VMs.
+     * establish a connection to the guest VM.
      *
      * @hide
      */
-    public Future<IBinder> connectToVsockServer(int port) throws VirtualMachineException {
-        if (getStatus() != Status.RUNNING) {
+    @NonNull
+    public IBinder connectToVsockServer(int port) throws VirtualMachineException {
+        if (getStatus() != STATUS_RUNNING) {
             throw new VirtualMachineException("VM is not running");
         }
-        return mExecutorService.submit(
-                () -> nativeConnectToVsockServer(mVirtualMachine.asBinder(), port));
+        return nativeConnectToVsockServer(mVirtualMachine.asBinder(), port);
     }
 
     /**
@@ -669,11 +780,12 @@
      *
      * @hide
      */
+    @NonNull
     public ParcelFileDescriptor connectVsock(int port) throws VirtualMachineException {
         try {
             return mVirtualMachine.connectVsock(port);
         } catch (RemoteException e) {
-            throw new VirtualMachineException("failed to connect Vsock", e);
+            throw new VirtualMachineException("failed to connect vsock", e);
         }
     }
 
@@ -731,7 +843,6 @@
             throws VirtualMachineException {
         try {
             ZipFile zipFile = new ZipFile(context.getPackageCodePath());
-            String payloadPath = config.getPayloadConfigPath();
             InputStream inputStream =
                     zipFile.getInputStream(zipFile.getEntry(config.getPayloadConfigPath()));
             List<String> apkList =
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
index c802678..c89b8bb 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineCallback.java
@@ -19,6 +19,7 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.SuppressLint;
 import android.os.ParcelFileDescriptor;
 
 import java.lang.annotation.Retention;
@@ -30,6 +31,7 @@
  *
  * @hide
  */
+@SuppressLint("CallbackInterface")  // Guidance has changed, lint is out of date (b/245552641)
 public interface VirtualMachineCallback {
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
@@ -43,90 +45,95 @@
 
     /** Error code for all other errors not listed below. */
     int ERROR_UNKNOWN = 0;
-
     /**
      * Error code indicating that the payload can't be verified due to various reasons (e.g invalid
      * merkle tree, invalid formats, etc).
      */
     int ERROR_PAYLOAD_VERIFICATION_FAILED = 1;
-
     /** Error code indicating that the payload is verified, but has changed since the last boot. */
     int ERROR_PAYLOAD_CHANGED = 2;
-
     /** Error code indicating that the payload config is invalid. */
     int ERROR_PAYLOAD_INVALID_CONFIG = 3;
 
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({
-        DEATH_REASON_VIRTUALIZATIONSERVICE_DIED,
-        DEATH_REASON_INFRASTRUCTURE_ERROR,
-        DEATH_REASON_KILLED,
-        DEATH_REASON_UNKNOWN,
-        DEATH_REASON_SHUTDOWN,
-        DEATH_REASON_ERROR,
-        DEATH_REASON_REBOOT,
-        DEATH_REASON_CRASH,
-        DEATH_REASON_HANGUP,
+        STOP_REASON_VIRTUALIZATION_SERVICE_DIED,
+        STOP_REASON_INFRASTRUCTURE_ERROR,
+        STOP_REASON_KILLED,
+        STOP_REASON_UNKNOWN,
+        STOP_REASON_SHUTDOWN,
+        STOP_REASON_ERROR,
+        STOP_REASON_REBOOT,
+        STOP_REASON_CRASH,
+        STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH,
+        STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED,
+        STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH,
+        STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED,
+        STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE,
+        STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED,
+        STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED,
+        STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG,
+        STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR,
+        STOP_REASON_HANGUP,
     })
-    @interface DeathReason {}
+    @interface StopReason {}
 
-    /**
-     * virtualizationservice itself died, taking the VM down with it. This is a negative number to
-     * avoid conflicting with the other death reasons which match the ones in the AIDL interface.
-     */
-    int DEATH_REASON_VIRTUALIZATIONSERVICE_DIED = -1;
+    /** The virtualization service itself died, taking the VM down with it. */
+    //  This is a negative number to avoid conflicting with the other death reasons which match
+    //  the ones in the AIDL interface.
+    int STOP_REASON_VIRTUALIZATION_SERVICE_DIED = -1;
 
     /** There was an error waiting for the VM. */
-    int DEATH_REASON_INFRASTRUCTURE_ERROR = 0;
+    int STOP_REASON_INFRASTRUCTURE_ERROR = 0;
 
     /** The VM was killed. */
-    int DEATH_REASON_KILLED = 1;
+    int STOP_REASON_KILLED = 1;
 
     /** The VM died for an unknown reason. */
-    int DEATH_REASON_UNKNOWN = 2;
+    int STOP_REASON_UNKNOWN = 2;
 
     /** The VM requested to shut down. */
-    int DEATH_REASON_SHUTDOWN = 3;
+    int STOP_REASON_SHUTDOWN = 3;
 
     /** crosvm had an error starting the VM. */
-    int DEATH_REASON_ERROR = 4;
+    int STOP_REASON_ERROR = 4;
 
     /** The VM requested to reboot, possibly as the result of a kernel panic. */
-    int DEATH_REASON_REBOOT = 5;
+    int STOP_REASON_REBOOT = 5;
 
     /** The VM or crosvm crashed. */
-    int DEATH_REASON_CRASH = 6;
+    int STOP_REASON_CRASH = 6;
 
     /** The pVM firmware failed to verify the VM because the public key doesn't match. */
-    int DEATH_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 7;
+    int STOP_REASON_PVM_FIRMWARE_PUBLIC_KEY_MISMATCH = 7;
 
     /** The pVM firmware failed to verify the VM because the instance image changed. */
-    int DEATH_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 8;
+    int STOP_REASON_PVM_FIRMWARE_INSTANCE_IMAGE_CHANGED = 8;
 
     /** The bootloader failed to verify the VM because the public key doesn't match. */
-    int DEATH_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH = 9;
+    int STOP_REASON_BOOTLOADER_PUBLIC_KEY_MISMATCH = 9;
 
     /** The bootloader failed to verify the VM because the instance image changed. */
-    int DEATH_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED = 10;
+    int STOP_REASON_BOOTLOADER_INSTANCE_IMAGE_CHANGED = 10;
 
     /** The microdroid failed to connect to VirtualizationService's RPC server. */
-    int DEATH_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE = 11;
+    int STOP_REASON_MICRODROID_FAILED_TO_CONNECT_TO_VIRTUALIZATION_SERVICE = 11;
 
     /** The payload for microdroid is changed. */
-    int DEATH_REASON_MICRODROID_PAYLOAD_HAS_CHANGED = 12;
+    int STOP_REASON_MICRODROID_PAYLOAD_HAS_CHANGED = 12;
 
     /** The microdroid failed to verify given payload APK. */
-    int DEATH_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED = 13;
+    int STOP_REASON_MICRODROID_PAYLOAD_VERIFICATION_FAILED = 13;
 
     /** The VM config for microdroid is invalid (e.g. missing tasks). */
-    int DEATH_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG = 14;
+    int STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG = 14;
 
     /** There was a runtime error while running microdroid manager. */
-    int DEATH_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR = 15;
+    int STOP_REASON_MICRODROID_UNKNOWN_RUNTIME_ERROR = 15;
 
     /** The VM killed due to hangup */
-    int DEATH_REASON_HANGUP = 16;
+    int STOP_REASON_HANGUP = 16;
 
     /**
      * Called when the payload starts in the VM. The stream, if non-null, provides access
@@ -136,7 +143,7 @@
 
     /**
      * Called when the payload in the VM is ready to serve. See
-     * {@link VirtualMachine#connectToVsockServer(int)} ()}.
+     * {@link VirtualMachine#connectToVsockServer(int)}.
      */
     void onPayloadReady(@NonNull VirtualMachine vm);
 
@@ -146,8 +153,8 @@
     /** Called when an error occurs in the VM. */
     void onError(@NonNull VirtualMachine vm, @ErrorCode int errorCode, @NonNull String message);
 
-    /** Called when the VM has ended. */
-    void onDied(@NonNull VirtualMachine vm, @DeathReason int reason);
+    /** Called when the VM has stopped. */
+    void onStopped(@NonNull VirtualMachine vm, @StopReason int reason);
 
     /** Called when kernel panic occurs and as a result ramdump is generated from the VM. */
     void onRamdump(@NonNull VirtualMachine vm, @NonNull ParcelFileDescriptor ramdump);
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 63a3f43..7f41874 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -18,11 +18,14 @@
 
 import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 
+import static java.util.Objects.requireNonNull;
+
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.PackageInfoFlags;
-import android.content.pm.Signature; // This actually is certificate!
+import android.content.pm.Signature;
 import android.os.ParcelFileDescriptor;
 import android.os.PersistableBundle;
 import android.sysprop.HypervisorProperties;
@@ -33,10 +36,11 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
-import java.util.Objects;
 
 /**
  * Represents a configuration of a virtual machine. A configuration consists of hardware
@@ -61,32 +65,40 @@
     @NonNull private final String mApkPath;
     @NonNull private final Signature[] mCerts;
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({
+            DEBUG_LEVEL_NONE,
+            DEBUG_LEVEL_APP_ONLY,
+            DEBUG_LEVEL_FULL
+    })
+    public @interface DebugLevel {}
+
     /**
-     * A debug level defines the set of debug features that the VM can be configured to.
+     * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
+     * app process running in the VM. This is the default level.
      *
      * @hide
      */
-    public enum DebugLevel {
-        /**
-         * Not debuggable at all. No log is exported from the VM. Debugger can't be attached to the
-         * app process running in the VM. This is the default level.
-         */
-        NONE,
+    public static final int DEBUG_LEVEL_NONE = 0;
 
-        /**
-         * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
-         * attached to the app process. Rest of the VM is not debuggable.
-         */
-        APP_ONLY,
+    /**
+     * Only the app is debuggable. Log from the app is exported from the VM. Debugger can be
+     * attached to the app process. Rest of the VM is not debuggable.
+     *
+     * @hide
+     */
+    public static final int DEBUG_LEVEL_APP_ONLY = 1;
 
-        /**
-         * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
-         * running in the VM can be attached to the debugger. Rooting is possible.
-         */
-        FULL,
-    }
+    /**
+     * Fully debuggable. All logs (both logcat and kernel message) are exported. All processes
+     * running in the VM can be attached to the debugger. Rooting is possible.
+     *
+     * @hide
+     */
+    public static final int DEBUG_LEVEL_FULL = 2;
 
-    private final DebugLevel mDebugLevel;
+    @DebugLevel private final int mDebugLevel;
 
     /**
      * Whether to run the VM in protected mode, so the host can't access its memory.
@@ -112,7 +124,7 @@
             @NonNull String apkPath,
             @NonNull Signature[] certs,
             @NonNull String payloadConfigPath,
-            DebugLevel debugLevel,
+            @DebugLevel int debugLevel,
             boolean protectedVm,
             int memoryMib,
             int numCpus) {
@@ -151,7 +163,11 @@
         if (payloadConfigPath == null) {
             throw new VirtualMachineException("No payloadConfigPath");
         }
-        final DebugLevel debugLevel = DebugLevel.values()[b.getInt(KEY_DEBUGLEVEL)];
+        @DebugLevel final int debugLevel = b.getInt(KEY_DEBUGLEVEL);
+        if (debugLevel != DEBUG_LEVEL_NONE && debugLevel != DEBUG_LEVEL_APP_ONLY
+                && debugLevel != DEBUG_LEVEL_FULL) {
+            throw new VirtualMachineException("Invalid debugLevel: " + debugLevel);
+        }
         final boolean protectedVm = b.getBoolean(KEY_PROTECTED_VM);
         final int memoryMib = b.getInt(KEY_MEMORY_MIB);
         final int numCpus = b.getInt(KEY_NUM_CPUS);
@@ -171,7 +187,7 @@
         String[] certs = certList.toArray(new String[0]);
         b.putStringArray(KEY_CERTS, certs);
         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
-        b.putInt(KEY_DEBUGLEVEL, mDebugLevel.ordinal());
+        b.putInt(KEY_DEBUGLEVEL, mDebugLevel);
         b.putBoolean(KEY_PROTECTED_VM, mProtectedVm);
         b.putInt(KEY_NUM_CPUS, mNumCpus);
         if (mMemoryMib > 0) {
@@ -191,6 +207,44 @@
     }
 
     /**
+     * Returns the debug level for the VM.
+     *
+     * @hide
+     */
+    @NonNull
+    @DebugLevel
+    public int getDebugLevel() {
+        return mDebugLevel;
+    }
+
+    /**
+     * Returns whether the VM's memory will be protected from the host.
+     *
+     * @hide
+     */
+    public boolean isProtectedVm() {
+        return mProtectedVm;
+    }
+
+    /**
+     * Returns the amount of RAM that will be made available to the VM.
+     *
+     * @hide
+     */
+    public int getMemoryMib() {
+        return mMemoryMib;
+    }
+
+    /**
+     * Returns the number of vCPUs that the VM will have.
+     *
+     * @hide
+     */
+    public int getNumCpus() {
+        return mNumCpus;
+    }
+
+    /**
      * 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
@@ -224,15 +278,15 @@
         parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
         parcel.payload = VirtualMachineAppConfig.Payload.configPath(mPayloadConfigPath);
         switch (mDebugLevel) {
-            case NONE:
-                parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
-                break;
-            case APP_ONLY:
+            case DEBUG_LEVEL_APP_ONLY:
                 parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.APP_ONLY;
                 break;
-            case FULL:
+            case DEBUG_LEVEL_FULL:
                 parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.FULL;
                 break;
+            default:
+                parcel.debugLevel = VirtualMachineAppConfig.DebugLevel.NONE;
+                break;
         }
         parcel.protectedVm = mProtectedVm;
         parcel.memoryMib = mMemoryMib;
@@ -248,10 +302,10 @@
      *
      * @hide
      */
-    public static class Builder {
+    public static final class Builder {
         private final Context mContext;
         private final String mPayloadConfigPath;
-        private DebugLevel mDebugLevel;
+        @DebugLevel private int mDebugLevel;
         private boolean mProtectedVm;
         private int mMemoryMib;
         private int mNumCpus;
@@ -262,9 +316,10 @@
          * @hide
          */
         public Builder(@NonNull Context context, @NonNull String payloadConfigPath) {
-            mContext = Objects.requireNonNull(context);
-            mPayloadConfigPath = Objects.requireNonNull(payloadConfigPath);
-            mDebugLevel = DebugLevel.NONE;
+            mContext = requireNonNull(context, "context must not be null");
+            mPayloadConfigPath = requireNonNull(payloadConfigPath,
+                    "payloadConfigPath must not be null");
+            mDebugLevel = DEBUG_LEVEL_NONE;
             mProtectedVm = false;
             mNumCpus = 1;
         }
@@ -274,7 +329,8 @@
          *
          * @hide
          */
-        public Builder debugLevel(DebugLevel debugLevel) {
+        @NonNull
+        public Builder setDebugLevel(@DebugLevel int debugLevel) {
             mDebugLevel = debugLevel;
             return this;
         }
@@ -284,7 +340,8 @@
          *
          * @hide
          */
-        public Builder protectedVm(boolean protectedVm) {
+        @NonNull
+        public Builder setProtectedVm(boolean protectedVm) {
             mProtectedVm = protectedVm;
             return this;
         }
@@ -295,7 +352,8 @@
          *
          * @hide
          */
-        public Builder memoryMib(int memoryMib) {
+        @NonNull
+        public Builder setMemoryMib(int memoryMib) {
             mMemoryMib = memoryMib;
             return this;
         }
@@ -305,7 +363,8 @@
          *
          * @hide
          */
-        public Builder numCpus(int num) {
+        @NonNull
+        public Builder setNumCpus(int num) {
             mNumCpus = num;
             return this;
         }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineException.java b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
index d6aeab3..88b5ea3 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineException.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineException.java
@@ -16,21 +16,27 @@
 
 package android.system.virtualmachine;
 
-/** @hide */
+import android.annotation.Nullable;
+
+/**
+ * Exception thrown when operations on virtual machines fail.
+ *
+ * @hide
+ */
 public class VirtualMachineException extends Exception {
     public VirtualMachineException() {
         super();
     }
 
-    public VirtualMachineException(String message) {
+    public VirtualMachineException(@Nullable String message) {
         super(message);
     }
 
-    public VirtualMachineException(String message, Throwable cause) {
+    public VirtualMachineException(@Nullable String message, @Nullable Throwable cause) {
         super(message, cause);
     }
 
-    public VirtualMachineException(Throwable cause) {
+    public VirtualMachineException(@Nullable Throwable cause) {
         super(cause);
     }
 }
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
index 1ffc6bb..ad5864e 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineManager.java
@@ -16,13 +16,16 @@
 
 package android.system.virtualmachine;
 
+import static java.util.Objects.requireNonNull;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
+import android.annotation.SuppressLint;
 import android.content.Context;
 
 import java.lang.ref.WeakReference;
 import java.util.Map;
-import java.util.Objects;
 import java.util.WeakHashMap;
 
 /**
@@ -46,8 +49,9 @@
      * @hide
      */
     @NonNull
+    @SuppressLint("ManagerLookup") // Optional API
     public static VirtualMachineManager getInstance(@NonNull Context context) {
-        Objects.requireNonNull(context);
+        requireNonNull(context, "context must not be null");
         synchronized (sInstances) {
             VirtualMachineManager vmm =
                     sInstances.containsKey(context) ? sInstances.get(context).get() : null;
@@ -65,13 +69,17 @@
     /**
      * Creates a new {@link VirtualMachine} with the given name and config. Creating a virtual
      * machine with the same name as an existing virtual machine is an error. The existing virtual
-     * machine has to be deleted before its name can be reused. Every call to this methods creates a
-     * new (and different) virtual machine even if the name and the config are the same as the
-     * deleted one.
+     * machine has to be deleted before its name can be reused.
      *
+     * Each successful call to this method creates a new (and different) virtual machine even if the
+     * name and the config are the same as a deleted one. The new virtual machine will initially
+     * be stopped.
+     *
+     * @throws VirtualMachineException If there is an existing virtual machine with the given name
      * @hide
      */
     @NonNull
+    @RequiresPermission(VirtualMachine.MANAGE_VIRTUAL_MACHINE_PERMISSION)
     public VirtualMachine create(
             @NonNull String name, @NonNull VirtualMachineConfig config)
             throws VirtualMachineException {
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
index da08a47..cbb9a0a 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/BenchmarkVmListener.java
@@ -49,7 +49,7 @@
         try {
             IBenchmarkService benchmarkService =
                     IBenchmarkService.Stub.asInterface(
-                            vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT).get());
+                            vm.connectToVsockServer(IBenchmarkService.SERVICE_PORT));
             assertThat(benchmarkService).isNotNull();
 
             mListener.onPayloadReady(vm, benchmarkService);
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 263956b..e0e2274 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -16,6 +16,9 @@
 
 package com.android.microdroid.benchmark;
 
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
+
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -27,7 +30,6 @@
 import android.os.RemoteException;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
 import android.system.virtualmachine.VirtualMachineException;
 import android.util.Log;
 
@@ -91,7 +93,7 @@
             VirtualMachineConfig.Builder builder =
                     mInner.newVmConfigBuilder("assets/vm_config.json");
             VirtualMachineConfig normalConfig =
-                    builder.debugLevel(DebugLevel.NONE).memoryMib(mem).build();
+                    builder.setDebugLevel(DEBUG_LEVEL_NONE).setMemoryMib(mem).build();
             mInner.forceCreateNewVirtualMachine("test_vm_minimum_memory", normalConfig);
 
             if (tryBootVm(TAG, "test_vm_minimum_memory").payloadStarted) return true;
@@ -140,12 +142,12 @@
         List<Double> userspaceBootTimeMetrics = new ArrayList<>();
 
         for (int i = 0; i < trialCount; i++) {
-            VirtualMachineConfig.Builder builder =
-                    mInner.newVmConfigBuilder("assets/vm_config.json");
 
             // To grab boot events from log, set debug mode to FULL
-            VirtualMachineConfig normalConfig =
-                    builder.debugLevel(DebugLevel.FULL).memoryMib(256).build();
+            VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
+                    .setDebugLevel(DEBUG_LEVEL_FULL)
+                    .setMemoryMib(256)
+                    .build();
             mInner.forceCreateNewVirtualMachine("test_vm_boot_time", normalConfig);
 
             BootResult result = tryBootVm(TAG, "test_vm_boot_time");
@@ -189,10 +191,9 @@
 
     @Test
     public void testVsockTransferFromHostToVM() throws Exception {
-        VirtualMachineConfig config =
-                mInner.newVmConfigBuilder("assets/vm_config_io.json")
-                        .debugLevel(DebugLevel.FULL)
-                        .build();
+        VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config_io.json")
+                .setDebugLevel(DEBUG_LEVEL_FULL)
+                .build();
         List<Double> transferRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
         for (int i = 0; i < IO_TEST_TRIAL_COUNT; ++i) {
@@ -216,10 +217,9 @@
     }
 
     private void testVirtioBlkReadRate(boolean isRand) throws Exception {
-        VirtualMachineConfig config =
-                mInner.newVmConfigBuilder("assets/vm_config_io.json")
-                        .debugLevel(DebugLevel.FULL)
-                        .build();
+        VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config_io.json")
+                .setDebugLevel(DEBUG_LEVEL_FULL)
+                .build();
         List<Double> readRates = new ArrayList<>(IO_TEST_TRIAL_COUNT);
 
         for (int i = 0; i < IO_TEST_TRIAL_COUNT + 1; ++i) {
@@ -284,8 +284,8 @@
         final String vmName = "test_vm_mem_usage";
         VirtualMachineConfig config =
                 mInner.newVmConfigBuilder("assets/vm_config_io.json")
-                        .debugLevel(DebugLevel.NONE)
-                        .memoryMib(256)
+                        .setDebugLevel(DEBUG_LEVEL_NONE)
+                        .setMemoryMib(256)
                         .build();
         mInner.forceCreateNewVirtualMachine(vmName, config);
         VirtualMachine vm = mInner.getVirtualMachineManager().get(vmName);
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 a1dee6d..2856a30 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
@@ -80,7 +80,7 @@
         /** Create a new VirtualMachineConfig.Builder with the parameterized protection mode. */
         public VirtualMachineConfig.Builder newVmConfigBuilder(String payloadConfigPath) {
             return new VirtualMachineConfig.Builder(mContext, payloadConfigPath)
-                        .protectedVm(mProtectedVm);
+                        .setProtectedVm(mProtectedVm);
         }
 
         /**
@@ -239,7 +239,7 @@
 
         @Override
         @CallSuper
-        public void onDied(VirtualMachine vm, int reason) {
+        public void onStopped(VirtualMachine vm, int reason) {
             vm.clearCallback();
             mExecutorService.shutdown();
         }
@@ -328,16 +328,16 @@
                     }
 
                     @Override
-                    public void onDied(VirtualMachine vm, int reason) {
+                    public void onStopped(VirtualMachine vm, int reason) {
                         deathReason.complete(reason);
-                        super.onDied(vm, reason);
+                        super.onStopped(vm, reason);
                     }
                 };
         long apiCallNanoTime = System.nanoTime();
         listener.runToFinish(logTag, vm);
         return new BootResult(
                 payloadStarted.getNow(false),
-                deathReason.getNow(VirtualMachineCallback.DEATH_REASON_INFRASTRUCTURE_ERROR),
+                deathReason.getNow(VmEventListener.STOP_REASON_INFRASTRUCTURE_ERROR),
                 apiCallNanoTime,
                 endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
                 listener.getVcpuStartedNanoTime(),
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 4b40293..707930f 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,6 +15,9 @@
  */
 package com.android.microdroid.test;
 
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_FULL;
+import static android.system.virtualmachine.VirtualMachineConfig.DEBUG_LEVEL_NONE;
+
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 
@@ -26,8 +29,6 @@
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineConfig.DebugLevel;
-import android.system.virtualmachine.VirtualMachineException;
 import android.util.Log;
 
 import com.android.compatibility.common.util.CddTest;
@@ -53,7 +54,6 @@
 import java.util.concurrent.CompletableFuture;
 
 import co.nstant.in.cbor.CborDecoder;
-import co.nstant.in.cbor.CborException;
 import co.nstant.in.cbor.model.Array;
 import co.nstant.in.cbor.model.DataItem;
 import co.nstant.in.cbor.model.MajorType;
@@ -86,7 +86,7 @@
             "9.17/C-1-1",
             "9.17/C-2-1"
     })
-    public void connectToVmService() throws VirtualMachineException, InterruptedException {
+    public void connectToVmService() throws Exception {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
             .that(KERNEL_VERSION)
@@ -98,10 +98,10 @@
             String primaryAbi = Build.SUPPORTED_ABIS[0];
             switch(primaryAbi) {
                 case "x86_64":
-                    builder.memoryMib(MIN_MEM_X86_64);
+                    builder.setMemoryMib(MIN_MEM_X86_64);
                     break;
                 case "arm64-v8a":
-                    builder.memoryMib(MIN_MEM_ARM64);
+                    builder.setMemoryMib(MIN_MEM_ARM64);
                     break;
             }
         }
@@ -123,7 +123,7 @@
                     private void testVMService(VirtualMachine vm) {
                         try {
                             ITestService testService = ITestService.Stub.asInterface(
-                                    vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+                                    vm.connectToVsockServer(ITestService.SERVICE_PORT));
                             testResults.mAddInteger = testService.addInteger(123, 456);
                             testResults.mAppRunProp =
                                     testService.readProperty("debug.microdroid.app.run");
@@ -163,15 +163,15 @@
     }
 
     @Test
-    public void bootFailsWhenLowMem() throws VirtualMachineException, InterruptedException {
+    public void bootFailsWhenLowMem() throws Exception {
         for (int memMib : new int[]{ 10, 20, 40 }) {
             VirtualMachineConfig lowMemConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
-                    .memoryMib(memMib)
-                    .debugLevel(DebugLevel.NONE)
+                    .setMemoryMib(memMib)
+                    .setDebugLevel(DEBUG_LEVEL_NONE)
                     .build();
             VirtualMachine vm = mInner.forceCreateNewVirtualMachine("low_mem", lowMemConfig);
             final CompletableFuture<Boolean> onPayloadReadyExecuted = new CompletableFuture<>();
-            final CompletableFuture<Boolean> onDiedExecuted = new CompletableFuture<>();
+            final CompletableFuture<Boolean> onStoppedExecuted = new CompletableFuture<>();
             VmEventListener listener =
                     new VmEventListener() {
                         @Override
@@ -180,14 +180,14 @@
                             super.onPayloadReady(vm);
                         }
                         @Override
-                        public void onDied(VirtualMachine vm,  int reason) {
-                            onDiedExecuted.complete(true);
-                            super.onDied(vm, reason);
+                        public void onStopped(VirtualMachine vm,  int reason) {
+                            onStoppedExecuted.complete(true);
+                            super.onStopped(vm, reason);
                         }
                     };
             listener.runToFinish(TAG, vm);
-            // Assert that onDied() was executed but onPayloadReady() was never run
-            assertThat(onDiedExecuted.getNow(false)).isTrue();
+            // Assert that onStopped() was executed but onPayloadReady() was never run
+            assertThat(onStoppedExecuted.getNow(false)).isTrue();
             assertThat(onPayloadReadyExecuted.getNow(false)).isFalse();
         }
     }
@@ -197,15 +197,14 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void changingDebugLevelInvalidatesVmIdentity()
-            throws VirtualMachineException, InterruptedException, IOException {
+    public void changingDebugLevelInvalidatesVmIdentity() throws Exception {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
             .that(KERNEL_VERSION)
             .isNotEqualTo("5.4");
 
         VirtualMachineConfig.Builder builder = mInner.newVmConfigBuilder("assets/vm_config.json");
-        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_NONE).build();
         mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isTrue();
 
@@ -223,19 +222,18 @@
         // Launch the same VM with different debug level. The Java API prohibits this (thankfully).
         // For testing, we do that by creating a new VM with debug level, and copy the old instance
         // image to the new VM instance image.
-        VirtualMachineConfig debugConfig = builder.debugLevel(DebugLevel.FULL).build();
+        VirtualMachineConfig debugConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
         mInner.forceCreateNewVirtualMachine("test_vm", debugConfig);
         Files.copy(vmInstanceBackup.toPath(), vmInstance.toPath(), REPLACE_EXISTING);
         assertThat(tryBootVm(TAG, "test_vm").payloadStarted).isFalse();
     }
 
-    private class VmCdis {
+    private static class VmCdis {
         public byte[] cdiAttest;
         public byte[] cdiSeal;
     }
 
-    private VmCdis launchVmAndGetCdis(String instanceName)
-            throws VirtualMachineException, InterruptedException {
+    private VmCdis launchVmAndGetCdis(String instanceName) throws Exception {
         VirtualMachine vm = mInner.getVirtualMachineManager().get(instanceName);
         final VmCdis vmCdis = new VmCdis();
         final CompletableFuture<Exception> exception = new CompletableFuture<>();
@@ -245,7 +243,7 @@
                     public void onPayloadReady(VirtualMachine vm) {
                         try {
                             ITestService testService = ITestService.Stub.asInterface(
-                                    vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+                                    vm.connectToVsockServer(ITestService.SERVICE_PORT));
                             vmCdis.cdiAttest = testService.insecurelyExposeAttestationCdi();
                             vmCdis.cdiSeal = testService.insecurelyExposeSealingCdi();
                             forceStop(vm);
@@ -264,15 +262,14 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void instancesOfSameVmHaveDifferentCdis()
-            throws VirtualMachineException, InterruptedException {
+    public void instancesOfSameVmHaveDifferentCdis() throws Exception {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
             .that(KERNEL_VERSION)
             .isNotEqualTo("5.4");
 
         VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
-                .debugLevel(DebugLevel.FULL)
+                .setDebugLevel(DEBUG_LEVEL_FULL)
                 .build();
         mInner.forceCreateNewVirtualMachine("test_vm_a", normalConfig);
         mInner.forceCreateNewVirtualMachine("test_vm_b", normalConfig);
@@ -292,15 +289,14 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void sameInstanceKeepsSameCdis()
-            throws VirtualMachineException, InterruptedException {
+    public void sameInstanceKeepsSameCdis() throws Exception {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
             .that(KERNEL_VERSION)
             .isNotEqualTo("5.4");
 
         VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
-                .debugLevel(DebugLevel.FULL)
+                .setDebugLevel(DEBUG_LEVEL_FULL)
                 .build();
         mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
 
@@ -317,18 +313,16 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void bccIsSuperficiallyWellFormed()
-            throws VirtualMachineException, InterruptedException, CborException {
+    public void bccIsSuperficiallyWellFormed() throws Exception {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
             .that(KERNEL_VERSION)
             .isNotEqualTo("5.4");
 
         VirtualMachineConfig normalConfig = mInner.newVmConfigBuilder("assets/vm_config.json")
-                .debugLevel(DebugLevel.FULL)
+                .setDebugLevel(DEBUG_LEVEL_FULL)
                 .build();
         VirtualMachine vm = mInner.forceCreateNewVirtualMachine("bcc_vm", normalConfig);
-        final VmCdis vmCdis = new VmCdis();
         final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
         final CompletableFuture<Exception> exception = new CompletableFuture<>();
         VmEventListener listener =
@@ -337,7 +331,7 @@
                     public void onPayloadReady(VirtualMachine vm) {
                         try {
                             ITestService testService = ITestService.Stub.asInterface(
-                                    vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+                                    vm.connectToVsockServer(ITestService.SERVICE_PORT));
                             bcc.complete(testService.getBcc());
                             forceStop(vm);
                         } catch (Exception e) {
@@ -398,10 +392,9 @@
         file.writeByte(b ^ 1);
     }
 
-    private RandomAccessFile prepareInstanceImage(String vmName)
-            throws VirtualMachineException, InterruptedException, IOException {
+    private RandomAccessFile prepareInstanceImage(String vmName) throws Exception {
         VirtualMachineConfig config = mInner.newVmConfigBuilder("assets/vm_config.json")
-                .debugLevel(DebugLevel.FULL)
+                .setDebugLevel(DEBUG_LEVEL_FULL)
                 .build();
 
         mInner.forceCreateNewVirtualMachine(vmName, config);
@@ -413,8 +406,7 @@
         return new RandomAccessFile(instanceImgPath, "rw");
     }
 
-    private void assertThatPartitionIsMissing(UUID partitionUuid)
-            throws VirtualMachineException, InterruptedException, IOException {
+    private void assertThatPartitionIsMissing(UUID partitionUuid) throws Exception {
         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
         assertThat(findPartitionDataOffset(instanceFile, partitionUuid).isPresent())
                 .isFalse();
@@ -422,7 +414,7 @@
 
     // Flips a bit of given partition, and then see if boot fails.
     private void assertThatBootFailsAfterCompromisingPartition(UUID partitionUuid)
-            throws VirtualMachineException, InterruptedException, IOException {
+            throws Exception {
         RandomAccessFile instanceFile = prepareInstanceImage("test_vm_integrity");
         OptionalLong offset = findPartitionDataOffset(instanceFile, partitionUuid);
         assertThat(offset.isPresent()).isTrue();
@@ -433,7 +425,7 @@
         assertThat(result.payloadStarted).isFalse();
 
         // This failure should shut the VM down immediately and shouldn't trigger a hangup.
-        assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.DEATH_REASON_HANGUP);
+        assertThat(result.deathReason).isNotEqualTo(VirtualMachineCallback.STOP_REASON_HANGUP);
     }
 
     @Test
@@ -441,8 +433,7 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void bootFailsWhenMicrodroidDataIsCompromised()
-            throws VirtualMachineException, InterruptedException, IOException {
+    public void bootFailsWhenMicrodroidDataIsCompromised() throws Exception {
         assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
     }
 
@@ -451,8 +442,7 @@
             "9.17/C-1-1",
             "9.17/C-2-7"
     })
-    public void bootFailsWhenPvmFwDataIsCompromised()
-            throws VirtualMachineException, InterruptedException, IOException {
+    public void bootFailsWhenPvmFwDataIsCompromised() throws Exception {
         if (mProtectedVm) {
             assertThatBootFailsAfterCompromisingPartition(PVM_FW_PARTITION_UUID);
         } else {
@@ -462,25 +452,23 @@
     }
 
     @Test
-    public void bootFailsWhenConfigIsInvalid()
-            throws VirtualMachineException, InterruptedException, IOException {
+    public void bootFailsWhenConfigIsInvalid() throws Exception {
         VirtualMachineConfig.Builder builder =
                 mInner.newVmConfigBuilder("assets/vm_config_no_task.json");
-        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.FULL).build();
+        VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_FULL).build();
         mInner.forceCreateNewVirtualMachine("test_vm_invalid_config", normalConfig);
 
         BootResult bootResult = tryBootVm(TAG, "test_vm_invalid_config");
         assertThat(bootResult.payloadStarted).isFalse();
         assertThat(bootResult.deathReason).isEqualTo(
-                VirtualMachineCallback.DEATH_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
+                VirtualMachineCallback.STOP_REASON_MICRODROID_INVALID_PAYLOAD_CONFIG);
     }
 
     @Test
-    public void sameInstancesShareTheSameVmObject()
-            throws VirtualMachineException, InterruptedException, IOException {
+    public void sameInstancesShareTheSameVmObject() throws Exception {
         VirtualMachineConfig.Builder builder =
                 mInner.newVmConfigBuilder("assets/vm_config.json");
-        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        VirtualMachineConfig normalConfig = builder.setDebugLevel(DEBUG_LEVEL_NONE).build();
         VirtualMachine vm = mInner.forceCreateNewVirtualMachine("test_vm", normalConfig);
         VirtualMachine vm2 = mInner.getVirtualMachineManager().get("test_vm");
         assertThat(vm).isEqualTo(vm2);