Merge "pvmfw: Expect proper CPU compatible "arm,armv8"" into main
diff --git a/apex/Android.bp b/apex/Android.bp
index e6c809c..48b7b1f 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -32,6 +32,13 @@
},
}
+soong_config_string_variable {
+ name: "avf_microdroid_guest_gki_version",
+ values: [
+ "android14_61_pkvm_experimental",
+ ],
+}
+
soong_config_module_type {
name: "avf_flag_aware_apex_defaults",
module_type: "apex_defaults",
@@ -44,6 +51,9 @@
"release_avf_enable_virt_cpufreq",
"release_avf_support_custom_vm_with_paravirtualized_devices",
],
+ variables: [
+ "avf_microdroid_guest_gki_version",
+ ],
properties: [
"androidManifest",
"arch",
@@ -154,6 +164,16 @@
"EmptyPayloadApp",
],
soong_config_variables: {
+ avf_microdroid_guest_gki_version: {
+ android14_61_pkvm_experimental: {
+ prebuilts: [
+ "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
+ "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
+ "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
+ "microdroid_gki-android14-6.1-pkvm_experimental.json",
+ ],
+ },
+ },
release_avf_enable_device_assignment: {
prebuilts: [
"com.android.virt.vfio_handler.rc",
@@ -170,14 +190,6 @@
release_avf_enable_llpvm_changes: {
androidManifest: "AndroidManifest.xml",
},
- release_avf_enable_vendor_modules: {
- prebuilts: [
- "microdroid_gki-android14-6.1_initrd_debuggable",
- "microdroid_gki-android14-6.1_initrd_normal",
- "microdroid_gki-android14-6.1_kernel",
- "microdroid_gki-android14-6.1.json",
- ],
- },
release_avf_enable_remote_attestation: {
vintf_fragments: [
"virtualizationservice.xml",
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 7c59b54..55fe6d2 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -498,7 +498,7 @@
RunCommand(args, cmd)
-gki_versions = ['android14-6.1']
+gki_versions = ['android14-6.1-pkvm_experimental']
# dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
virt_apex_non_gki_files = {
diff --git a/compos/apex/Android.bp b/compos/apex/Android.bp
index 55cc446..9996e4e 100644
--- a/compos/apex/Android.bp
+++ b/compos/apex/Android.bp
@@ -51,6 +51,13 @@
"compsvc",
],
+ native_shared_libs: [
+ // b/334192594: compsvc has a transitive dependency to libminijail.
+ // Adding it explicitly here is required because the existence of
+ // it in Microdroid cannot be guaranteed.
+ "libminijail",
+ ],
+
systemserverclasspath_fragments: ["com.android.compos-systemserverclasspath-fragment"],
apps: [
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index 51b91ca..1076219 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -63,6 +63,7 @@
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.system.virtualizationcommon.DeathReason;
import android.system.virtualizationcommon.ErrorCode;
@@ -102,6 +103,7 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
+import java.util.Arrays;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -161,6 +163,7 @@
public static final long MAX_VSOCK_PORT = (1L << 32) - 1;
private ParcelFileDescriptor mTouchSock;
+ private ParcelFileDescriptor mKeySock;
/**
* Status of a virtual machine
@@ -861,25 +864,48 @@
// Handle input devices here
List<InputDevice> inputDevices = new ArrayList<>();
if (vmConfig.getCustomImageConfig() != null
- && vmConfig.getCustomImageConfig().useTouch()
&& rawConfig.displayConfig != null) {
- ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
- mTouchSock = pfds[0];
- InputDevice.SingleTouch t = new InputDevice.SingleTouch();
- t.width = rawConfig.displayConfig.width;
- t.height = rawConfig.displayConfig.height;
- t.pfd = pfds[1];
- inputDevices.add(InputDevice.singleTouch(t));
+ if (vmConfig.getCustomImageConfig().useTouch()) {
+ ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+ mTouchSock = pfds[0];
+ InputDevice.SingleTouch t = new InputDevice.SingleTouch();
+ t.width = rawConfig.displayConfig.width;
+ t.height = rawConfig.displayConfig.height;
+ t.pfd = pfds[1];
+ inputDevices.add(InputDevice.singleTouch(t));
+ }
+ if (vmConfig.getCustomImageConfig().useKeyboard()) {
+ ParcelFileDescriptor[] pfds = ParcelFileDescriptor.createSocketPair();
+ mKeySock = pfds[0];
+ InputDevice.Keyboard k = new InputDevice.Keyboard();
+ k.pfd = pfds[1];
+ inputDevices.add(InputDevice.keyboard(k));
+ }
}
rawConfig.inputDevices = inputDevices.toArray(new InputDevice[0]);
return android.system.virtualizationservice.VirtualMachineConfig.rawConfig(rawConfig);
}
- private void addInputEvent(ByteBuffer buffer, short type, short code, int value) {
- buffer.putShort(type);
- buffer.putShort(code);
- buffer.putInt(value);
+ private static record InputEvent(short type, short code, int value) {}
+
+ /** @hide */
+ public boolean sendKeyEvent(KeyEvent event) {
+ if (mKeySock == null) {
+ Log.d(TAG, "mKeySock == null");
+ return false;
+ }
+ // from include/uapi/linux/input-event-codes.h in the kernel.
+ short EV_SYN = 0x00;
+ short EV_KEY = 0x01;
+ short SYN_REPORT = 0x00;
+ boolean down = event.getAction() != MotionEvent.ACTION_UP;
+
+ return writeEventsToSock(
+ mKeySock,
+ Arrays.asList(
+ new InputEvent(EV_KEY, (short) event.getScanCode(), down ? 1 : 0),
+ new InputEvent(EV_SYN, SYN_REPORT, 0)));
}
/** @hide */
@@ -901,24 +927,30 @@
int y = (int) event.getY();
boolean down = event.getAction() != MotionEvent.ACTION_UP;
+ return writeEventsToSock(
+ mTouchSock,
+ Arrays.asList(
+ new InputEvent(EV_ABS, ABS_X, x),
+ new InputEvent(EV_ABS, ABS_Y, y),
+ new InputEvent(EV_KEY, BTN_TOUCH, down ? 1 : 0),
+ new InputEvent(EV_SYN, SYN_REPORT, 0)));
+ }
+
+ private boolean writeEventsToSock(ParcelFileDescriptor sock, List<InputEvent> evtList) {
ByteBuffer byteBuffer =
- ByteBuffer.allocate(32 /* (type: u16 + code: u16 + value: i32) * 4 */);
+ ByteBuffer.allocate(8 /* (type: u16 + code: u16 + value: i32) */ * evtList.size());
byteBuffer.clear();
byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
-
- addInputEvent(byteBuffer, EV_ABS, ABS_X, x);
- addInputEvent(byteBuffer, EV_ABS, ABS_Y, y);
- addInputEvent(byteBuffer, EV_KEY, BTN_TOUCH, down ? 1 : 0);
- addInputEvent(byteBuffer, EV_SYN, SYN_REPORT, 0);
-
+ for (InputEvent e : evtList) {
+ byteBuffer.putShort(e.type);
+ byteBuffer.putShort(e.code);
+ byteBuffer.putInt(e.value);
+ }
try {
IoBridge.write(
- mTouchSock.getFileDescriptor(),
- byteBuffer.array(),
- 0,
- byteBuffer.array().length);
+ sock.getFileDescriptor(), byteBuffer.array(), 0, byteBuffer.array().length);
} catch (IOException e) {
- Log.d(TAG, "cannot send touch evt", e);
+ Log.d(TAG, "cannot send event", e);
return false;
}
return true;
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
index 7cf5893..8d294fd 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachineCustomImageConfig.java
@@ -35,6 +35,7 @@
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;
@NonNull private final String kernelPath;
@@ -44,6 +45,7 @@
@Nullable private final Disk[] disks;
@Nullable private final DisplayConfig displayConfig;
private final boolean touch;
+ private final boolean keyboard;
@Nullable
public Disk[] getDisks() {
@@ -79,6 +81,10 @@
return touch;
}
+ public boolean useKeyboard() {
+ return keyboard;
+ }
+
/** @hide */
public VirtualMachineCustomImageConfig(
String name,
@@ -88,7 +94,8 @@
String[] params,
Disk[] disks,
DisplayConfig displayConfig,
- boolean touch) {
+ boolean touch,
+ boolean keyboard) {
this.name = name;
this.kernelPath = kernelPath;
this.initrdPath = initrdPath;
@@ -97,6 +104,7 @@
this.disks = disks;
this.displayConfig = displayConfig;
this.touch = touch;
+ this.keyboard = keyboard;
}
static VirtualMachineCustomImageConfig from(PersistableBundle customImageConfigBundle) {
@@ -125,6 +133,7 @@
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();
}
@@ -154,6 +163,7 @@
.map(dc -> dc.toPersistableBundle())
.orElse(null));
pb.putBoolean(KEY_TOUCH, touch);
+ pb.putBoolean(KEY_KEYBOARD, keyboard);
return pb;
}
@@ -203,6 +213,7 @@
private List<Disk> disks = new ArrayList<>();
private DisplayConfig displayConfig;
private boolean touch;
+ private boolean keyboard;
/** @hide */
public Builder() {}
@@ -256,6 +267,12 @@
}
/** @hide */
+ public Builder useKeyboard(boolean keyboard) {
+ this.keyboard = keyboard;
+ return this;
+ }
+
+ /** @hide */
public VirtualMachineCustomImageConfig build() {
return new VirtualMachineCustomImageConfig(
this.name,
@@ -265,7 +282,8 @@
this.params.toArray(new String[0]),
this.disks.toArray(new Disk[0]),
displayConfig,
- touch);
+ touch,
+ keyboard);
}
}
diff --git a/libs/vmconfig/src/lib.rs b/libs/vmconfig/src/lib.rs
index 907e0d3..7c917b0 100644
--- a/libs/vmconfig/src/lib.rs
+++ b/libs/vmconfig/src/lib.rs
@@ -18,6 +18,8 @@
aidl::android::system::virtualizationservice::CpuTopology::CpuTopology,
aidl::android::system::virtualizationservice::DiskImage::DiskImage as AidlDiskImage,
aidl::android::system::virtualizationservice::Partition::Partition as AidlPartition,
+ aidl::android::system::virtualizationservice::VirtualMachineAppConfig::DebugLevel::DebugLevel,
+ aidl::android::system::virtualizationservice::VirtualMachineConfig::VirtualMachineConfig,
aidl::android::system::virtualizationservice::VirtualMachineRawConfig::VirtualMachineRawConfig,
binder::ParcelFileDescriptor,
};
@@ -127,6 +129,14 @@
}
}
+/// Returns the debug level of the VM from its configuration.
+pub fn get_debug_level(config: &VirtualMachineConfig) -> Option<DebugLevel> {
+ match config {
+ VirtualMachineConfig::AppConfig(config) => Some(config.debugLevel),
+ VirtualMachineConfig::RawConfig(_) => None,
+ }
+}
+
/// A disk image to be made available to the VM.
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
pub struct DiskImage {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 33d98dc..98a541f 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -582,39 +582,39 @@
}
///////////////////////////////////////
-// GKI-android14-6.1 modules
+// GKI-android14-6.1-pkvm_experimental modules
///////////////////////////////////////
prebuilt_etc {
- name: "microdroid_gki-android14-6.1.json",
- src: "microdroid_gki-android14-6.1.json",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental.json",
+ src: "microdroid_gki-android14-6.1-pkvm_experimental.json",
}
avb_add_hash_footer {
- name: "microdroid_gki-android14-6.1_kernel_signed",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
defaults: ["microdroid_kernel_signed_defaults"],
- filename: "microdroid_gki-android14-6.1_kernel_signed",
+ filename: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
arch: {
arm64: {
- src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+ src: ":microdroid_gki_kernel_prebuilts-6.1-pkvm_experimental-arm64",
},
x86_64: {
- src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+ src: ":microdroid_gki_kernel_prebuilts-6.1-pkvm_experimental-x86_64",
},
},
include_descriptors_from_images: [
- ":microdroid_gki-android14-6.1_initrd_normal_hashdesc",
- ":microdroid_gki-android14-6.1_initrd_debug_hashdesc",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_hashdesc",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debug_hashdesc",
],
}
// HACK: use cc_genrule for arch-specific properties
cc_genrule {
- name: "microdroid_gki-android14-6.1_kernel_signed-lz4",
- out: ["microdroid_gki-android14-6.1_kernel_signed-lz4"],
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4",
+ out: ["microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4"],
srcs: [":empty_file"],
arch: {
arm64: {
- srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+ srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
exclude_srcs: [":empty_file"],
},
},
@@ -623,30 +623,30 @@
}
prebuilt_etc {
- name: "microdroid_gki-android14-6.1_kernel",
- filename: "microdroid_gki-android14-6.1_kernel",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
+ filename: "microdroid_gki-android14-6.1-pkvm_experimental_kernel",
src: ":empty_file",
relative_install_path: "fs",
arch: {
arm64: {
- src: ":microdroid_gki-android14-6.1_kernel_signed-lz4",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed-lz4",
},
x86_64: {
- src: ":microdroid_gki-android14-6.1_kernel_signed",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed",
},
},
}
avb_gen_vbmeta_image {
- name: "microdroid_gki-android14-6.1_initrd_normal_hashdesc",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_hashdesc",
defaults: ["microdroid_initrd_normal_defaults"],
- src: ":microdroid_gki-android14-6.1_initrd_normal",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
}
avb_gen_vbmeta_image {
- name: "microdroid_gki-android14-6.1_initrd_debug_hashdesc",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debug_hashdesc",
defaults: ["microdroid_initrd_debug_defaults"],
- src: ":microdroid_gki-android14-6.1_initrd_debuggable",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
}
python_binary_host {
@@ -660,10 +660,10 @@
srcs: [":microdroid_kernel"],
arch: {
arm64: {
- srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+ srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
},
x86_64: {
- srcs: [":microdroid_gki-android14-6.1_kernel_signed"],
+ srcs: [":microdroid_gki-android14-6.1-pkvm_experimental_kernel_signed"],
},
},
out: ["lib.rs"],
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 544a3ff..7246f04 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -41,22 +41,22 @@
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_gen_arm64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
srcs: [
":microdroid_ramdisk",
":microdroid_first_stage_ramdisk",
- ":microdroid_gki_modules-6.1-arm64",
+ ":microdroid_gki_modules-6.1-pkvm_experimental-arm64",
],
out: ["microdroid_initrd.img"],
cmd: "cat $(in) > $(out)",
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_gen_x86_64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
srcs: [
":microdroid_ramdisk",
":microdroid_first_stage_ramdisk",
- ":microdroid_gki_modules-6.1-x86_64",
+ ":microdroid_gki_modules-6.1-pkvm_experimental-x86_64",
],
out: ["microdroid_initrd.img"],
cmd: "cat $(in) > $(out)",
@@ -96,13 +96,13 @@
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_debuggable_arm64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki-android14-6.1_initrd_gen_arm64",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
":microdroid_bootconfig_debuggable_src",
] + bootconfigs_arm64,
- out: ["microdroid_gki-android14-6.1_initrd_debuggable_arm64"],
+ out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -118,13 +118,13 @@
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
":microdroid_bootconfig_debuggable_src",
] + bootconfigs_x86_64,
- out: ["microdroid_gki-android14-6.1_initrd_debuggable_x86_64"],
+ out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -140,13 +140,13 @@
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_normal_arm64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki-android14-6.1_initrd_gen_arm64",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_arm64",
":microdroid_bootconfig_normal_src",
] + bootconfigs_arm64,
- out: ["microdroid_gki-android14-6.1_initrd_normal_arm64"],
+ out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -162,13 +162,13 @@
}
genrule {
- name: "microdroid_gki-android14-6.1_initrd_normal_x86_64",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki-android14-6.1_initrd_gen_x86_64",
+ ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_gen_x86_64",
":microdroid_bootconfig_normal_src",
] + bootconfigs_x86_64,
- out: ["microdroid_gki-android14-6.1_initrd_normal_x86_64"],
+ out: ["microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -188,18 +188,18 @@
}
prebuilt_etc {
- name: "microdroid_gki-android14-6.1_initrd_debuggable",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable",
// We don't have ramdisk for architectures other than x86_64 & arm64
src: ":empty_file",
arch: {
x86_64: {
- src: ":microdroid_gki-android14-6.1_initrd_debuggable_x86_64",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_x86_64",
},
arm64: {
- src: ":microdroid_gki-android14-6.1_initrd_debuggable_arm64",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable_arm64",
},
},
- filename: "microdroid_gki-android14-6.1_initrd_debuggable.img",
+ filename: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_debuggable.img",
}
prebuilt_etc {
@@ -218,16 +218,16 @@
}
prebuilt_etc {
- name: "microdroid_gki-android14-6.1_initrd_normal",
+ name: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal",
// We don't have ramdisk for architectures other than x86_64 & arm64
src: ":empty_file",
arch: {
x86_64: {
- src: ":microdroid_gki-android14-6.1_initrd_normal_x86_64",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_x86_64",
},
arm64: {
- src: ":microdroid_gki-android14-6.1_initrd_normal_arm64",
+ src: ":microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal_arm64",
},
},
- filename: "microdroid_gki-android14-6.1_initrd_normal.img",
+ filename: "microdroid_gki-android14-6.1-pkvm_experimental_initrd_normal.img",
}
diff --git a/microdroid/microdroid_gki-android14-6.1.json b/microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
similarity index 92%
rename from microdroid/microdroid_gki-android14-6.1.json
rename to microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
index 9392fae..4e58573 100644
--- a/microdroid/microdroid_gki-android14-6.1.json
+++ b/microdroid/microdroid_gki-android14-6.1-pkvm_experimental.json
@@ -1,5 +1,5 @@
{
- "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android14-6.1_kernel",
+ "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-android14-6.1-pkvm_experimental_kernel",
"disks": [
{
"partitions": [
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 253604b..72212c3 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -403,12 +403,6 @@
unsafe { slice::from_raw_parts_mut(range.start.0 as *mut u8, range.end - range.start) }
}
-enum AppendedConfigType {
- Valid,
- Invalid,
- NotFound,
-}
-
enum AppendedPayload<'a> {
/// Configuration data.
Config(config::Config<'a>),
@@ -418,35 +412,29 @@
impl<'a> AppendedPayload<'a> {
fn new(data: &'a mut [u8]) -> Option<Self> {
- match Self::guess_config_type(data) {
- AppendedConfigType::Valid => {
- let config = config::Config::new(data);
- Some(Self::Config(config.unwrap()))
- }
- AppendedConfigType::NotFound if cfg!(feature = "legacy") => {
+ // The borrow checker gets confused about the ownership of data (see inline comments) so we
+ // intentionally obfuscate it using a raw pointer; see a similar issue (still not addressed
+ // in v1.77) in https://users.rust-lang.org/t/78467.
+ let data_ptr = data as *mut [u8];
+
+ // Config::new() borrows data as mutable ...
+ match config::Config::new(data) {
+ // ... so this branch has a mutable reference to data, from the Ok(Config<'a>). But ...
+ Ok(valid) => Some(Self::Config(valid)),
+ // ... if Config::new(data).is_err(), the Err holds no ref to data. However ...
+ Err(config::Error::InvalidMagic) if cfg!(feature = "legacy") => {
+ // ... the borrow checker still complains about a second mutable ref without this.
+ // SAFETY: Pointer to a valid mut (not accessed elsewhere), 'a lifetime re-used.
+ let data: &'a mut _ = unsafe { &mut *data_ptr };
+
const BCC_SIZE: usize = SIZE_4KB;
warn!("Assuming the appended data at {:?} to be a raw BCC", data.as_ptr());
Some(Self::LegacyBcc(&mut data[..BCC_SIZE]))
}
- _ => None,
- }
- }
-
- fn guess_config_type(data: &mut [u8]) -> AppendedConfigType {
- // This function is necessary to prevent the borrow checker from getting confused
- // about the ownership of data in new(); see https://users.rust-lang.org/t/78467.
- let addr = data.as_ptr();
-
- match config::Config::new(data) {
- Err(config::Error::InvalidMagic) => {
- warn!("No configuration data found at {addr:?}");
- AppendedConfigType::NotFound
- }
Err(e) => {
- error!("Invalid configuration data at {addr:?}: {e}");
- AppendedConfigType::Invalid
+ error!("Invalid configuration data at {data_ptr:?}: {e}");
+ None
}
- Ok(_) => AppendedConfigType::Valid,
}
}
diff --git a/pvmfw/src/fdt.rs b/pvmfw/src/fdt.rs
index 4d282a1..7e7d641 100644
--- a/pvmfw/src/fdt.rs
+++ b/pvmfw/src/fdt.rs
@@ -199,11 +199,16 @@
opp_node: FdtNode,
) -> libfdt::Result<ArrayVec<[u64; CpuInfo::MAX_OPPTABLES]>> {
let mut table = ArrayVec::new();
- for subnode in opp_node.subnodes()? {
+ let mut opp_nodes = opp_node.subnodes()?;
+ for subnode in opp_nodes.by_ref().take(table.capacity()) {
let prop = subnode.getprop_u64(cstr!("opp-hz"))?.ok_or(FdtError::NotFound)?;
table.push(prop);
}
+ if opp_nodes.next().is_some() {
+ warn!("OPP table has more than {} entries: discarding extra nodes.", table.capacity());
+ }
+
Ok(table)
}
diff --git a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
index 678e56f..f456cb4 100644
--- a/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
+++ b/service_vm/test_apk/src/java/com/android/virt/rkpd/vm_attestation/testapp/RkpdVmAttestationTest.java
@@ -95,6 +95,11 @@
if (mGki == null) {
// We don't need this permission to use the microdroid kernel.
revokePermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
+ } else {
+ // The permission is needed to use the GKI kernel.
+ // Granting the permission is needed as the microdroid kernel test setup
+ // can revoke the permission before the GKI kernel test.
+ grantPermission(VirtualMachine.USE_CUSTOM_VIRTUAL_MACHINE_PERMISSION);
}
prepareTestSetup(true /* protectedVm */, mGki);
setMaxPerformanceTaskProfile();
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 f881909..364e769 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
@@ -67,7 +67,8 @@
protected static final String KERNEL_VERSION = SystemProperties.get("ro.kernel.version");
protected static final Set<String> SUPPORTED_GKI_VERSIONS =
- Collections.unmodifiableSet(new HashSet(Arrays.asList("android14-6.1")));
+ Collections.unmodifiableSet(
+ new HashSet(Arrays.asList("android14-6.1-pkvm_experimental")));
public static boolean isCuttlefish() {
return getDeviceProperties().isCuttlefish();
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 1e81172..203bcae 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -69,7 +69,8 @@
/ MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
protected static final Set<String> SUPPORTED_GKI_VERSIONS =
- Collections.unmodifiableSet(new HashSet(Arrays.asList("android14-6.1")));
+ Collections.unmodifiableSet(
+ new HashSet(Arrays.asList("android14-6.1-pkvm_experimental")));
/* Keep this sync with AssignableDevice.aidl */
public static final class AssignableDevice {
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 2b5c564..e7e9ded 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -174,7 +174,7 @@
boolean updateBootconfigs) {
File signVirtApex = findTestFile("sign_virt_apex");
- RunUtil runUtil = new RunUtil();
+ RunUtil runUtil = createRunUtil();
// Set the parent dir on the PATH (e.g. <workdir>/bin)
String separator = System.getProperty("path.separator");
String path = signVirtApex.getParentFile().getPath() + separator + System.getenv("PATH");
@@ -409,7 +409,7 @@
configPath);
PipedInputStream pis = new PipedInputStream();
- Process process = RunUtil.getDefault().runCmdInBackground(args, new PipedOutputStream(pis));
+ Process process = createRunUtil().runCmdInBackground(args, new PipedOutputStream(pis));
return new VmInfo(process);
}
@@ -890,7 +890,7 @@
File sepolicyAnalyzeBin = findTestFile("sepolicy-analyze");
CommandResult result =
- RunUtil.getDefault()
+ createRunUtil()
.runTimedCmd(
10000,
sepolicyAnalyzeBin.getPath(),
@@ -1034,14 +1034,14 @@
private boolean isLz4(String path) throws Exception {
File lz4tool = findTestFile("lz4");
CommandResult result =
- new RunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path);
+ createRunUtil().runTimedCmd(5000, lz4tool.getAbsolutePath(), "-t", path);
return result.getStatus() == CommandStatus.SUCCESS;
}
private void decompressLz4(String inputPath, String outputPath) throws Exception {
File lz4tool = findTestFile("lz4");
CommandResult result =
- new RunUtil()
+ createRunUtil()
.runTimedCmd(
5000, lz4tool.getAbsolutePath(), "-d", "-f", inputPath, outputPath);
String out = result.getStdout();
@@ -1072,7 +1072,7 @@
List<String> command =
Arrays.asList(avbtool.getAbsolutePath(), "info_image", "--image", image_path);
CommandResult result =
- new RunUtil().runTimedCmd(5000, "/bin/bash", "-c", String.join(" ", command));
+ createRunUtil().runTimedCmd(5000, "/bin/bash", "-c", String.join(" ", command));
String out = result.getStdout();
String err = result.getStderr();
assertWithMessage(
@@ -1241,4 +1241,14 @@
assertThat(androidDevice).isNotNull();
return androidDevice;
}
+
+ // The TradeFed Dockerfile sets LD_LIBRARY_PATH to a directory with an older libc++.so, which
+ // breaks binaries that are linked against a newer libc++.so. Binaries commonly use DT_RUNPATH
+ // to find an adjacent libc++.so (e.g. `$ORIGIN/../lib64`), but LD_LIBRARY_PATH overrides
+ // DT_RUNPATH, so clear LD_LIBRARY_PATH. See b/332593805 and b/333782216.
+ private static RunUtil createRunUtil() {
+ RunUtil runUtil = new RunUtil();
+ runUtil.unsetEnvVariable("LD_LIBRARY_PATH");
+ return runUtil;
+ }
}
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index c5f1ab7..0c4aa7c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -89,7 +89,7 @@
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
use vbmeta::VbMetaImage;
-use vmconfig::VmConfig;
+use vmconfig::{VmConfig, get_debug_level};
use vsock::VsockStream;
use zip::ZipArchive;
@@ -751,6 +751,9 @@
InputDevice::EvDev(evdev) => InputDeviceOption::EvDev(clone_file(
evdev.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
)?),
+ InputDevice::Keyboard(keyboard) => InputDeviceOption::Keyboard(clone_file(
+ keyboard.pfd.as_ref().ok_or(anyhow!("pfd should have value"))?,
+ )?),
})
}
/// Given the configuration for a disk image, assembles the `DiskFile` to pass to crosvm.
@@ -1356,17 +1359,12 @@
.or_binder_exception(ExceptionCode::SECURITY);
}
- match config {
- VirtualMachineConfig::RawConfig(_) => Ok(()),
- VirtualMachineConfig::AppConfig(config) => {
- if config.debugLevel != DebugLevel::FULL {
- Err(anyhow!("Can't use gdb with non-debuggable VMs"))
- .or_binder_exception(ExceptionCode::SECURITY)
- } else {
- Ok(())
- }
- }
+ if get_debug_level(config) == Some(DebugLevel::NONE) {
+ return Err(anyhow!("Can't use gdb with non-debuggable VMs"))
+ .or_binder_exception(ExceptionCode::SECURITY);
}
+
+ Ok(())
}
fn extract_instance_id(config: &VirtualMachineConfig) -> [u8; 64] {
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 4be48a5..040e552 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -161,6 +161,7 @@
pub enum InputDeviceOption {
EvDev(File),
SingleTouch { file: File, width: u32, height: u32, name: Option<String> },
+ Keyboard(File),
}
type VfioDevice = Strong<dyn IBoundDevice>;
@@ -976,12 +977,24 @@
}
if cfg!(paravirtualized_devices) {
+ // TODO(b/325929096): Need to set up network from the config
+ if rustutils::system_properties::read_bool("ro.crosvm.network.setup.done", false)
+ .unwrap_or(false)
+ {
+ command.arg("--net").arg("tap-name=crosvm_tap");
+ }
+ }
+
+ if cfg!(paravirtualized_devices) {
for input_device_option in config.input_device_options.iter() {
command.arg("--input");
command.arg(match input_device_option {
InputDeviceOption::EvDev(file) => {
format!("evdev[path={}]", add_preserved_fd(&mut preserved_fds, file))
}
+ InputDeviceOption::Keyboard(file) => {
+ format!("keyboard[path={}]", add_preserved_fd(&mut preserved_fds, file))
+ }
InputDeviceOption::SingleTouch { file, width, height, name } => format!(
"single-touch[path={},width={},height={}{}]",
add_preserved_fd(&mut preserved_fds, file),
diff --git a/virtualizationmanager/src/debug_config.rs b/virtualizationmanager/src/debug_config.rs
index 451d1c6..6b74353 100644
--- a/virtualizationmanager/src/debug_config.rs
+++ b/virtualizationmanager/src/debug_config.rs
@@ -26,6 +26,7 @@
use std::fs;
use std::io::ErrorKind;
use std::path::{Path, PathBuf};
+use vmconfig::get_debug_level;
const CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP: &str =
"hypervisor.virtualizationmanager.debug_policy.path";
@@ -147,7 +148,7 @@
}
/// Debug configurations for both debug level and debug policy
-#[derive(Debug)]
+#[derive(Debug, Default)]
pub struct DebugConfig {
pub debug_level: DebugLevel,
debug_policy_log: bool,
@@ -157,12 +158,15 @@
impl DebugConfig {
pub fn new(config: &VirtualMachineConfig) -> Self {
- let debug_level = match config {
- VirtualMachineConfig::AppConfig(config) => config.debugLevel,
- _ => DebugLevel::NONE,
- };
+ let debug_level = get_debug_level(config).unwrap_or(DebugLevel::NONE);
- match system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP).unwrap_or_default() {
+ let dp_sysprop = system_properties::read(CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP);
+ let custom_dp = dp_sysprop.unwrap_or_else(|e| {
+ warn!("Failed to read sysprop {CUSTOM_DEBUG_POLICY_OVERLAY_SYSPROP}: {e}");
+ Default::default()
+ });
+
+ match custom_dp {
Some(path) if !path.is_empty() => {
match Self::from_custom_debug_overlay_policy(debug_level, Path::new(&path)) {
Ok(debug_config) => {
@@ -184,11 +188,6 @@
}
info!("Debug policy is disabled");
- Self::new_with_debug_level(debug_level)
- }
-
- /// Creates a new DebugConfig with debug level. Only use this for test purpose.
- pub fn new_with_debug_level(debug_level: DebugLevel) -> Self {
Self {
debug_level,
debug_policy_log: false,
@@ -197,6 +196,12 @@
}
}
+ #[cfg(test)]
+ /// Creates a new DebugConfig with debug level. Only use this for test purpose.
+ pub(crate) fn new_with_debug_level(debug_level: DebugLevel) -> Self {
+ Self { debug_level, ..Default::default() }
+ }
+
/// Get whether console output should be configred for VM to leave console and adb log.
/// Caller should create pipe and prepare for receiving VM log with it.
pub fn should_prepare_console_output(&self) -> bool {
@@ -214,15 +219,15 @@
}
fn from_custom_debug_overlay_policy(debug_level: DebugLevel, path: &Path) -> Result<Self> {
- match OwnedFdt::from_overlay_onto_new_fdt(path) {
- Ok(fdt) => Ok(Self {
- debug_level,
- debug_policy_log: get_fdt_prop_bool(fdt.as_fdt(), &DP_LOG_PATH)?,
- debug_policy_ramdump: get_fdt_prop_bool(fdt.as_fdt(), &DP_RAMDUMP_PATH)?,
- debug_policy_adb: get_fdt_prop_bool(fdt.as_fdt(), &DP_ADB_PATH)?,
- }),
- Err(err) => Err(err),
- }
+ let owned_fdt = OwnedFdt::from_overlay_onto_new_fdt(path)?;
+ let fdt = owned_fdt.as_fdt();
+
+ Ok(Self {
+ debug_level,
+ debug_policy_log: get_fdt_prop_bool(fdt, &DP_LOG_PATH)?,
+ debug_policy_ramdump: get_fdt_prop_bool(fdt, &DP_RAMDUMP_PATH)?,
+ debug_policy_adb: get_fdt_prop_bool(fdt, &DP_ADB_PATH)?,
+ })
}
fn from_host(debug_level: DebugLevel) -> Result<Self> {
@@ -318,4 +323,18 @@
Ok(())
}
+
+ #[test]
+ fn test_new_with_debug_level() -> Result<()> {
+ assert_eq!(
+ DebugConfig::new_with_debug_level(DebugLevel::NONE).debug_level,
+ DebugLevel::NONE
+ );
+ assert_eq!(
+ DebugConfig::new_with_debug_level(DebugLevel::FULL).debug_level,
+ DebugLevel::FULL
+ );
+
+ Ok(())
+ }
}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
index fe12291..712d6a9 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/InputDevice.aidl
@@ -30,7 +30,11 @@
parcelable EvDev {
ParcelFileDescriptor pfd;
}
-
+ // Keyboard input
+ parcelable Keyboard {
+ ParcelFileDescriptor pfd;
+ }
SingleTouch singleTouch;
EvDev evDev;
+ Keyboard keyboard;
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 07e0276..ca3e857 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -39,7 +39,7 @@
use std::os::unix::io::{AsRawFd, FromRawFd};
use std::path::{Path, PathBuf};
use vmclient::{ErrorCode, VmInstance};
-use vmconfig::{open_parcel_file, VmConfig};
+use vmconfig::{get_debug_level, open_parcel_file, VmConfig};
use zip::ZipArchive;
/// Run a VM from the given APK, idsig, and config.
@@ -315,10 +315,8 @@
.context("Failed to create VM")?;
vm.start().context("Failed to start VM")?;
- let debug_level = match config {
- VirtualMachineConfig::AppConfig(config) => config.debugLevel,
- _ => DebugLevel::NONE,
- };
+ let debug_level = get_debug_level(config).unwrap_or(DebugLevel::NONE);
+
println!(
"Created {} from {} with CID {}, state is {}.",
if debug_level == DebugLevel::FULL { "debuggable VM" } else { "VM" },
diff --git a/vmlauncher_app/AndroidManifest.xml b/vmlauncher_app/AndroidManifest.xml
index 607a895..d800ec7 100644
--- a/vmlauncher_app/AndroidManifest.xml
+++ b/vmlauncher_app/AndroidManifest.xml
@@ -4,9 +4,11 @@
<uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
<uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
+ <uses-permission android:name="android.permission.INTERNET" />
<uses-feature android:name="android.software.virtualization_framework" android:required="true" />
<application
- android:label="VmLauncherApp">
+ android:label="VmLauncherApp"
+ android:networkSecurityConfig="@xml/network_security_config">
<activity android:name=".MainActivity"
android:enabled="false"
android:screenOrientation="landscape"
diff --git a/vmlauncher_app/README.md b/vmlauncher_app/README.md
index 9175e57..0109f37 100644
--- a/vmlauncher_app/README.md
+++ b/vmlauncher_app/README.md
@@ -1,13 +1,16 @@
# VM launcher app
-## Building & Installing
+## Building
-Add `VmLauncherApp` into `PRODUCT_PACKAGES` and then `m`
+This app is now part of the virt APEX.
-You can also explicitly grant or revoke the permission, e.g.
+## Enabling
+
+This app is disabled by default. To re-enable it, execute the following command.
+
```
-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
+adb root
+adb shell pm enable com.android.virtualization.vmlauncher/.MainActivity
```
## Running
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 450f4ed..4c42bb4 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -37,6 +37,7 @@
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
+import android.view.KeyEvent;
import android.view.WindowManager;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
@@ -127,6 +128,7 @@
customImageConfigBuilder.setDisplayConfig(displayConfigBuilder.build());
customImageConfigBuilder.useTouch(true);
+ customImageConfigBuilder.useKeyboard(true);
configBuilder.setCustomImageConfig(customImageConfigBuilder.build());
@@ -137,6 +139,22 @@
}
@Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (mVirtualMachine == null) {
+ return false;
+ }
+ return mVirtualMachine.sendKeyEvent(event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ if (mVirtualMachine == null) {
+ return false;
+ }
+ return mVirtualMachine.sendKeyEvent(event);
+ }
+
+ @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
try {
diff --git a/vmlauncher_app/res/xml/network_security_config.xml b/vmlauncher_app/res/xml/network_security_config.xml
new file mode 100644
index 0000000..f27fa56
--- /dev/null
+++ b/vmlauncher_app/res/xml/network_security_config.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ 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.
+ -->
+
+<network-security-config>
+ <domain-config cleartextTrafficPermitted="true">
+ <domain includeSubdomains="true">localhost</domain>
+ </domain-config>
+</network-security-config>