VmLauncherApp for custom configuration
Test: copy vm config json file to /data/local/tmp/vm_config.json, and
then run the app, check the log message
Bug: 330256602
Change-Id: Icbfddd55e3e4b8988043c35b43179ab1e7f04b12
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>