Merge "Refactor VM config to support different CPU topologies"
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
index afdc944..9281e73 100644
--- a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -57,6 +57,10 @@
     };
 
     RpcSessionHandle session;
+    // We need a thread pool to be able to support linkToDeath, or callbacks
+    // (b/268335700). This if a fairly arbitrary number, although it happens to
+    // match the default max outgoing threads.
+    ARpcSession_setMaxIncomingThreads(session.get(), 10);
     auto client = ARpcSession_setupPreconnectedClient(session.get(), requestFunc, &args);
     return AIBinder_toJavaBinder(env, client);
 }
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 383f371..bf2d755 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -19,8 +19,7 @@
         "libbinder_rs",
         "libbyteorder",
         "libcap_rust",
-        "libdiced",
-        "libdiced_open_dice_cbor",
+        "libdiced_open_dice",
         "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index a7288b6..cf5e73e 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -16,8 +16,8 @@
 
 use anyhow::{bail, Context, Error, Result};
 use byteorder::{NativeEndian, ReadBytesExt};
-use diced_open_dice_cbor::{
-    Config, ContextImpl, DiceMode, Hash, Hidden, InputValues, OpenDiceCborContext, CDI_SIZE,
+use diced_open_dice::{
+    retry_bcc_main_flow, Config, DiceMode, Hash, Hidden, InputValues, OwnedDiceArtifacts, CDI_SIZE,
 };
 use keystore2_crypto::ZVec;
 use libc::{c_void, mmap, munmap, MAP_FAILED, MAP_PRIVATE, PROT_READ};
@@ -31,12 +31,23 @@
 
 /// Artifacts that are kept in the process address space after the artifacts from the driver have
 /// been consumed.
+/// TODO(b/267575445): Replace with `OwnedDiceArtifacts` from the library `diced_open_dice`.
 pub struct DiceContext {
     pub cdi_attest: [u8; CDI_SIZE],
     pub cdi_seal: [u8; CDI_SIZE],
     pub bcc: Vec<u8>,
 }
 
+impl From<OwnedDiceArtifacts> for DiceContext {
+    fn from(dice_artifacts: OwnedDiceArtifacts) -> Self {
+        Self {
+            cdi_attest: dice_artifacts.cdi_values.cdi_attest,
+            cdi_seal: dice_artifacts.cdi_values.cdi_seal,
+            bcc: dice_artifacts.bcc[..].to_vec(),
+        }
+    }
+}
+
 impl DiceContext {
     pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
         // Deterministically derive a key to use for sealing data based on salt. Use different salt
@@ -68,13 +79,9 @@
             bail!("Strict boot requires DICE value from driver but none were found");
         } else {
             log::warn!("Using sample DICE values");
-            let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+            let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()
                 .expect("Failed to create sample dice artifacts.");
-            return Ok(Self::Fake(DiceContext {
-                cdi_attest: cdi_attest[..].try_into().unwrap(),
-                cdi_seal: cdi_seal[..].try_into().unwrap(),
-                bcc,
-            }));
+            return Ok(Self::Fake(dice_artifacts.into()));
         };
 
         let mut file = fs::File::open(driver_path)
@@ -153,8 +160,7 @@
             Self::Real { cdi_attest, cdi_seal, bcc, .. } => (*cdi_attest, *cdi_seal, *bcc),
             Self::Fake(fake) => (&fake.cdi_attest, &fake.cdi_seal, fake.bcc.as_slice()),
         };
-        let (cdi_attest, cdi_seal, bcc) = OpenDiceCborContext::new()
-            .bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
+        let dice_artifacts = retry_bcc_main_flow(cdi_attest, cdi_seal, bcc, &input_values)
             .context("DICE derive from driver")?;
         if let Self::Real { driver_path, .. } = &self {
             // Writing to the device wipes the artifacts. The string is ignored by the driver but
@@ -162,11 +168,7 @@
             fs::write(driver_path, "wipe")
                 .map_err(|err| Error::new(err).context("Wiping driver"))?;
         }
