Merge "Support multi-touch screen" into main
diff --git a/dice_for_avf_guest.cddl b/dice_for_avf_guest.cddl
new file mode 100644
index 0000000..372ee15
--- /dev/null
+++ b/dice_for_avf_guest.cddl
@@ -0,0 +1,69 @@
+; DICE Specification for guest VM
+
+; See the Open DICE specification
+; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md,
+; and the Android Profile for DICE
+; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md.
+
+; This CDDL describes the Configuration Descriptor used for components running in AVF Guest environment
+; (VM core components and payload). It extends the `ConfigurationDescriptor` specified at
+; https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl
+
+; Additionally, we reserve range -71000...-71999 for AVF system specific usage. These are or can be
+; used by frameworks & parsers of DICE chains such as local and remote attestation frameworks.
+; Vendor must not use these key/values for other purposes, that may compromise the integrity of the system.
+; Note that each of the key-value pairs may not be useful for all the boot components and therefore
+; are optional. For e.g., SubcomponentDescriptor is only used in Microdroid payload, it may
+; not have immediate use for pVM firmware.
+
+; Each components of VM must specify the value corresponding to `Component name`(-70002).
+; For provided reference implementations:
+;
+; 1. "vm_entry" - Guest 'OS'. This is the payload booted by pVM firmware. For e.g, Microdroid, Rialto.
+; 2. "Microdroid vendor" - The vendor image, specific to Microdroid.
+; 3. "Microdroid Payload" - Payload run by Microdroid Manager.
+
+
+ConfigDescriptor = {
+    -70002 : tstr,                        ; Component name
+    (? -71000: tstr //                    ; Path to the payload config file
+    ? -71001: PayloadConfig),
+    ? -71002: [+ SubcomponentDescriptor], ; The order of these should be kept constant on each boot
+                                          ; of the VM instance
+    ? -71003: bstr .size 64               ; Instance hash: Unique identifier of the VM instance
+}
+
+PayloadConfig = {
+    1: tstr                             ; Path to the binary file where payload execution starts
+}
+
+; Describes a unit of code (e.g. an APK or an APEX) present inside the VM.
+;
+; For an APK, the fields are as follows:
+; - Component name: The string "apk:" followed by the package name.
+; - Security version: The long version code from the APK manifest
+;   (https://developer.android.com/reference/android/content/pm/PackageInfo#getLongVersionCode()).
+; - Code hash: This is the root hash of a Merkle tree computed over all bytes of the APK, as used
+;   in the APK Signature Scheme v4 (https://source.android.com/docs/security/features/apksigning/v4)
+;   with empty salt and using SHA-256 as the hash algorithm.
+; - Authority hash: The SHA-512 hash of the DER representation of the X.509 certificate for the
+;   public key used to sign the APK.
+;
+; For an APEX, they are as follows:
+; - Component name: The string "apex:" followed by the APEX name as specified in the APEX Manifest
+;   (see https://source.android.com/docs/core/ota/apex).
+; - Security version: The version number from the APEX Manifest.
+; - Code hash: The root hash of the apex_payload.img file within the APEX, taken from the first
+;   hashtree descriptor in the VBMeta image
+;   (see https://android.googlesource.com/platform/external/avb/+/master/README.md).
+; - Authority hash: The SHA-512 hash of the public key used to sign the file system image in the
+;   APEX (as stored in the apex_pubkey file). The format is as described for AvbRSAPublicKeyHeader
+;   in https://cs.android.com/android/platform/superproject/main/+/main:external/avb/libavb/avb_crypto.h.
+SubcomponentDescriptor = {
+  1: tstr,                              ; Component name
+  2: uint,                              ; Security version
+  3: bstr,                              ; Code hash
+  4: bstr,                              ; Authority hash
+}
+
+TODO: Describe how these descriptors are used by AVF components in Android W.
\ No newline at end of file
diff --git a/docs/updatable_vm.md b/docs/updatable_vm.md
index ff1a0d6..24c1b4b 100644
--- a/docs/updatable_vm.md
+++ b/docs/updatable_vm.md
@@ -59,8 +59,9 @@
   1. GreaterOrEqual on SECURITY_VERSION: The secrets will be accessible if version of any
      image is greater or equal to the set version.
 - For each Subcomponent on the last DiceChainEntry (which corresponds to VM payload, See
-  [vm_config.cddl][vm_config_cddl]): - GreaterOrEqual on SECURITY_VERSION - ExactMatch on
-  AUTHORITY_HASH.
+  [dice_for_avf_guest.cddl][dice_for_avf_guest_cddl]):
+    - GreaterOrEqual on SECURITY_VERSION
+    - ExactMatch on AUTHORITY_HASH.
 
 The sealing policy is updated each time the secret is retrieved. This ensures the secrets are only
 released if the security version of the images are non-decreasing.
@@ -96,4 +97,4 @@
 [open_dice_spec_cdi]: https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md#cdi-values
 [secretkeeperhal]: https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/secretkeeper/aidl/android/hardware/security/secretkeeper/ISecretkeeper.aidl
 [sk_project]: https://android.googlesource.com/platform/system/secretkeeper/
-[vm_config_cddl]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/microdroid_manager/src/vm_config.cddl
+[dice_for_avf_guest_cddl]: https://cs.android.com/android/platform/superproject/main/+/main:packages/modules/Virtualization/dice_for_avf_guest.cddl
diff --git a/ferrochrome_app/custom_vm_setup.sh b/ferrochrome_app/custom_vm_setup.sh
index f007f6a..a5480ff 100644
--- a/ferrochrome_app/custom_vm_setup.sh
+++ b/ferrochrome_app/custom_vm_setup.sh
@@ -1,14 +1,31 @@
 #!/system/bin/sh
 
-function copy_files() {
-  cp -u /sdcard/vm_config.json /data/local/tmp
-  cp -u /data/media/10/vm_config.json /data/local/tmp
-  cp -u /sdcard/chromiumos_test_image.bin /data/local/tmp
-  cp -u /data/media/10/chromiumos_test_image.bin /data/local/tmp
-  chmod 666 /data/local/tmp/vm_config.json
-  chmod 666 /data/local/tmp/chromiumos_test_image.bin
+function round_up() {
+  num=$1
+  div=$2
+  echo $((( (( ${num} / ${div} ) + 1) * ${div} )))
 }
+
+function install() {
+  user=$(cmd user get-main-user)
+  src_dir=/data/media/${user}/ferrochrome/
+  dst_dir=/data/local/tmp/
+
+  cat $(find ${src_dir} -name "images.tar.gz*" | sort) | tar xz -C ${dst_dir}
+  cp -u ${src_dir}vm_config.json ${dst_dir}
+  chmod 666 ${dst_dir}*
+
+  # increase the size of state.img to the multiple of 4096
+  num_blocks=$(du -b -K ${dst_dir}state.img | cut -f 1)
+  required_num_blocks=$(round_up ${num_blocks} 4)
+  additional_blocks=$((( ${required_num_blocks} - ${num_blocks} )))
+  dd if=/dev/zero bs=512 count=${additional_blocks} >> ${dst_dir}state.img
+
+  rm ${src_dir}images.tar.gz*
+  rm ${src_dir}vm_config.json
+}
+
 setprop debug.custom_vm_setup.done false
-copy_files
+install
 setprop debug.custom_vm_setup.start false
 setprop debug.custom_vm_setup.done true
diff --git a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java b/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
index 7c18537..58005aa 100644
--- a/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
+++ b/ferrochrome_app/java/com/android/virtualization/ferrochrome/FerrochromeActivity.java
@@ -28,15 +28,11 @@
 import android.view.WindowManager;
 import android.widget.TextView;
 
-import org.apache.commons.compress.archivers.tar.TarArchiveEntry;
-import org.apache.commons.compress.archivers.tar.TarArchiveInputStream;
-import org.apache.commons.compress.compressors.xz.XZCompressorInputStream;
-
-import java.io.File;
+import java.io.BufferedReader;
+import java.io.FileInputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.net.URL;
-import java.nio.charset.StandardCharsets;
+import java.io.InputStreamReader;
 import java.nio.file.Files;
 import java.nio.file.Path;
 import java.nio.file.StandardCopyOption;
@@ -45,19 +41,17 @@
 import java.util.concurrent.Executors;
 
 public class FerrochromeActivity extends Activity {
-    ExecutorService executorService = Executors.newSingleThreadExecutor();
-    private static final String TAG = "FerrochromeActivity";
+    private static final String TAG = FerrochromeActivity.class.getName();
     private static final String ACTION_VM_LAUNCHER = "android.virtualization.VM_LAUNCHER";
-    private static final String FERROCHROME_VERSION = "R128-15926.0.0";
-    private static final String EXTERNAL_STORAGE_DIR =
-            Environment.getExternalStorageDirectory().getPath() + File.separator;
-    private static final Path IMAGE_PATH =
-            Path.of(EXTERNAL_STORAGE_DIR + "chromiumos_test_image.bin");
-    private static final Path IMAGE_VERSION_INFO =
-            Path.of(EXTERNAL_STORAGE_DIR + "ferrochrome_image_version");
-    private static final Path VM_CONFIG_PATH = Path.of(EXTERNAL_STORAGE_DIR + "vm_config.json");
+
+    private static final Path DEST_DIR =
+            Path.of(Environment.getExternalStorageDirectory().getPath(), "ferrochrome");
+    private static final Path VERSION_FILE = Path.of(DEST_DIR.toString(), "version");
+
     private static final int REQUEST_CODE_VMLAUNCHER = 1;
 
+    ExecutorService executorService = Executors.newSingleThreadExecutor();
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -80,35 +74,7 @@
 
         executorService.execute(
                 () -> {
-                    if (Files.notExists(IMAGE_PATH)
-                            || !FERROCHROME_VERSION.equals(getVersionInfo())) {
-                        updateStatus("Starting first-time setup.");
-                        updateStatus(
-                                "Downloading Ferrochrome image. This can take about 5 to 10"
-                                        + " minutes, depending on your network speed.");
-                        if (download(FERROCHROME_VERSION)) {
-                            updateStatus("Done.");
-                        } else {
-                            updateStatus(
-                                    "Download failed. Check the internet connection and retry.");
-                            return;
-                        }
-                    } else {
-                        updateStatus("Ferrochrome is already downloaded.");
-                    }
-                    updateStatus("Updating VM config.");
-                    copyVmConfigJson();
-                    updateStatus("Updating VM images. This may take a few minutes.");
-                    SystemProperties.set("debug.custom_vm_setup.start", "true");
-                    while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
-                        // Wait for custom_vm_setup
-                        try {
-                            Thread.sleep(1000);
-                        } catch (Exception e) {
-                            Log.d(TAG, e.toString());
-                        }
-                    }
-                    updateStatus("Done.");
+                    updateImageIfNeeded();
                     updateStatus("Starting Ferrochrome...");
                     runOnUiThread(() -> startActivityForResult(intent, REQUEST_CODE_VMLAUNCHER));
                 });
@@ -121,63 +87,86 @@
         }
     }
 
+    private void updateImageIfNeeded() {
+        if (!isUpdateNeeded()) {
+            Log.d(TAG, "No update needed.");
+            return;
+        }
+
+        updateStatus("Copying images...");
+        try {
+            if (Files.notExists(DEST_DIR)) {
+                Files.createDirectory(DEST_DIR);
+            }
+            for (String file : getAssets().list("ferrochrome")) {
+                updateStatus(file);
+                Path dst = Path.of(DEST_DIR.toString(), file);
+                updateFile(getAssets().open("ferrochrome/" + file), dst);
+            }
+        } catch (IOException e) {
+            Log.e(TAG, "Error while updating image: " + e);
+            updateStatus("Failed.");
+            return;
+        }
+        updateStatus("Done.");
+
+        updateStatus("Extracting images...");
+        SystemProperties.set("debug.custom_vm_setup.start", "true");
+        while (!SystemProperties.getBoolean("debug.custom_vm_setup.done", false)) {
+            try {
+                Thread.sleep(1000);
+            } catch (Exception e) {
+                Log.e(TAG, "Error while extracting image: " + e);
+                updateStatus("Failed.");
+                return;
+            }
+        }
+        updateStatus("Done.");
+    }
+
+    private boolean isUpdateNeeded() {
+        Path[] pathsToCheck = {DEST_DIR, VERSION_FILE};
+        for (Path p : pathsToCheck) {
+            if (Files.notExists(p)) {
+                Log.d(TAG, p.toString() + " does not exist.");
+                return true;
+            }
+        }
+
+        try {
+            String installedVer = readLine(new FileInputStream(VERSION_FILE.toFile()));
+            String updatedVer = readLine(getAssets().open("ferrochrome/version"));
+            if (installedVer.equals(updatedVer)) {
+                return false;
+            }
+            Log.d(TAG, "Version mismatch. Installed: " + installedVer + "  Updated: " + updatedVer);
+        } catch (IOException e) {
+            Log.e(TAG, "Error while checking version: " + e);
+        }
+        return true;
+    }
+
+    private static String readLine(InputStream input) throws IOException {
+        try (BufferedReader reader = new BufferedReader(new InputStreamReader(input))) {
+            return reader.readLine();
+        } catch (IOException e) {
+            throw e;
+        }
+    }
+
+    private static void updateFile(InputStream input, Path path) throws IOException {
+        try {
+            Files.copy(input, path, StandardCopyOption.REPLACE_EXISTING);
+        } finally {
+            input.close();
+        }
+    }
+
     private void updateStatus(String line) {
-        Log.d(TAG, line);
         runOnUiThread(
                 () -> {
                     TextView statusView = findViewById(R.id.status_txt_view);
                     statusView.append(line + "\n");
                 });
     }
-
-    private void copyVmConfigJson() {
-        try (InputStream is = getResources().openRawResource(R.raw.vm_config)) {
-            Files.copy(is, VM_CONFIG_PATH, StandardCopyOption.REPLACE_EXISTING);
-        } catch (IOException e) {
-            updateStatus(e.toString());
-        }
-    }
-
-    private String getVersionInfo() {
-        try {
-            return new String(Files.readAllBytes(IMAGE_VERSION_INFO), StandardCharsets.UTF_8);
-        } catch (IOException e) {
-            return null;
-        }
-    }
-
-    private boolean updateVersionInfo(String version) {
-        try {
-            Files.write(IMAGE_VERSION_INFO, version.getBytes(StandardCharsets.UTF_8));
-        } catch (IOException e) {
-            Log.d(TAG, e.toString());
-        }
-        return true;
-    }
-
-    private boolean download(String version) {
-        String urlString =
-                "https://storage.googleapis.com/chromiumos-image-archive/ferrochrome-public/"
-                        + version
-                        + "/chromiumos_test_image.tar.xz";
-        try (InputStream is = (new URL(urlString)).openStream();
-                XZCompressorInputStream xz = new XZCompressorInputStream(is);
-                TarArchiveInputStream tar = new TarArchiveInputStream(xz)) {
-            TarArchiveEntry entry;
-            while ((entry = tar.getNextTarEntry()) != null) {
-                if (!entry.getName().contains("chromiumos_test_image.bin")) {
-                    continue;
-                }
-                updateStatus("copy " + entry.getName() + " start");
-                Files.copy(tar, IMAGE_PATH, StandardCopyOption.REPLACE_EXISTING);
-                updateStatus("copy " + entry.getName() + " done");
-                updateVersionInfo(version);
-                break;
-            }
-        } catch (Exception e) {
-            updateStatus(e.toString());
-            return false;
-        }
-        return true;
-    }
 }
diff --git a/ferrochrome_app/repack.sh b/ferrochrome_app/repack.sh
new file mode 100755
index 0000000..d47b529
--- /dev/null
+++ b/ferrochrome_app/repack.sh
@@ -0,0 +1,53 @@
+#!/bin/bash
+# Repacks chromiumos_*.bin into the assets of FerrochromeApp
+
+usage() {
+	echo "Usage: $0 CHROME_OS_DISK_IMAGE"
+	exit 1
+}
+
+if [ "$#" -ne 1 ]; then
+	usage
+fi
+
+disk=$1
+
+loop=$(sudo losetup --show -f -P ${disk})
+kern=$(sudo fdisk -x ${loop} | grep KERN-A | awk "{print\$1}")
+root=$(sudo fdisk -x ${loop} | grep ROOT-A | awk "{print\$1}")
+efi=$(sudo fdisk -x ${loop} | grep EFI-SYSTEM | awk "{print\$1}")
+state=$(sudo fdisk -x ${loop} | grep STATE | awk "{print\$1}")
+root_guid=$(sudo fdisk -x ${loop} | grep ROOT-A | awk "{print\$6}")
+
+tempdir=$(mktemp -d)
+pushd ${tempdir} > /dev/null
+echo Extracting partition images...
+sudo cp --sparse=always ${kern} kernel.img
+sudo cp --sparse=always ${root} root.img
+sudo cp --sparse=always ${efi} efi.img
+sudo cp --sparse=always ${state} state.img
+sudo chmod 777 *.img
+
+echo Archiving. This can take long...
+tar czvS -f images.tar.gz *.img
+
+echo Calculating hash...
+hash=$(sha1sum images.tar.gz | cut -d' ' -f 1)
+
+echo Splitting...
+split -b 100M -d images.tar.gz images.tar.gz.part
+
+popd > /dev/null
+asset_dir=$(dirname $0)/assets/ferrochrome
+echo Updating ${asset_dir}...
+vm_config_template=$(dirname $0)/vm_config.json.template
+mkdir -p ${asset_dir}
+rm ${asset_dir}/images.tar.gz.part*
+mv ${tempdir}/images.tar.gz.part* ${asset_dir}
+sed -E s/GUID/${root_guid}/ ${vm_config_template} > ${asset_dir}/vm_config.json
+echo ${hash} > ${asset_dir}/version
+
+echo Cleanup...
+sudo losetup -d ${loop}
+rm -rf ${tempdir}
+echo Done.
diff --git a/ferrochrome_app/res/raw/vm_config.json b/ferrochrome_app/res/raw/vm_config.json
deleted file mode 100644
index d79400c..0000000
--- a/ferrochrome_app/res/raw/vm_config.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-    "name": "cros",
-    "disks": [
-        {
-            "image": "/data/local/tmp/chromiumos_test_image.bin",
-            "partitions": [],
-            "writable": true
-        }
-    ],
-    "params": "root=/dev/vda3 rootwait noinitrd ro enforcing=0 cros_debug cros_secure",
-    "protected": false,
-    "cpu_topology": "match_host",
-    "platform_version": "~1.0",
-    "memory_mib": 8096,
-    "gpu": {
-        "backend": "virglrenderer",
-        "context_types": ["virgl2"]
-    },
-    "console_input_device": "ttyS0"
-}
\ No newline at end of file
diff --git a/ferrochrome_app/vm_config.json.template b/ferrochrome_app/vm_config.json.template
new file mode 100644
index 0000000..cb968ec
--- /dev/null
+++ b/ferrochrome_app/vm_config.json.template
@@ -0,0 +1,37 @@
+{
+    "name": "cros",
+    "disks": [
+        {
+            "writable": true,
+            "partitions": [
+                {
+                    "label": "STATE",
+                    "path": "/data/local/tmp/state.img",
+                    "writable": true
+                },
+                {
+                    "label": "KERN-A",
+                    "path": "/data/local/tmp/kernel.img"
+                },
+                {
+                    "label": "ROOT-A",
+                    "path": "/data/local/tmp/root.img",
+                    "guid": "GUID"
+                },
+                {
+                    "label": "EFI-SYSTEM",
+                    "path": "/data/local/tmp/efi.img"
+                }
+            ]
+        }
+    ],
+    "protected": false,
+    "cpu_topology": "match_host",
+    "platform_version": "~1.0",
+    "memory_mib": 8096,
+    "gpu": {
+        "backend": "virglrenderer",
+        "context_types": ["virgl2"]
+    },
+    "console_input_device": "ttyS0"
+}
diff --git a/java/framework/src/android/system/virtualmachine/VirtualMachine.java b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
index a203358..dccaad0 100644
--- a/java/framework/src/android/system/virtualmachine/VirtualMachine.java
+++ b/java/framework/src/android/system/virtualmachine/VirtualMachine.java
@@ -71,7 +71,6 @@
 import android.system.virtualizationservice.IVirtualMachineCallback;
 import android.system.virtualizationservice.IVirtualizationService;
 import android.system.virtualizationservice.InputDevice;
