/* * 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 android.system.virtualmachine; import android.annotation.Nullable; import android.os.PersistableBundle; import java.util.ArrayList; import java.util.List; import java.util.Optional; /** @hide */ public class VirtualMachineCustomImageConfig { private static final String KEY_NAME = "name"; private static final String KEY_KERNEL = "kernel"; private static final String KEY_INITRD = "initrd"; private static final String KEY_BOOTLOADER = "bootloader"; private static final String KEY_PARAMS = "params"; private static final String KEY_DISK_WRITABLES = "disk_writables"; private static final String KEY_DISK_IMAGES = "disk_images"; private static final String KEY_DISPLAY_CONFIG = "display_config"; private static final String KEY_TOUCH = "touch"; private static final String KEY_KEYBOARD = "keyboard"; @Nullable private final String name; @Nullable private final String kernelPath; @Nullable private final String initrdPath; @Nullable private final String bootloaderPath; @Nullable private final String[] params; @Nullable private final Disk[] disks; @Nullable private final DisplayConfig displayConfig; private final boolean touch; private final boolean keyboard; @Nullable public Disk[] getDisks() { return disks; } @Nullable public String getBootloaderPath() { return bootloaderPath; } @Nullable public String getInitrdPath() { return initrdPath; } @Nullable public String getKernelPath() { return kernelPath; } @Nullable public String getName() { return name; } @Nullable public String[] getParams() { return params; } public boolean useTouch() { return touch; } public boolean useKeyboard() { return keyboard; } /** @hide */ public VirtualMachineCustomImageConfig( String name, String kernelPath, String initrdPath, String bootloaderPath, String[] params, Disk[] disks, DisplayConfig displayConfig, boolean touch, boolean keyboard) { this.name = name; this.kernelPath = kernelPath; this.initrdPath = initrdPath; this.bootloaderPath = bootloaderPath; this.params = params; this.disks = disks; this.displayConfig = displayConfig; this.touch = touch; this.keyboard = keyboard; } static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) { Builder builder = new Builder(); builder.setName(customImageConfigBundle.getString(KEY_NAME)); builder.setKernelPath(customImageConfigBundle.getString(KEY_KERNEL)); builder.setInitrdPath(customImageConfigBundle.getString(KEY_INITRD)); builder.setBootloaderPath(customImageConfigBundle.getString(KEY_BOOTLOADER)); String[] params = customImageConfigBundle.getStringArray(KEY_PARAMS); if (params != null) { for (String param : params) { builder.addParam(param); } } boolean[] writables = customImageConfigBundle.getBooleanArray(KEY_DISK_WRITABLES); String[] diskImages = customImageConfigBundle.getStringArray(KEY_DISK_IMAGES); if (writables != null && diskImages != null) { if (writables.length == diskImages.length) { for (int i = 0; i < writables.length; i++) { builder.addDisk( writables[i] ? Disk.RWDisk(diskImages[i]) : Disk.RODisk(diskImages[i])); } } } PersistableBundle displayConfigPb = customImageConfigBundle.getPersistableBundle(KEY_DISPLAY_CONFIG); builder.setDisplayConfig(DisplayConfig.from(displayConfigPb)); builder.useTouch(customImageConfigBundle.getBoolean(KEY_TOUCH)); builder.useKeyboard(customImageConfigBundle.getBoolean(KEY_KEYBOARD)); return builder.build(); } PersistableBundle toPersistableBundle() { PersistableBundle pb = new PersistableBundle(); pb.putString(KEY_NAME, this.name); pb.putString(KEY_KERNEL, this.kernelPath); pb.putString(KEY_BOOTLOADER, this.bootloaderPath); pb.putString(KEY_INITRD, this.initrdPath); pb.putStringArray(KEY_PARAMS, this.params); if (disks != null) { boolean[] writables = new boolean[disks.length]; String[] images = new String[disks.length]; for (int i = 0; i < disks.length; i++) { writables[i] = disks[i].writable; images[i] = disks[i].imagePath; } pb.putBooleanArray(KEY_DISK_WRITABLES, writables); pb.putStringArray(KEY_DISK_IMAGES, images); } pb.putPersistableBundle( KEY_DISPLAY_CONFIG, Optional.ofNullable(displayConfig) .map(dc -> dc.toPersistableBundle()) .orElse(null)); pb.putBoolean(KEY_TOUCH, touch); pb.putBoolean(KEY_KEYBOARD, keyboard); return pb; } @Nullable public DisplayConfig getDisplayConfig() { return displayConfig; } /** @hide */ public static final class Disk { private final boolean writable; private final String imagePath; private Disk(boolean writable, String imagePath) { this.writable = writable; this.imagePath = imagePath; } /** @hide */ public static Disk RWDisk(String imagePath) { return new Disk(true, imagePath); } /** @hide */ public static Disk RODisk(String imagePath) { return new Disk(false, imagePath); } /** @hide */ public boolean isWritable() { return writable; } /** @hide */ public String getImagePath() { return imagePath; } } /** @hide */ public static final class Builder { private String name; private String kernelPath; private String initrdPath; private String bootloaderPath; private List params = new ArrayList<>(); private List disks = new ArrayList<>(); private DisplayConfig displayConfig; private boolean touch; private boolean keyboard; /** @hide */ public Builder() {} /** @hide */ public Builder setName(String name) { this.name = name; return this; } /** @hide */ public Builder setKernelPath(String kernelPath) { this.kernelPath = kernelPath; return this; } /** @hide */ public Builder setBootloaderPath(String bootloaderPath) { this.bootloaderPath = bootloaderPath; return this; } /** @hide */ public Builder setInitrdPath(String initrdPath) { this.initrdPath = initrdPath; return this; } /** @hide */ public Builder addDisk(Disk disk) { this.disks.add(disk); return this; } /** @hide */ public Builder addParam(String param) { this.params.add(param); return this; } /** @hide */ public Builder setDisplayConfig(DisplayConfig displayConfig) { this.displayConfig = displayConfig; return this; } /** @hide */ public Builder useTouch(boolean touch) { this.touch = touch; return this; } /** @hide */ public Builder useKeyboard(boolean keyboard) { this.keyboard = keyboard; return this; } /** @hide */ public VirtualMachineCustomImageConfig build() { return new VirtualMachineCustomImageConfig( this.name, this.kernelPath, this.initrdPath, this.bootloaderPath, this.params.toArray(new String[0]), this.disks.toArray(new Disk[0]), displayConfig, touch, keyboard); } } /** @hide */ public static final class DisplayConfig { private static final String KEY_WIDTH = "width"; private static final String KEY_HEIGHT = "height"; private static final String KEY_HORIZONTAL_DPI = "horizontal_dpi"; private static final String KEY_VERTICAL_DPI = "vertical_dpi"; private static final String KEY_REFRESH_RATE = "refresh_rate"; private final int width; private final int height; private final int horizontalDpi; private final int verticalDpi; private final int refreshRate; private DisplayConfig( int width, int height, int horizontalDpi, int verticalDpi, int refreshRate) { this.width = width; this.height = height; this.horizontalDpi = horizontalDpi; this.verticalDpi = verticalDpi; this.refreshRate = refreshRate; } /** @hide */ public int getWidth() { return width; } /** @hide */ public int getHeight() { return height; } /** @hide */ public int getHorizontalDpi() { return horizontalDpi; } /** @hide */ public int getVerticalDpi() { return verticalDpi; } /** @hide */ public int getRefreshRate() { return refreshRate; } android.system.virtualizationservice.DisplayConfig toParcelable() { android.system.virtualizationservice.DisplayConfig parcelable = new android.system.virtualizationservice.DisplayConfig(); parcelable.width = this.width; parcelable.height = this.height; parcelable.horizontalDpi = this.horizontalDpi; parcelable.verticalDpi = this.verticalDpi; parcelable.refreshRate = this.refreshRate; return parcelable; } private static DisplayConfig from(PersistableBundle pb) { if (pb == null) { return null; } Builder builder = new Builder(); builder.setWidth(pb.getInt(KEY_WIDTH)); builder.setHeight(pb.getInt(KEY_HEIGHT)); builder.setHorizontalDpi(pb.getInt(KEY_HORIZONTAL_DPI)); builder.setVerticalDpi(pb.getInt(KEY_VERTICAL_DPI)); builder.setRefreshRate(pb.getInt(KEY_REFRESH_RATE)); return builder.build(); } private PersistableBundle toPersistableBundle() { PersistableBundle pb = new PersistableBundle(); pb.putInt(KEY_WIDTH, this.width); pb.putInt(KEY_HEIGHT, this.height); pb.putInt(KEY_HORIZONTAL_DPI, this.horizontalDpi); pb.putInt(KEY_VERTICAL_DPI, this.verticalDpi); pb.putInt(KEY_REFRESH_RATE, this.refreshRate); return pb; } /** @hide */ public static class Builder { // Default values come from external/crosvm/vm_control/src/gpu.rs private int width; private int height; private int horizontalDpi = 320; private int verticalDpi = 320; private int refreshRate = 60; /** @hide */ public Builder() {} /** @hide */ public Builder setWidth(int width) { this.width = width; return this; } /** @hide */ public Builder setHeight(int height) { this.height = height; return this; } /** @hide */ public Builder setHorizontalDpi(int horizontalDpi) { this.horizontalDpi = horizontalDpi; return this; } /** @hide */ public Builder setVerticalDpi(int verticalDpi) { this.verticalDpi = verticalDpi; return this; } /** @hide */ public Builder setRefreshRate(int refreshRate) { this.refreshRate = refreshRate; return this; } /** @hide */ public DisplayConfig build() { if (this.width == 0 || this.height == 0) { throw new IllegalStateException("width and height must be specified"); } return new DisplayConfig(width, height, horizontalDpi, verticalDpi, refreshRate); } } } }