Refactor VM lifecycle routine to a separate class
Bug: N/A
Test: N/A
Change-Id: Ib30cf12a6238b85a1b80a9a010e93680bead9df7
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
index 82331b3..376cc7f 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -29,10 +29,8 @@
import android.os.ServiceManager;
import android.system.virtualizationservice_internal.IVirtualizationServiceInternal;
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 android.util.Log;
import android.view.SurfaceView;
import android.view.View;
@@ -56,7 +54,8 @@
public class MainActivity extends Activity {
static final String TAG = "VmLauncherApp";
- private static final String VM_NAME = "my_custom_vm";
+ // TODO: this path should be from outside of this activity
+ private static final String VM_CONFIG_PATH = "/data/local/tmp/vm_config.json";
private static final boolean DEBUG = true;
private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
@@ -83,65 +82,25 @@
mExecutorService = Executors.newCachedThreadPool();
getWindow().setDecorFitsSystemWindows(false);
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) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit
- // this.
- }
+ ConfigJson json = ConfigJson.from(VM_CONFIG_PATH);
+ VirtualMachineConfig config = json.toConfig(this);
- @Override
- public void onPayloadReady(VirtualMachine vm) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit
- // this.
- }
-
- @Override
- public void onPayloadFinished(VirtualMachine vm, int exitCode) {
- // This event is only from Microdroid-based VM. Custom VM shouldn't emit
- // this.
- }
-
- @Override
- public void onError(VirtualMachine vm, int errorCode, String message) {
- Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
- setResult(RESULT_CANCELED);
- finish();
- }
-
- @Override
- public void onStopped(VirtualMachine vm, int reason) {
- Log.d(TAG, "VM stopped. Reason: " + reason);
- setResult(RESULT_OK);
- finish();
- }
- };
+ Runner runner;
+ try {
+ runner = Runner.create(this, config);
+ } catch (VirtualMachineException e) {
+ throw new RuntimeException(e);
+ }
+ mVirtualMachine = runner.getVm();
+ runner.getExitStatus()
+ .thenAcceptAsync(
+ success -> {
+ setResult(success ? RESULT_OK : RESULT_CANCELED);
+ finish();
+ });
try {
- VirtualMachineConfig config =
- ConfigJson.from("/data/local/tmp/vm_config.json").toConfig(this);
- 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 for setting VM config", e);
- }
-
- Log.d(TAG, "vm start");
- mVirtualMachine.run();
- mVirtualMachine.setCallback(Executors.newSingleThreadExecutor(), callback);
if (DEBUG) {
InputStream console = mVirtualMachine.getConsoleOutput();
InputStream log = mVirtualMachine.getLogOutput();
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
new file mode 100644
index 0000000..f50ec86
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/Runner.java
@@ -0,0 +1,111 @@
+/*
+ * 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.content.Context;
+import android.system.virtualmachine.VirtualMachine;
+import android.system.virtualmachine.VirtualMachineCallback;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.system.virtualmachine.VirtualMachineException;
+import android.system.virtualmachine.VirtualMachineManager;
+import android.util.Log;
+
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ForkJoinPool;
+
+/** Utility class for creating a VM and waiting for it to finish. */
+class Runner {
+ private static final String TAG = MainActivity.TAG;
+ private final VirtualMachine mVirtualMachine;
+ private final Callback mCallback;
+
+ private Runner(VirtualMachine vm, Callback cb) {
+ mVirtualMachine = vm;
+ mCallback = cb;
+ }
+
+ /** Create a virtual machine of the given config, under the given context. */
+ static Runner create(Context context, VirtualMachineConfig config)
+ throws VirtualMachineException {
+ VirtualMachineManager vmm = context.getSystemService(VirtualMachineManager.class);
+ VirtualMachineCustomImageConfig customConfig = config.getCustomImageConfig();
+ if (customConfig == null) {
+ throw new RuntimeException("CustomImageConfig is missing");
+ }
+
+ String name = customConfig.getName();
+ if (name == null || name.isEmpty()) {
+ throw new RuntimeException("Virtual machine's name is missing in the config");
+ }
+
+ VirtualMachine vm = vmm.getOrCreate(name, config);
+ try {
+ vm.setConfig(config);
+ } catch (VirtualMachineException e) {
+ vmm.delete(name);
+ vm = vmm.create(name, config);
+ Log.w(TAG, "Re-creating virtual machine (" + name + ")", e);
+ }
+
+ Callback cb = new Callback();
+ vm.setCallback(ForkJoinPool.commonPool(), cb);
+ vm.run();
+ return new Runner(vm, cb);
+ }
+
+ /** Give access to the underlying VirtualMachine object. */
+ VirtualMachine getVm() {
+ return mVirtualMachine;
+ }
+
+ /** Get future about VM's exit status. */
+ CompletableFuture<Boolean> getExitStatus() {
+ return mCallback.mFinishedSuccessfully;
+ }
+
+ private static class Callback implements VirtualMachineCallback {
+ final CompletableFuture<Boolean> mFinishedSuccessfully = new CompletableFuture<>();
+
+ @Override
+ public void onPayloadStarted(VirtualMachine vm) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ @Override
+ public void onPayloadFinished(VirtualMachine vm, int exitCode) {
+ // This event is only from Microdroid-based VM. Custom VM shouldn't emit this.
+ }
+
+ @Override
+ public void onError(VirtualMachine vm, int errorCode, String message) {
+ Log.e(TAG, "Error from VM. code: " + errorCode + " (" + message + ")");
+ mFinishedSuccessfully.complete(false);
+ }
+
+ @Override
+ public void onStopped(VirtualMachine vm, int reason) {
+ Log.d(TAG, "VM stopped. Reason: " + reason);
+ mFinishedSuccessfully.complete(true);
+ }
+ }
+}