-        Ok(DiceContext {
-            cdi_attest: cdi_attest[..].try_into().unwrap(),
-            cdi_seal: cdi_seal[..].try_into().unwrap(),
-            bcc,
-        })
+        Ok(dice_artifacts.into())
     }
 }
 
diff --git a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
index 2591fce..4dbf4ba 100644
--- a/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
+++ b/tests/benchmark_hostside/java/android/avf/test/AVFHostTestCase.java
@@ -94,6 +94,14 @@
 
     @After
     public void tearDown() throws Exception {
+        try {
+            testIfDeviceIsCapable(getDevice());
+        } catch (Exception e) {
+            // Suppress execption here.
+            // If we throw exceptions in both setUp() and tearDown(),
+            // then test is reported as fail with org.junit.TestCouldNotBeSkippedException.
+            return;
+        }
         // Set PKVM enable and reboot to prevent previous staged session.
         if (!isCuttlefish()) {
             setPKVMStatusWithRebootToBootloader(true);
diff --git a/tests/hostside/helper/Android.bp b/tests/hostside/helper/Android.bp
index 6196ec5..e8b6f36 100644
--- a/tests/hostside/helper/Android.bp
+++ b/tests/hostside/helper/Android.bp
@@ -6,6 +6,7 @@
     name: "MicrodroidHostTestHelper",
     srcs: ["java/**/*.java"],
     libs: [
+        "androidx.annotation_annotation",
         "compatibility-tradefed",
         "tradefed",
         "truth-prebuilt",
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
new file mode 100644
index 0000000..95eaa58
--- /dev/null
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/Pvmfw.java
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2023 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.microdroid.test.host;
+
+import static java.nio.ByteOrder.LITTLE_ENDIAN;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.Objects;
+import java.nio.ByteBuffer;
+
+/** pvmfw.bin with custom config payloads on host. */
+public final class Pvmfw {
+    private static final int SIZE_8B = 8; // 8 bytes
+    private static final int SIZE_4K = 4 << 10; // 4 KiB, PAGE_SIZE
+    private static final int BUFFER_SIZE = 1024;
+    private static final int HEADER_SIZE = Integer.BYTES * 8; // Header has 8 integers.
+    private static final int HEADER_MAGIC = 0x666d7670;
+    private static final int HEADER_VERSION = getVersion(1, 0);
+    private static final int HEADER_FLAGS = 0;
+
+    @NonNull private final File mPvmfwBinFile;
+    @NonNull private final File mBccFile;
+    @Nullable private final File mDebugPolicyFile;
+
+    private Pvmfw(
+            @NonNull File pvmfwBinFile, @NonNull File bccFile, @Nullable File debugPolicyFile) {
+        mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+        mBccFile = Objects.requireNonNull(bccFile);
+        mDebugPolicyFile = debugPolicyFile;
+    }
+
+    /**
+     * Serializes pvmfw.bin and its config, as written in the <a
+     * href="https://android.googlesource.com/platform/packages/modules/Virtualization/+/master/pvmfw/README.md">README.md</a>
+     */
+    public void serialize(@NonNull File outFile) throws IOException {
+        Objects.requireNonNull(outFile);
+
+        int bccOffset = HEADER_SIZE;
+        int bccSize = (int) mBccFile.length();
+
+        int debugPolicyOffset = alignTo(bccOffset + bccSize, SIZE_8B);
+        int debugPolicySize = mDebugPolicyFile == null ? 0 : (int) mDebugPolicyFile.length();
+
+        int totalSize = debugPolicyOffset + debugPolicySize;
+
+        ByteBuffer header = ByteBuffer.allocate(HEADER_SIZE).order(LITTLE_ENDIAN);
+        header.putInt(HEADER_MAGIC);
+        header.putInt(HEADER_VERSION);
+        header.putInt(totalSize);
+        header.putInt(HEADER_FLAGS);
+        header.putInt(bccOffset);
+        header.putInt(bccSize);
+        header.putInt(debugPolicyOffset);
+        header.putInt(debugPolicySize);
+
+        try (FileOutputStream pvmfw = new FileOutputStream(outFile)) {
+            appendFile(pvmfw, mPvmfwBinFile);
+            padTo(pvmfw, SIZE_4K);
+            pvmfw.write(header.array());
+            padTo(pvmfw, HEADER_SIZE);
+            appendFile(pvmfw, mBccFile);
+            if (mDebugPolicyFile != null) {
+                padTo(pvmfw, SIZE_8B);
+                appendFile(pvmfw, mDebugPolicyFile);
+            }
+            padTo(pvmfw, SIZE_4K);
+        }
+    }
+
+    private void appendFile(@NonNull FileOutputStream out, @NonNull File inFile)
+            throws IOException {
+        byte buffer[] = new byte[BUFFER_SIZE];
+        try (FileInputStream in = new FileInputStream(inFile)) {
+            int size;
+            while (true) {
+                size = in.read(buffer);
+                if (size < 0) {
+                    return;
+                }
+                out.write(buffer, /* offset= */ 0, size);
+            }
+        }
+    }
+
+    private void padTo(@NonNull FileOutputStream out, int size) throws IOException {
+        int streamSize = (int) out.getChannel().size();
+        for (int i = streamSize; i < alignTo(streamSize, size); i++) {
+            out.write(0); // write byte.
+        }
+    }
+
+    private static int alignTo(int x, int size) {
+        return (x + size - 1) & ~(size - 1);
+    }
+
+    private static int getVersion(int major, int minor) {
+        return ((major & 0xFFFF) << 16) | (minor & 0xFFFF);
+    }
+
+    /** Builder for {@link Pvmfw}. */
+    public static final class Builder {
+        @NonNull private final File mPvmfwBinFile;
+        @NonNull private final File mBccFile;
+        @Nullable private File mDebugPolicyFile;
+
+        public Builder(@NonNull File pvmfwBinFile, @NonNull File bccFile) {
+            mPvmfwBinFile = Objects.requireNonNull(pvmfwBinFile);
+            mBccFile = Objects.requireNonNull(bccFile);
+        }
+
+        @NonNull
+        public Builder setDebugPolicyOverlay(@Nullable File debugPolicyFile) {
+            mDebugPolicyFile = debugPolicyFile;
+            return this;
+        }
+
+        @NonNull
+        public Pvmfw build() {
+            return new Pvmfw(mPvmfwBinFile, mBccFile, mDebugPolicyFile);
+        }
+    }
+}
diff --git a/tests/hostside/tools/Android.bp b/tests/hostside/tools/Android.bp
new file mode 100644
index 0000000..f3cc275
--- /dev/null
+++ b/tests/hostside/tools/Android.bp
@@ -0,0 +1,10 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+java_binary_host {
+    name: "pvmfw-tool",
+    manifest: "pvmfw-tool-manifest.txt",
+    srcs: ["PvmfwTool.java"],
+    static_libs: ["MicrodroidHostTestHelper"],
+}
diff --git a/tests/hostside/tools/PvmfwTool.java b/tests/hostside/tools/PvmfwTool.java
new file mode 100644
index 0000000..18dd6d7
--- /dev/null
+++ b/tests/hostside/tools/PvmfwTool.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 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.microdroid;
+
+import com.android.microdroid.test.host.Pvmfw;
+
+import java.io.File;
+import java.io.IOException;
+
+/** CLI for {@link com.android.microdroid.test.host.Pvmfw}. */
+public class PvmfwTool {
+    public static void printUsage() {
+        System.out.println("pvmfw-tool: Appends pvmfw.bin and config payloads.");
+        System.out.println("Requires BCC and debug policy dtbo files");
+        System.out.println("");
+        System.out.println("Usage: pvmfw-tool <pvmfw_with_config> <pvmfw_bin> <bcc.dat> <dp.dtbo>");
+    }
+
+    public static void main(String[] args) {
+        if (args.length != 4) {
+            printUsage();
+            System.exit(1);
+        }
+
+        File out = new File(args[0]);
+        File pvmfw_bin = new File(args[1]);
+        File bcc_dat = new File(args[2]);
+        File dtbo = new File(args[3]);
+
+        try {
+            Pvmfw pvmfw = new Pvmfw.Builder(pvmfw_bin, bcc_dat).setDebugPolicyOverlay(dtbo).build();
+            pvmfw.serialize(out);
+        } catch (IOException e) {
+            e.printStackTrace();
+            printUsage();
+            System.exit(1);
+        }
+    }
+}
diff --git a/tests/hostside/tools/pvmfw-tool-manifest.txt b/tests/hostside/tools/pvmfw-tool-manifest.txt
new file mode 100644
index 0000000..dc71fd2
--- /dev/null
+++ b/tests/hostside/tools/pvmfw-tool-manifest.txt
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+Main-Class: com.android.microdroid.PvmfwTool
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index d8f0e11..3c487ee 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -2,12 +2,22 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-android_test {
-    name: "MicrodroidTestApp",
+java_defaults {
+    name: "MicrodroidTestAppsDefaults",
     test_suites: [
         "cts",
         "general-tests",
     ],
+    sdk_version: "test_current",
+    jni_uses_platform_apis: true,
+    use_embedded_native_libs: true,
+    // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
+    compile_multilib: "both",
+}
+
+android_test {
+    name: "MicrodroidTestApp",
+    defaults: ["MicrodroidTestAppsDefaults"],
     srcs: ["src/java/**/*.java"],
     static_libs: [
         "MicrodroidDeviceTestHelper",
@@ -19,7 +29,6 @@
         "truth-prebuilt",
         "compatibility-common-util-devicesidelib",
     ],
-    sdk_version: "test_current",
     jni_libs: [
         "MicrodroidTestNativeLib",
         "MicrodroidIdleNativeLib",
@@ -28,15 +37,16 @@
         "MicrodroidPrivateLinkingNativeLib",
         "MicrodroidCrashNativeLib",
     ],
-    jni_uses_platform_apis: true,
-    use_embedded_native_libs: true,
-    // We only support 64-bit ABI, but CTS demands all APKs to be multi-ABI.
-    compile_multilib: "both",
     min_sdk_version: "33",
+    // Defined in ../vmshareapp/Android.bp
+    data: [":MicrodroidVmShareApp"],
 }
 
-cc_library_shared {
-    name: "MicrodroidTestNativeLib",
+// Defaults shared between MicrodroidTestNativeLib and MicrodroidPayloadInOtherAppNativeLib shared
+// libs. They are expected to share everything apart from the name, so that one app
+// (MicrodroidTestApp) can start a payload defined in the another app (MicrodroidVmShareApp).
+cc_defaults {
+    name: "MicrodroidTestNativeLibDefaults",
     srcs: ["src/native/testbinary.cpp"],
     stl: "libc++_static",
     header_libs: ["vm_payload_restricted_headers"],
@@ -56,6 +66,16 @@
 }
 
 cc_library_shared {
+    name: "MicrodroidPayloadInOtherAppNativeLib",
+    defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
+    name: "MicrodroidTestNativeLib",
+    defaults: ["MicrodroidTestNativeLibDefaults"],
+}
+
+cc_library_shared {
     name: "MicrodroidTestNativeLibSub",
     srcs: ["src/native/testlib.cpp"],
     stl: "libc++_static",
diff --git a/tests/testapk/AndroidManifest.xml b/tests/testapk/AndroidManifest.xml
index fefd20a..2ea3f6c 100644
--- a/tests/testapk/AndroidManifest.xml
+++ b/tests/testapk/AndroidManifest.xml
@@ -19,6 +19,9 @@
     <uses-permission android:name="android.permission.USE_CUSTOM_VIRTUAL_MACHINE" />
     <uses-sdk android:minSdkVersion="33" android:targetSdkVersion="33" />
     <uses-feature android:name="android.software.virtualization_framework" android:required="false" />
+    <queries>
+        <package android:name="com.android.microdroid.vmshare_app" />
+    </queries>
     <application>
     </application>
     <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/testapk/AndroidTest.xml b/tests/testapk/AndroidTest.xml
index 787ebd4..929dd31 100644
--- a/tests/testapk/AndroidTest.xml
+++ b/tests/testapk/AndroidTest.xml
@@ -21,6 +21,7 @@
     <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
     <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
         <option name="test-file-name" value="MicrodroidTestApp.apk" />
+        <option name="test-file-name" value="MicrodroidVmShareApp.apk" />
     </target_preparer>
     <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
         <option name="package" value="com.android.microdroid.test" />
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index c31bf52..4a76ead 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -127,6 +127,8 @@
     private static final long MIN_MEM_X86_64 = 196 * ONE_MEBI;
     private static final String EXAMPLE_STRING = "Literally any string!! :)";
 
+    private static final String VM_SHARE_APP_PACKAGE_NAME = "com.android.microdroid.vmshare_app";
+
     @Test
     @CddTest(requirements = {"9.17/C-1-1", "9.17/C-2-1"})
     public void createAndConnectToVm() throws Exception {
@@ -1517,6 +1519,33 @@
         assertThat(checkVmOutputIsRedirectedToLogcat(false)).isFalse();
     }
 
+    @Test
+    public void testStartVmWithPayloadOfAnotherApp() throws Exception {
+        assumeSupportedKernel();
+
+        Context ctx = getContext();
+        Context otherAppCtx = ctx.createPackageContext(VM_SHARE_APP_PACKAGE_NAME, 0);
+
+        VirtualMachineConfig config =
+                new VirtualMachineConfig.Builder(otherAppCtx)
+                        .setDebugLevel(DEBUG_LEVEL_FULL)
+                        .setProtectedVm(isProtectedVm())
+                        .setPayloadBinaryName("MicrodroidPayloadInOtherAppNativeLib.so")
+                        .build();
+
+        try (VirtualMachine vm = forceCreateNewVirtualMachine("vm_from_another_app", config)) {
+            TestResults results =
+                    runVmTestService(
+                            vm,
+                            (ts, tr) -> {
+                                tr.mAddInteger = ts.addInteger(101, 303);
+                            });
+            assertThat(results.mAddInteger).isEqualTo(404);
+        }
+
+        getVirtualMachineManager().delete("vm_from_another_app");
+    }
+
     private void assertFileContentsAreEqualInTwoVms(String fileName, String vmName1, String vmName2)
             throws IOException {
         File file1 = getVmFile(vmName1, fileName);
@@ -1590,6 +1619,11 @@
                             mTestService =
                                     ITestService.Stub.asInterface(
                                             vm.connectToVsockServer(ITestService.SERVICE_PORT));
+                            // Make sure linkToDeath works, and include it in the log in case it's
+                            // helpful.
+                            mTestService
+                                    .asBinder()
+                                    .linkToDeath(() -> Log.i(TAG, "ITestService binder died"), 0);
                         } catch (Exception e) {
                             testResults.mException = e;
                         }
diff --git a/tests/vmshareapp/Android.bp b/tests/vmshareapp/Android.bp
new file mode 100644
index 0000000..2b117a1
--- /dev/null
+++ b/tests/vmshareapp/Android.bp
@@ -0,0 +1,15 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// Helper app to verify that we can create a VM using others app payload, and share VMs between apps
+android_test_helper_app {
+    name: "MicrodroidVmShareApp",
+    // Defaults are defined in ../testapk/Android.bp
+    defaults: ["MicrodroidTestAppsDefaults"],
+    jni_libs: [
+        // Defined in ../testapk/Android.bp
+        "MicrodroidPayloadInOtherAppNativeLib",
+    ],
+    min_sdk_version: "UpsideDownCake",
+}
diff --git a/tests/vmshareapp/AndroidManifest.xml b/tests/vmshareapp/AndroidManifest.xml
new file mode 100644
index 0000000..eed3364
--- /dev/null
+++ b/tests/vmshareapp/AndroidManifest.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 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.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.microdroid.vmshare_app">
+    <uses-permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE" />
+
+    <uses-feature android:name="android.software.virtualization_framework"
+                  android:required="false" />
+
+    <application />
+</manifest>