Refactor vm_config.json parsing routine
The routine is separate into a dedicated class. The parsing is now done
structurally using Gson.
Bug: N/A
Test: run Ferrochrome
Change-Id: I25cac5e8a7a12a7a0e6493a9362a239e43a00274
diff --git a/android/VmLauncherApp/Android.bp b/android/VmLauncherApp/Android.bp
index 7103d53..7dd2473 100644
--- a/android/VmLauncherApp/Android.bp
+++ b/android/VmLauncherApp/Android.bp
@@ -11,6 +11,7 @@
"android.system.virtualizationservice_internal-java",
// TODO(b/331708504): will be removed when AVF framework handles surface
"libcrosvm_android_display_service-java",
+ "gson",
],
libs: [
"framework-virtualization.impl",
@@ -22,7 +23,7 @@
"com.android.virt",
],
optimize: {
- optimize: true,
+ proguard_flags_files: ["proguard.flags"],
shrink_resources: true,
},
}
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
index d837c04..160140a 100644
--- a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -17,7 +17,6 @@
package com.android.virtualization.vmlauncher;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.system.virtualmachine.VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
import android.Manifest.permission;
import android.app.Activity;
@@ -26,7 +25,6 @@
import android.content.Intent;
import android.crosvm.ICrosvmAndroidDisplayService;
import android.graphics.PixelFormat;
-import android.graphics.Rect;
import android.hardware.input.InputManager;
import android.os.Bundle;
import android.os.ParcelFileDescriptor;
@@ -36,13 +34,8 @@
import android.system.virtualmachine.VirtualMachine;
import android.system.virtualmachine.VirtualMachineCallback;
import android.system.virtualmachine.VirtualMachineConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
-import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
import android.system.virtualmachine.VirtualMachineException;
import android.system.virtualmachine.VirtualMachineManager;
-import android.util.DisplayMetrics;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
@@ -53,14 +46,9 @@
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import android.view.WindowManager;
-import android.view.WindowMetrics;
import libcore.io.IoBridge;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.FileInputStream;
@@ -72,10 +60,6 @@
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.ArrayList;
-import java.util.Arrays;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -94,164 +78,6 @@
private CursorHandler mCursorHandler;
private ClipboardManager mClipboardManager;
- private VirtualMachineConfig createVirtualMachineConfig(String jsonPath) {
- VirtualMachineConfig.Builder configBuilder =
- new VirtualMachineConfig.Builder(getApplication());
- configBuilder.setCpuTopology(CPU_TOPOLOGY_MATCH_HOST);
-
- configBuilder.setProtectedVm(false);
- if (DEBUG) {
- configBuilder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL);
- configBuilder.setVmOutputCaptured(true);
- configBuilder.setConnectVmConsole(true);
- }
- VirtualMachineCustomImageConfig.Builder customImageConfigBuilder =
- new VirtualMachineCustomImageConfig.Builder();
- try {
- String rawJson = new String(Files.readAllBytes(Path.of(jsonPath)));
- JSONObject json = new JSONObject(rawJson);
- customImageConfigBuilder.setName(json.optString("name", ""));
- if (json.has("kernel")) {
- customImageConfigBuilder.setKernelPath(json.getString("kernel"));
- }
- if (json.has("initrd")) {
- customImageConfigBuilder.setInitrdPath(json.getString("initrd"));
- }
- if (json.has("params")) {
- Arrays.stream(json.getString("params").split(" "))
- .forEach(customImageConfigBuilder::addParam);
- }
- if (json.has("bootloader")) {
- customImageConfigBuilder.setBootloaderPath(json.getString("bootloader"));
- }
- if (json.has("disks")) {
- JSONArray diskArr = json.getJSONArray("disks");
- for (int i = 0; i < diskArr.length(); i++) {
- JSONObject item = diskArr.getJSONObject(i);
- if (item.has("image")) {
- if (item.optBoolean("writable", false)) {
- customImageConfigBuilder.addDisk(
- VirtualMachineCustomImageConfig.Disk.RWDisk(
- item.getString("image")));
- } else {
- customImageConfigBuilder.addDisk(
- VirtualMachineCustomImageConfig.Disk.RODisk(
- item.getString("image")));
- }
- } else if (item.has("partitions")) {
- boolean diskWritable = item.optBoolean("writable", false);
- VirtualMachineCustomImageConfig.Disk disk =
- diskWritable
- ? VirtualMachineCustomImageConfig.Disk.RWDisk(null)
- : VirtualMachineCustomImageConfig.Disk.RODisk(null);
- JSONArray partitions = item.getJSONArray("partitions");
- for (int j = 0; j < partitions.length(); j++) {
- JSONObject partition = partitions.getJSONObject(j);
- String label = partition.getString("label");
- String path = partition.getString("path");
- boolean partitionWritable =
- diskWritable && partition.optBoolean("writable", false);
- String guid = partition.optString("guid");
- VirtualMachineCustomImageConfig.Partition p =
- new VirtualMachineCustomImageConfig.Partition(
- label, path, partitionWritable, guid);
- disk.addPartition(p);
- }
- customImageConfigBuilder.addDisk(disk);
- }
- }
- }
- if (json.has("console_input_device")) {
- configBuilder.setConsoleInputDevice(json.getString("console_input_device"));
- }
- if (json.has("gpu")) {
- JSONObject gpuJson = json.getJSONObject("gpu");
-
- GpuConfig.Builder gpuConfigBuilder = new GpuConfig.Builder();
-
- if (gpuJson.has("backend")) {
- gpuConfigBuilder.setBackend(gpuJson.getString("backend"));
- }
- if (gpuJson.has("context_types")) {
- ArrayList<String> contextTypes = new ArrayList<String>();
- JSONArray contextTypesJson = gpuJson.getJSONArray("context_types");
- for (int i = 0; i < contextTypesJson.length(); i++) {
- contextTypes.add(contextTypesJson.getString(i));
- }
- gpuConfigBuilder.setContextTypes(contextTypes.toArray(new String[0]));
- }
- if (gpuJson.has("pci_address")) {
- gpuConfigBuilder.setPciAddress(gpuJson.getString("pci_address"));
- }
- if (gpuJson.has("renderer_features")) {
- gpuConfigBuilder.setRendererFeatures(gpuJson.getString("renderer_features"));
- }
- if (gpuJson.has("renderer_use_egl")) {
- gpuConfigBuilder.setRendererUseEgl(gpuJson.getBoolean("renderer_use_egl"));
- }
- if (gpuJson.has("renderer_use_gles")) {
- gpuConfigBuilder.setRendererUseGles(gpuJson.getBoolean("renderer_use_gles"));
- }
- if (gpuJson.has("renderer_use_glx")) {
- gpuConfigBuilder.setRendererUseGlx(gpuJson.getBoolean("renderer_use_glx"));
- }
- if (gpuJson.has("renderer_use_surfaceless")) {
- gpuConfigBuilder.setRendererUseSurfaceless(
- gpuJson.getBoolean("renderer_use_surfaceless"));
- }
- if (gpuJson.has("renderer_use_vulkan")) {
- gpuConfigBuilder.setRendererUseVulkan(
- gpuJson.getBoolean("renderer_use_vulkan"));
- }
- customImageConfigBuilder.setGpuConfig(gpuConfigBuilder.build());
- }
-
- long memoryMib = 1024; // 1GB by default
- if (json.has("memory_mib")) {
- memoryMib = json.getLong("memory_mib");
- }
- configBuilder.setMemoryBytes(memoryMib * 1024 * 1024);
-
- WindowMetrics windowMetrics = getWindowManager().getCurrentWindowMetrics();
- float dpi = DisplayMetrics.DENSITY_DEFAULT * windowMetrics.getDensity();
- int refreshRate = (int) getDisplay().getRefreshRate();
- if (json.has("display")) {
- JSONObject display = json.getJSONObject("display");
- if (display.has("scale")) {
- dpi *= (float) display.getDouble("scale");
- }
- if (display.has("refresh_rate")) {
- refreshRate = display.getInt("refresh_rate");
- }
- }
- int dpiInt = (int) dpi;
- DisplayConfig.Builder displayConfigBuilder = new DisplayConfig.Builder();
- Rect windowSize = windowMetrics.getBounds();
- displayConfigBuilder.setWidth(windowSize.right);
- displayConfigBuilder.setHeight(windowSize.bottom);
- displayConfigBuilder.setHorizontalDpi(dpiInt);
- displayConfigBuilder.setVerticalDpi(dpiInt);
- displayConfigBuilder.setRefreshRate(refreshRate);
-
- customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
- customImageConfigBuilder.useTouch(true);
- customImageConfigBuilder.useKeyboard(true);
- customImageConfigBuilder.useMouse(true);
- customImageConfigBuilder.useSwitches(true);
- customImageConfigBuilder.useTrackpad(true);
- customImageConfigBuilder.useNetwork(true);
-
- AudioConfig.Builder audioConfigBuilder = new AudioConfig.Builder();
- audioConfigBuilder.setUseMicrophone(true);
- audioConfigBuilder.setUseSpeaker(true);
- customImageConfigBuilder.setAudioConfig(audioConfigBuilder.build());
- configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
-
- } catch (JSONException | IOException e) {
- throw new IllegalStateException("malformed input", e);
- }
- return configBuilder.build();
- }
private static boolean isVolumeKey(int keyCode) {
return keyCode == KeyEvent.KEYCODE_VOLUME_UP
@@ -386,7 +212,7 @@
try {
VirtualMachineConfig config =
- createVirtualMachineConfig("/data/local/tmp/vm_config.json");
+ VmConfigJson.from("/data/local/tmp/vm_config.json").toConfig(this);
VirtualMachineManager vmm =
getApplication().getSystemService(VirtualMachineManager.class);
if (vmm == null) {
diff --git a/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmConfigJson.java b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmConfigJson.java
new file mode 100644
index 0000000..332b9f5
--- /dev/null
+++ b/android/VmLauncherApp/java/com/android/virtualization/vmlauncher/VmConfigJson.java
@@ -0,0 +1,220 @@
+/*
+ * 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.graphics.Rect;
+import android.system.virtualmachine.VirtualMachineConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.AudioConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.Disk;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.DisplayConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.GpuConfig;
+import android.system.virtualmachine.VirtualMachineCustomImageConfig.Partition;
+import android.util.DisplayMetrics;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
+
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+
+import java.io.FileReader;
+import java.util.Arrays;
+
+/** This class and its inner classes model vm_config.json. */
+class VmConfigJson {
+ private static final boolean DEBUG = true;
+
+ private VmConfigJson() {}
+
+ @SerializedName("protected")
+ private boolean isProtected;
+
+ private String name;
+ private String cpu_topology;
+ private String platform_version;
+ private int memory_mib = 1024;
+ private String console_input_device;
+ private String bootloader;
+ private String kernel;
+ private String initrd;
+ private String params;
+ private DiskJson[] disks;
+ private DisplayJson display;
+ private GpuJson gpu;
+
+ /** Parses JSON file at jsonPath */
+ static VmConfigJson from(String jsonPath) {
+ try (FileReader r = new FileReader(jsonPath)) {
+ return new Gson().fromJson(r, VmConfigJson.class);
+ } catch (Exception e) {
+ throw new RuntimeException("Failed to parse " + jsonPath, e);
+ }
+ }
+
+ private int getCpuTopology() {
+ switch (cpu_topology) {
+ case "one_cpu":
+ return VirtualMachineConfig.CPU_TOPOLOGY_ONE_CPU;
+ case "match_host":
+ return VirtualMachineConfig.CPU_TOPOLOGY_MATCH_HOST;
+ default:
+ throw new RuntimeException("invalid cpu topology: " + cpu_topology);
+ }
+ }
+
+ /** Converts this parsed JSON into VirtualMachieConfig */
+ VirtualMachineConfig toConfig(Context context) {
+ VirtualMachineConfig.Builder builder = new VirtualMachineConfig.Builder(context);
+ builder.setProtectedVm(isProtected)
+ .setMemoryBytes((long) memory_mib * 1024 * 1024)
+ .setConsoleInputDevice(console_input_device)
+ .setCpuTopology(getCpuTopology())
+ .setCustomImageConfig(toCustomImageConfig(context));
+
+ // TODO: make these configurable via json
+ if (DEBUG) {
+ builder.setDebugLevel(VirtualMachineConfig.DEBUG_LEVEL_FULL)
+ .setVmOutputCaptured(true)
+ .setConnectVmConsole(true);
+ }
+
+ return builder.build();
+ }
+
+ private VirtualMachineCustomImageConfig toCustomImageConfig(Context context) {
+ VirtualMachineCustomImageConfig.Builder builder =
+ new VirtualMachineCustomImageConfig.Builder();
+
+ builder.setName(name)
+ .setBootloaderPath(bootloader)
+ .setKernelPath(kernel)
+ .setInitrdPath(initrd);
+ if (params != null) {
+ Arrays.stream(params.split(" ")).forEach(builder::addParam);
+ }
+
+ // TODO: make these configurable via json
+ builder.useTouch(true)
+ .useKeyboard(true)
+ .useMouse(true)
+ .useSwitches(true)
+ .useTrackpad(true)
+ .useNetwork(true)
+ .setAudioConfig(
+ new AudioConfig.Builder()
+ .setUseMicrophone(true)
+ .setUseSpeaker(true)
+ .build());
+
+ for (DiskJson d : disks) {
+ builder.addDisk(d.toConfig());
+ }
+ builder.setDisplayConfig(display.toConfig(context)).setGpuConfig(gpu.toConfig());
+
+ return builder.build();
+ }
+
+ private static class DiskJson {
+ private DiskJson() {}
+
+ private boolean writable;
+ private String image;
+ private PartitionJson[] partitions;
+
+ private Disk toConfig() {
+ Disk d = writable ? Disk.RWDisk(image) : Disk.RODisk(image);
+ for (PartitionJson pj : partitions) {
+ boolean writable = this.writable && pj.writable;
+ d.addPartition(new Partition(pj.label, pj.path, writable, pj.guid));
+ }
+ return d;
+ }
+ }
+
+ private static class PartitionJson {
+ private PartitionJson() {}
+
+ private boolean writable;
+ private String label;
+ private String path;
+ private String guid;
+ }
+
+ private static class DisplayJson {
+ private DisplayJson() {}
+
+ private float scale;
+ private int refresh_rate;
+
+ private DisplayConfig toConfig(Context context) {
+ WindowManager wm = context.getSystemService(WindowManager.class);
+ WindowMetrics metrics = wm.getCurrentWindowMetrics();
+ Rect dispBounds = metrics.getBounds();
+
+ // TODO: make this overridable by json
+ int width = dispBounds.right;
+ int height = dispBounds.bottom;
+
+ int dpi = (int) (DisplayMetrics.DENSITY_DEFAULT * metrics.getDensity());
+ if (scale > 0.0f) {
+ dpi = (int) (dpi * scale);
+ }
+
+ int refreshRate = (int) context.getDisplay().getRefreshRate();
+ if (this.refresh_rate != 0) {
+ refreshRate = this.refresh_rate;
+ }
+
+ return new DisplayConfig.Builder()
+ .setWidth(width)
+ .setHeight(height)
+ .setHorizontalDpi(dpi)
+ .setVerticalDpi(dpi)
+ .setRefreshRate(refreshRate)
+ .build();
+ }
+ }
+
+ private static class GpuJson {
+ private GpuJson() {}
+
+ private String backend;
+ private String pci_address;
+ private String renderer_features;
+ private boolean renderer_use_egl = true;
+ private boolean renderer_use_gles = true;
+ private boolean renderer_use_glx = false;
+ private boolean renderer_use_surfaceless = true;
+ private boolean renderer_use_vulkan = false;
+ private String[] context_types;
+
+ private GpuConfig toConfig() {
+ return new GpuConfig.Builder()
+ .setBackend(backend)
+ .setPciAddress(pci_address)
+ .setRendererFeatures(renderer_features)
+ .setRendererUseEgl(renderer_use_egl)
+ .setRendererUseGles(renderer_use_gles)
+ .setRendererUseGlx(renderer_use_glx)
+ .setRendererUseSurfaceless(renderer_use_surfaceless)
+ .setRendererUseVulkan(renderer_use_vulkan)
+ .setContextTypes(context_types)
+ .build();
+ }
+ }
+}
diff --git a/android/VmLauncherApp/proguard.flags b/android/VmLauncherApp/proguard.flags
new file mode 100644
index 0000000..5e05ecf
--- /dev/null
+++ b/android/VmLauncherApp/proguard.flags
@@ -0,0 +1,7 @@
+# Keep the no-args constructor of the deserialized class
+-keepclassmembers class com.android.virtualization.vmlauncher.VmConfigJson {
+ <init>();
+}
+-keepclassmembers class com.android.virtualization.vmlauncher.VmConfigJson$* {
+ <init>();
+}