-import android.system.virtualizationservice.MemoryTrimLevel;
 import android.system.virtualizationservice.PartitionType;
 import android.system.virtualizationservice.VirtualMachineAppConfig;
 import android.system.virtualizationservice.VirtualMachineRawConfig;
@@ -270,34 +269,35 @@
 
         @Override
         public void onTrimMemory(int level) {
-            @MemoryTrimLevel int vmTrimLevel;
+            int percent;
 
             switch (level) {
                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL:
-                    vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
+                    percent = 50;
                     break;
                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW:
-                    vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
+                    percent = 30;
                     break;
                 case ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE:
-                    vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_MODERATE;
+                    percent = 10;
                     break;
                 case ComponentCallbacks2.TRIM_MEMORY_BACKGROUND:
                 case ComponentCallbacks2.TRIM_MEMORY_MODERATE:
                 case ComponentCallbacks2.TRIM_MEMORY_COMPLETE:
                     /* Release as much memory as we can. The app is on the LMKD LRU kill list. */
-                    vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_CRITICAL;
+                    percent = 50;
                     break;
                 default:
                     /* Treat unrecognised messages as generic low-memory warnings. */
-                    vmTrimLevel = MemoryTrimLevel.TRIM_MEMORY_RUNNING_LOW;
+                    percent = 30;
                     break;
             }
 
             synchronized (mLock) {
                 try {
                     if (mVirtualMachine != null) {
-                        mVirtualMachine.onTrimMemory(vmTrimLevel);
+                        long bytes = mConfig.getMemoryBytes();
+                        mVirtualMachine.setMemoryBalloon(bytes * percent / 100);
                     }
                 } catch (Exception e) {
                     /* Caller doesn't want our exceptions. Log them instead. */
diff --git a/libs/hypervisor_props/src/lib.rs b/libs/hypervisor_props/src/lib.rs
index 6665bc5..14614fd 100644
--- a/libs/hypervisor_props/src/lib.rs
+++ b/libs/hypervisor_props/src/lib.rs
@@ -37,8 +37,3 @@
 pub fn version() -> Result<Option<String>> {
     Ok(hypervisorproperties::hypervisor_version()?)
 }
-
-/// Returns if the hypervisor is pKVM
-pub fn is_pkvm() -> Result<bool> {
-    Ok(version()?.unwrap_or_default().starts_with("kvm") && is_protected_vm_supported()?)
-}
diff --git a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
index c7bc3f5..0f0631e 100644
--- a/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
+++ b/microdroid/derive_microdroid_vendor_dice_node/src/main.rs
@@ -47,6 +47,7 @@
     Path::new(AVF_STRICT_BOOT).exists()
 }
 
+// See dice_for_avf_guest.cddl for CDDL of Configuration Descriptor of VM components.
 fn build_descriptor(vbmeta: &VbMetaImage) -> Result<Vec<u8>> {
     let values = DiceConfigValues {
         component_name: Some(cstr!("Microdroid vendor")),
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index cecf413..7cfeb21 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -134,8 +134,8 @@
     apks.chain(apexes).collect()
 }
 
-// Returns a configuration descriptor of the given payload. See vm_config.cddl for the definition
-// of the format.
+// Returns a configuration descriptor of the given payload. See dice_for_avf_guest.cddl for the
+// definition of the format.
 fn format_payload_config_descriptor(
     payload: &PayloadMetadata,
     subcomponents: Vec<Subcomponent>,
diff --git a/microdroid_manager/src/vm_config.cddl b/microdroid_manager/src/vm_config.cddl
deleted file mode 100644
index 8508e8f..0000000
--- a/microdroid_manager/src/vm_config.cddl
+++ /dev/null
@@ -1,56 +0,0 @@
-; Configuration Descriptor used in the DICE node that describes the payload of a Microdroid virtual
-; machine.
-;
-; See the Open DICE specification
-; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/specification.md,
-; and the Android Profile for DICE
-; https://pigweed.googlesource.com/open-dice/+/HEAD/docs/android.md.
-;
-; CDDL for the normal Configuration Descriptor can be found at
-; https://cs.android.com/android/platform/superproject/main/+/main:hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/generateCertificateRequestV2.cddl
-
-; The configuration descriptor node for a Microdroid VM, with extensions to describe the contents
-; of the VM payload.
-; The subcomponents describe the APKs and then the APEXes that are part of the VM. The main APK
-; is first, followed by any extra APKs in the order they are specified in the VM config.
-; The APEXes are listed in the order specified when the VM is created, which is normally alphabetic
-; order by name.
-VmConfigDescriptor = {
-    -70002 : "Microdroid payload",      ; Component name
-    (? -71000: tstr //                  ; Path to the payload config file
-    ? -71001: PayloadConfig),
-    ? -71002: [+ SubcomponentDescriptor],
-}
-
-PayloadConfig = {
-    1: tstr                             ; Path to the binary file where payload execution starts
-}
-
-; Describes a unit of code (e.g. an APK or an APEX) present inside the VM.
-;
-; For an APK, the fields are as follows:
-; - Component name: The string "apk:" followed by the package name.
-; - Security version: The long version code from the APK manifest
-;   (https://developer.android.com/reference/android/content/pm/PackageInfo#getLongVersionCode()).
-; - Code hash: This is the root hash of a Merkle tree computed over all bytes of the APK, as used
-;   in the APK Signature Scheme v4 (https://source.android.com/docs/security/features/apksigning/v4)
-;   with empty salt and using SHA-256 as the hash algorithm.
-; - Authority hash: The SHA-512 hash of the DER representation of the X.509 certificate for the
-;   public key used to sign the APK.
-;
-; For an APEX, they are as follows:
-; - Component name: The string "apex:" followed by the APEX name as specified in the APEX Manifest
-;   (see https://source.android.com/docs/core/ota/apex).
-; - Security version: The version number from the APEX Manifest.
-; - Code hash: The root hash of the apex_payload.img file within the APEX, taken from the first
-;   hashtree descriptor in the VBMeta image
-;   (see https://android.googlesource.com/platform/external/avb/+/master/README.md).
-; - Authority hash: The SHA-512 hash of the public key used to sign the file system image in the
-;   APEX (as stored in the apex_pubkey file). The format is as described for AvbRSAPublicKeyHeader
-;   in https://cs.android.com/android/platform/superproject/main/+/main:external/avb/libavb/avb_crypto.h.
-SubcomponentDescriptor = {
-  1: tstr,                              ; Component name
-  2: uint,                              ; Security version
-  3: bstr,                              ; Code hash
-  4: bstr,                              ; Authority hash
-}
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index c16a45e..1ad2d88 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -171,7 +171,7 @@
 //    rollback protection of pvmfw. Such components may chose to not put SECURITY_VERSION in the
 //    corresponding DiceChainEntry.
 //  4. For each Subcomponent on the last DiceChainEntry (which corresponds to VM payload, See
-//     microdroid_manager/src/vm_config.cddl):
+//     dice_for_avf_guest.cddl):
 //       - GreaterOrEqual on SECURITY_VERSION (Required)
 //       - ExactMatch on AUTHORITY_HASH (Required).
 //  5. ExactMatch on Instance Hash (Required) - This uniquely identifies one VM instance from
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index da19931..8be73a4 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -26,10 +26,11 @@
 use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
 use zerocopy::AsBytes;
 
+// pVM firmware (like other VM components) is expected to populate some fields in DICE
+// Configuration Descriptor. See dice_for_avf_guest.cddl
 const COMPONENT_NAME_KEY: i64 = -70002;
 const SECURITY_VERSION_KEY: i64 = -70005;
 const RKP_VM_MARKER_KEY: i64 = -70006;
-// TODO(b/291245237): Document this key along with others used in ConfigDescriptor in AVF based VM.
 const INSTANCE_HASH_KEY: i64 = -71003;
 
 #[derive(Debug)]
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 026834f..d62a4b4 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -36,7 +36,6 @@
     IVirtualMachine::{BnVirtualMachine, IVirtualMachine},
     IVirtualMachineCallback::IVirtualMachineCallback,
     IVirtualizationService::IVirtualizationService,
-    MemoryTrimLevel::MemoryTrimLevel,
     Partition::Partition,
     PartitionType::PartitionType,
     VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
@@ -1240,10 +1239,20 @@
             .or_service_specific_exception(-1)
     }
 
-    fn onTrimMemory(&self, level: MemoryTrimLevel) -> binder::Result<()> {
+    fn getMemoryBalloon(&self) -> binder::Result<i64> {
+        let balloon = self
+            .instance
+            .get_memory_balloon()
+            .with_context(|| format!("Error getting balloon for VM with CID {}", self.instance.cid))
+            .with_log()
+            .or_service_specific_exception(-1)?;
+        Ok(balloon.try_into().unwrap())
+    }
+
+    fn setMemoryBalloon(&self, num_bytes: i64) -> binder::Result<()> {
         self.instance
-            .trim_memory(level)
-            .with_context(|| format!("Error trimming VM with CID {}", self.instance.cid))
+            .set_memory_balloon(num_bytes.try_into().unwrap())
+            .with_context(|| format!("Error setting balloon for VM with CID {}", self.instance.cid))
             .with_log()
             .or_service_specific_exception(-1)
     }
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 6af84b6..78dd9a2 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -44,7 +44,6 @@
 use std::thread::{self, JoinHandle};
 use android_system_virtualizationcommon::aidl::android::system::virtualizationcommon::DeathReason::DeathReason;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    MemoryTrimLevel::MemoryTrimLevel,
     VirtualMachineAppConfig::DebugLevel::DebugLevel,
     AudioConfig::AudioConfig as AudioConfigParcelable,
     DisplayConfig::DisplayConfig as DisplayConfigParcelable,
@@ -628,42 +627,35 @@
 
     /// Responds to memory-trimming notifications by inflating the virtio
     /// balloon to reclaim guest memory.
-    pub fn trim_memory(&self, level: MemoryTrimLevel) -> Result<(), Error> {
+    pub fn get_memory_balloon(&self) -> Result<u64, Error> {
         let request = VmRequest::BalloonCommand(BalloonControlCommand::Stats {});
-        match vm_control::client::handle_request(&request, &self.crosvm_control_socket_path) {
-            Ok(VmResponse::BalloonStats { stats, balloon_actual: _ }) => {
-                if let Some(total_memory) = stats.total_memory {
-                    // Reclaim up to 50% of total memory assuming worst case
-                    // most memory is anonymous and must be swapped to zram
-                    // with an approximate 2:1 compression ratio.
-                    let pct = match level {
-                        MemoryTrimLevel::TRIM_MEMORY_RUNNING_CRITICAL => 50,
-                        MemoryTrimLevel::TRIM_MEMORY_RUNNING_LOW => 30,
-                        MemoryTrimLevel::TRIM_MEMORY_RUNNING_MODERATE => 10,
-                        _ => bail!("Invalid memory trim level {:?}", level),
-                    };
-                    let command = BalloonControlCommand::Adjust {
-                        num_bytes: total_memory * pct / 100,
-                        wait_for_success: false,
-                    };
-                    if let Err(e) = vm_control::client::handle_request(
-                        &VmRequest::BalloonCommand(command),
-                        &self.crosvm_control_socket_path,
-                    ) {
-                        bail!("Error sending balloon adjustment: {:?}", e);
+        let result =
+            match vm_control::client::handle_request(&request, &self.crosvm_control_socket_path) {
+                Ok(VmResponse::BalloonStats { stats: _, balloon_actual }) => balloon_actual,
+                Ok(VmResponse::Err(e)) => {
+                    // ENOTSUP is returned when the balloon protocol is not initialized. This
+                    // can occur for numerous reasons: Guest is still booting, guest doesn't
+                    // support ballooning, host doesn't support ballooning. We don't log or
+                    // raise an error in this case: trim is just a hint and we can ignore it.
+                    if e.errno() != libc::ENOTSUP {
+                        bail!("Errno return when requesting balloon stats: {}", e.errno())
                     }
+                    0
                 }
-            }
-            Ok(VmResponse::Err(e)) => {
-                // ENOTSUP is returned when the balloon protocol is not initialized. This
-                // can occur for numerous reasons: Guest is still booting, guest doesn't
-                // support ballooning, host doesn't support ballooning. We don't log or
-                // raise an error in this case: trim is just a hint and we can ignore it.
-                if e.errno() != libc::ENOTSUP {
-                    bail!("Errno return when requesting balloon stats: {}", e.errno())
-                }
-            }
-            e => bail!("Error requesting balloon stats: {:?}", e),
+                e => bail!("Error requesting balloon stats: {:?}", e),
+            };
+        Ok(result)
+    }
+
+    /// Responds to memory-trimming notifications by inflating the virtio
+    /// balloon to reclaim guest memory.
+    pub fn set_memory_balloon(&self, num_bytes: u64) -> Result<(), Error> {
+        let command = BalloonControlCommand::Adjust { num_bytes, wait_for_success: false };
+        if let Err(e) = vm_control::client::handle_request(
+            &VmRequest::BalloonCommand(command),
+            &self.crosvm_control_socket_path,
+        ) {
+            bail!("Error sending balloon adjustment: {:?}", e);
         }
         Ok(())
     }
@@ -943,11 +935,8 @@
         let swiotlb_size_mib = 2 * virtio_pci_device_count as u32;
         command.arg("--swiotlb").arg(swiotlb_size_mib.to_string());
 
-        // b/346770542 for consistent "usable" memory across protected and non-protected VMs under
-        // pKVM.
-        if hypervisor_props::is_pkvm()? {
-            memory_mib = memory_mib.map(|m| m.saturating_add(swiotlb_size_mib));
-        }
+        // b/346770542 for consistent "usable" memory across protected and non-protected VMs.
+        memory_mib = memory_mib.map(|m| m.saturating_add(swiotlb_size_mib));
 
         // Workaround to keep crash_dump from trying to read protected guest memory.
         // Context in b/238324526.
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
index 9d1d5d5..afa25e2 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualMachine.aidl
@@ -16,7 +16,6 @@
 package android.system.virtualizationservice;
 
 import android.system.virtualizationservice.IVirtualMachineCallback;
-import android.system.virtualizationservice.MemoryTrimLevel;
 import android.system.virtualizationservice.VirtualMachineState;
 
 interface IVirtualMachine {
@@ -42,8 +41,9 @@
      */
     void stop();
 
-    /** Communicate app low-memory notifications to the VM. */
-    void onTrimMemory(MemoryTrimLevel level);
+    /** Access to the VM's memory balloon. */
+    long getMemoryBalloon();
+    void setMemoryBalloon(long num_bytes);
 
     /** Open a vsock connection to the CID of the VM on the given port. */
     ParcelFileDescriptor connectVsock(int port);
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/MemoryTrimLevel.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/MemoryTrimLevel.aidl
deleted file mode 100644
index 9ed9e99..0000000
--- a/virtualizationservice/aidl/android/system/virtualizationservice/MemoryTrimLevel.aidl
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * Copyright 2022 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.virtualizationservice;
-
-/**
- * Memory trim levels propagated from the app to the VM.
- */
-@Backing(type="int")
-enum MemoryTrimLevel {
-    /* Same meaning as in ComponentCallbacks2 */
-    TRIM_MEMORY_RUNNING_CRITICAL = 0,
-    TRIM_MEMORY_RUNNING_LOW = 1,
-    TRIM_MEMORY_RUNNING_MODERATE = 2,
-}
diff --git a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
index 6f85c42..4980e55 100644
--- a/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
+++ b/vmlauncher_app/java/com/android/virtualization/vmlauncher/MainActivity.java
@@ -83,7 +83,7 @@
     private static final boolean DEBUG = true;
     private ExecutorService mExecutorService;
     private VirtualMachine mVirtualMachine;
-    private ParcelFileDescriptor mCursorStream;
+    private CursorHandler mCursorHandler;
     private ClipboardManager mClipboardManager;
     private static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 101;
 
@@ -414,9 +414,11 @@
                                 try {
                                     ParcelFileDescriptor[] pfds =
                                             ParcelFileDescriptor.createSocketPair();
-                                    mExecutorService.execute(
-                                            new CursorHandler(cursorSurfaceView, pfds[0]));
-                                    mCursorStream = pfds[0];
+                                    if (mCursorHandler != null) {
+                                        mCursorHandler.interrupt();
+                                    }
+                                    mCursorHandler = new CursorHandler(cursorSurfaceView, pfds[0]);
+                                    mCursorHandler.start();
                                     runWithDisplayService(
                                             (service) -> service.setCursorStream(pfds[1]));
                                 } catch (Exception e) {
@@ -646,7 +648,7 @@
         }
     }
 
-    static class CursorHandler implements Runnable {
+    static class CursorHandler extends Thread {
         private final SurfaceView mSurfaceView;
         private final ParcelFileDescriptor mStream;
 
@@ -662,6 +664,10 @@
                 ByteBuffer byteBuffer = ByteBuffer.allocate(8 /* (x: u32, y: u32) */);
                 byteBuffer.order(ByteOrder.LITTLE_ENDIAN);
                 while (true) {
+                    if (Thread.interrupted()) {
+                        Log.d(TAG, "interrupted: exiting CursorHandler");
+                        return;
+                    }
                     byteBuffer.clear();
                     int bytes =
                             IoBridge.read(
@@ -669,6 +675,10 @@
                                     byteBuffer.array(),
                                     0,
                                     byteBuffer.array().length);
+                    if (bytes == -1) {
+                        Log.e(TAG, "cannot read from cursor stream, stop the handler");
+                        return;
+                    }
                     float x = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
                     float y = (float) (byteBuffer.getInt() & 0xFFFFFFFF);
                     mSurfaceView.post(