Merge "Ignore identity if the debug policy is turned on"
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 33498d3..c967473 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -35,6 +35,9 @@
       "path": "packages/modules/Virtualization/apkdmverity"
     },
     {
+      "path": "packages/modules/Virtualization/avmd"
+    },
+    {
       "path": "packages/modules/Virtualization/virtualizationservice"
     },
     {
diff --git a/authfs/fd_server/src/aidl.rs b/authfs/fd_server/src/aidl.rs
index 3a3cdb2..9a60bf7 100644
--- a/authfs/fd_server/src/aidl.rs
+++ b/authfs/fd_server/src/aidl.rs
@@ -36,13 +36,12 @@
 use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::{
     BnVirtFdService, FsStat::FsStat, IVirtFdService, MAX_REQUESTING_DATA,
 };
-use authfs_aidl_interface::binder::{
-    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
-};
 use authfs_fsverity_metadata::{
     get_fsverity_metadata_path, parse_fsverity_metadata, FSVerityMetadata,
 };
-use binder_common::{new_binder_exception, new_binder_service_specific_error};
+use binder::{
+    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, StatusCode, Strong,
+};
 
 /// Bitflags of forbidden file mode, e.g. setuid, setgid and sticky bit.
 const FORBIDDEN_MODES: Mode = Mode::from_bits_truncate(!0o777);
@@ -107,9 +106,12 @@
             entry.insert(new_fd_config);
             Ok(new_fd)
         } else {
-            Err(new_binder_exception(
+            Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_STATE,
-                format!("The newly created FD {} is already in the pool unexpectedly", new_fd),
+                Some(format!(
+                    "The newly created FD {} is already in the pool unexpectedly",
+                    new_fd
+                )),
             ))
         }
     }
@@ -173,9 +175,9 @@
                     if let Some(signature) = &metadata.signature {
                         Ok(signature.clone())
                     } else {
-                        Err(new_binder_exception(
-                            ExceptionCode::SERVICE_SPECIFIC,
-                            "metadata doesn't contain a signature",
+                        Err(Status::new_service_specific_error_str(
+                            -1,
+                            Some("metadata doesn't contain a signature"),
                         ))
                     }
                 } else {
@@ -395,7 +397,7 @@
 }
 
 fn new_errno_error(errno: Errno) -> Status {
-    new_binder_service_specific_error(errno as i32, errno.desc())
+    Status::new_service_specific_error_str(errno as i32, Some(errno.desc()))
 }
 
 fn open_readonly_at(dir_fd: RawFd, path: &Path) -> nix::Result<File> {
diff --git a/authfs/service/Android.bp b/authfs/service/Android.bp
index 6c32c67..943db35 100644
--- a/authfs/service/Android.bp
+++ b/authfs/service/Android.bp
@@ -12,7 +12,6 @@
         "authfs_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
-        "libbinder_common",
         "libbinder_rs",
         "liblibc",
         "liblog_rust",
diff --git a/authfs/service/src/authfs.rs b/authfs/service/src/authfs.rs
index c941360..3e8e0e0 100644
--- a/authfs/service/src/authfs.rs
+++ b/authfs/service/src/authfs.rs
@@ -31,10 +31,7 @@
     OutputDirFdAnnotation::OutputDirFdAnnotation, OutputFdAnnotation::OutputFdAnnotation,
 };
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFs::{BnAuthFs, IAuthFs};
-use authfs_aidl_interface::binder::{
-    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, Strong,
-};
-use binder_common::new_binder_exception;
+use binder::{self, BinderFeatures, Interface, ParcelFileDescriptor, Status, Strong};
 
 const AUTHFS_BIN: &str = "/system/bin/authfs";
 const AUTHFS_SETUP_POLL_INTERVAL_MS: Duration = Duration::from_millis(50);
@@ -60,9 +57,9 @@
         let mut path = PathBuf::from(&self.mountpoint);
         path.push(remote_fd_name.to_string());
         let file = OpenOptions::new().read(true).write(writable).open(&path).map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("failed to open {:?} on authfs: {}", &path, e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("failed to open {:?} on authfs: {}", &path, e)),
             )
         })?;
         Ok(ParcelFileDescriptor::new(file))
@@ -72,7 +69,7 @@
         if let Some(s) = self.mountpoint.to_str() {
             Ok(s.to_string())
         } else {
-            Err(new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, "Bad string encoding"))
+            Err(Status::new_service_specific_error_str(-1, Some("Bad string encoding")))
         }
     }
 }
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index 890e108..77cac9a 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -33,10 +33,9 @@
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::{
     BnAuthFsService, IAuthFsService,
 };
-use authfs_aidl_interface::binder::{
-    self, add_service, BinderFeatures, ExceptionCode, Interface, ProcessState, Strong,
+use binder::{
+    self, add_service, BinderFeatures, ExceptionCode, Interface, ProcessState, Status, Strong,
 };
-use binder_common::new_binder_exception;
 
 const SERVICE_NAME: &str = "authfs_service";
 const SERVICE_ROOT: &str = "/data/misc/authfs";
@@ -57,16 +56,16 @@
 
         // The directory is supposed to be deleted when `AuthFs` is dropped.
         create_dir(&mountpoint).map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Cannot create mount directory {:?}: {:?}", &mountpoint, e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Cannot create mount directory {:?}: {:?}", &mountpoint, e)),
             )
         })?;
 
         authfs::AuthFs::mount_and_wait(mountpoint, config, self.debuggable).map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("mount_and_wait failed: {:?}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("mount_and_wait failed: {:?}", e)),
             )
         })
     }
@@ -80,9 +79,9 @@
 
     fn validate(&self, config: &AuthFsConfig) -> binder::Result<()> {
         if config.port < 0 {
-            return Err(new_binder_exception(
+            return Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_ARGUMENT,
-                format!("Invalid port: {}", config.port),
+                Some(format!("Invalid port: {}", config.port)),
             ));
         }
         Ok(())
diff --git a/authfs/src/file.rs b/authfs/src/file.rs
index d9f8964..df52a0e 100644
--- a/authfs/src/file.rs
+++ b/authfs/src/file.rs
@@ -8,8 +8,7 @@
 
 use crate::common::{divide_roundup, CHUNK_SIZE};
 use authfs_aidl_interface::aidl::com::android::virt::fs::IVirtFdService::IVirtFdService;
-use authfs_aidl_interface::binder::{Status, Strong};
-use binder::StatusCode;
+use binder::{Status, StatusCode, Strong};
 use binder_common::rpc_client::connect_rpc_binder;
 use std::convert::TryFrom;
 use std::io;
diff --git a/avmd/Android.bp b/avmd/Android.bp
index 9f0b28b..b09bed5 100644
--- a/avmd/Android.bp
+++ b/avmd/Android.bp
@@ -22,7 +22,6 @@
 rust_binary {
     name: "avmdtool",
     srcs: ["src/main.rs"],
-    required: ["avbtool"],
     host_supported: true,
     prefer_rlib: true,
     rustlibs: [
@@ -36,3 +35,16 @@
         "libvbmeta_rust",
     ],
 }
+
+rust_test_host {
+    name: "avmdtool_tests",
+    srcs: ["tests/*_test.rs"],
+    test_suites: ["general-tests"],
+    prefer_rlib: true,
+    data: ["tests/data/*"],
+    data_bins: ["avmdtool"],
+    data_libs: [
+        "libcrypto",
+        "libz",
+    ],
+}
diff --git a/avmd/TEST_MAPPING b/avmd/TEST_MAPPING
new file mode 100644
index 0000000..dd687fe
--- /dev/null
+++ b/avmd/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+  "presubmit" : [
+    {
+      "name" : "avmdtool_tests"
+    }
+  ]
+}
diff --git a/avmd/tests/avmdtool_test.rs b/avmd/tests/avmdtool_test.rs
new file mode 100644
index 0000000..d93cb6f
--- /dev/null
+++ b/avmd/tests/avmdtool_test.rs
@@ -0,0 +1,34 @@
+// 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.
+
+//! Tests for avmdtool.
+
+use std::fs;
+use std::process::Command;
+
+#[test]
+fn test_dump() {
+    // test.avmd is generated with
+    // ```
+    // avmdtool create /tmp/test.amvd \
+    // --apex-payload microdroid vbmeta ./libs/apexutil/tests/data/test.apex \
+    // --apk microdroid_manager apk \
+    // ./libs/apkverify/tests/data/v3-only-with-rsa-pkcs1-sha256-4096.apk \
+    // --apk microdroid_manager extra-apk ./libs/apkverify/tests/data/v3-only-with-stamp.apk
+    //```
+    let output =
+        Command::new("./avmdtool").args(["dump", "tests/data/test.avmd"]).output().unwrap();
+    assert!(output.status.success());
+    assert_eq!(output.stdout, fs::read("tests/data/test.avmd.dump").unwrap());
+}
diff --git a/avmd/tests/data/test.avmd b/avmd/tests/data/test.avmd
new file mode 100644
index 0000000..52e634f
--- /dev/null
+++ b/avmd/tests/data/test.avmd
Binary files differ
diff --git a/avmd/tests/data/test.avmd.dump b/avmd/tests/data/test.avmd.dump
new file mode 100644
index 0000000..a63a151
--- /dev/null
+++ b/avmd/tests/data/test.avmd.dump
@@ -0,0 +1,16 @@
+Descriptors:
+  VBMeta descriptor:
+    namespace:             microdroid
+    name:                  vbmeta
+    vbmeta digest:         296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d
+  APK descriptor:
+    namespace:             microdroid_manager
+    name:                  apk
+    Signing algorithm ID:  0x103
+    APK digest:            0df2426ea33aedaf495d88e5be0c6a1663ff0a81c5ed12d5b2929ae4b4300f2f
+  APK descriptor:
+    namespace:             microdroid_manager
+    name:                  extra-apk
+    Signing algorithm ID:  0x201
+    APK digest:            626bb647c0089717a7ffa52fd8e845f9403d5e27f7a5a8752e47b3345fb82f5c
+
diff --git a/compos/common/Android.bp b/compos/common/Android.bp
index 3c3397d..23a1eb9 100644
--- a/compos/common/Android.bp
+++ b/compos/common/Android.bp
@@ -11,7 +11,7 @@
         "android.system.virtualizationservice-rust",
         "compos_aidl_interface-rust",
         "libanyhow",
-        "libbinder_common",
+        "libbinder_rs",
         "liblazy_static",
         "liblog_rust",
         "libnested_virt",
diff --git a/compos/common/binder.rs b/compos/common/binder.rs
index 45139f3..d3550f7 100644
--- a/compos/common/binder.rs
+++ b/compos/common/binder.rs
@@ -16,8 +16,7 @@
 
 //! Helper for converting Error types to what Binder expects
 
-use android_system_virtualizationservice::binder::{ExceptionCode, Result as BinderResult};
-use binder_common::new_binder_exception;
+use binder::{Result as BinderResult, Status};
 use log::warn;
 use std::fmt::Debug;
 
@@ -28,6 +27,6 @@
     result.map_err(|e| {
         let message = format!("{:?}", e);
         warn!("Returning binder error: {}", &message);
-        new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, message)
+        Status::new_service_specific_error_str(-1, Some(message))
     })
 }
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index cd1ece4..770f489 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -19,16 +19,12 @@
 use crate::timeouts::TIMEOUTS;
 use crate::{COMPOS_APEX_ROOT, COMPOS_DATA_ROOT, COMPOS_VSOCK_PORT, DEFAULT_VM_CONFIG_PATH};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason,
-    IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
     IVirtualizationService::IVirtualizationService,
     VirtualMachineAppConfig::{DebugLevel::DebugLevel, VirtualMachineAppConfig},
     VirtualMachineConfig::VirtualMachineConfig,
 };
-use android_system_virtualizationservice::binder::{
-    BinderFeatures, Interface, ParcelFileDescriptor, Result as BinderResult, Strong,
-};
 use anyhow::{bail, Context, Result};
+use binder::{ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
 use log::{info, warn};
 use rustutils::system_properties;
@@ -37,7 +33,7 @@
 use std::num::NonZeroU32;
 use std::path::{Path, PathBuf};
 use std::thread;
-use vmclient::{VmInstance, VmWaitError};
+use vmclient::{DeathReason, VmInstance, VmWaitError};
 
 /// This owns an instance of the CompOS VM.
 pub struct ComposClient(VmInstance);
@@ -119,13 +115,10 @@
             taskProfiles: parameters.task_profiles.clone(),
         });
 
-        let instance = VmInstance::create(service, &config, console_fd, log_fd)
+        let callback = Box::new(Callback {});
+        let instance = VmInstance::create(service, &config, console_fd, log_fd, Some(callback))
             .context("Failed to create VM")?;
 
-        let callback =
-            BnVirtualMachineCallback::new_binder(VmCallback(), BinderFeatures::default());
-        instance.vm.registerCallback(&callback)?;
-
         instance.start()?;
 
         let ready = instance.wait_until_ready(TIMEOUTS.vm_max_time_to_ready);
@@ -163,7 +156,7 @@
     fn wait_for_shutdown(self) {
         let death_reason = self.0.wait_for_death_with_timeout(TIMEOUTS.vm_max_time_to_exit);
         match death_reason {
-            Some(vmclient::DeathReason::Shutdown) => info!("VM has exited normally"),
+            Some(DeathReason::Shutdown) => info!("VM has exited normally"),
             Some(reason) => warn!("VM died with reason {:?}", reason),
             None => warn!("VM failed to exit, dropping"),
         }
@@ -228,53 +221,36 @@
     bail!("No VM support available")
 }
 
-struct VmCallback();
-
-impl Interface for VmCallback {}
-
-impl IVirtualMachineCallback for VmCallback {
-    fn onDied(&self, cid: i32, reason: DeathReason) -> BinderResult<()> {
-        log::warn!("VM died, cid = {}, reason = {:?}", cid, reason);
-        Ok(())
-    }
-
-    fn onPayloadStarted(
-        &self,
-        cid: i32,
-        stream: Option<&ParcelFileDescriptor>,
-    ) -> BinderResult<()> {
-        if let Some(pfd) = stream {
-            if let Err(e) = start_logging(pfd) {
+struct Callback {}
+impl vmclient::VmCallback for Callback {
+    fn on_payload_started(&self, cid: i32, stream: Option<&File>) {
+        if let Some(file) = stream {
+            if let Err(e) = start_logging(file) {
                 warn!("Can't log vm output: {}", e);
             };
         }
         log::info!("VM payload started, cid = {}", cid);
-        Ok(())
     }
 
-    fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
+    fn on_payload_ready(&self, cid: i32) {
         log::info!("VM payload ready, cid = {}", cid);
-        Ok(())
     }
 
-    fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
+    fn on_payload_finished(&self, cid: i32, exit_code: i32) {
         log::warn!("VM payload finished, cid = {}, exit code = {}", cid, exit_code);
-        Ok(())
     }
 
-    fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
-        log::warn!("VM error, cid = {}, error code = {}, message = {}", cid, error_code, message,);
-        Ok(())
+    fn on_error(&self, cid: i32, error_code: i32, message: &str) {
+        log::warn!("VM error, cid = {}, error code = {}, message = {}", cid, error_code, message);
     }
 
-    fn onRamdump(&self, _cid: i32, _ramdump: &ParcelFileDescriptor) -> BinderResult<()> {
-        // TODO(b/238295267) send this to tombstone?
-        Ok(())
+    fn on_died(&self, cid: i32, death_reason: DeathReason) {
+        log::warn!("VM died, cid = {}, reason = {:?}", cid, death_reason);
     }
 }
 
-fn start_logging(pfd: &ParcelFileDescriptor) -> Result<()> {
-    let reader = BufReader::new(pfd.as_ref().try_clone().context("Cloning fd failed")?);
+fn start_logging(file: &File) -> Result<()> {
+    let reader = BufReader::new(file.try_clone().context("Cloning file failed")?);
     thread::spawn(move || {
         for line in reader.lines() {
             match line {
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index ebcd689..5315828 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -25,8 +25,8 @@
 mod service;
 
 use crate::instance_manager::InstanceManager;
-use android_system_composd::binder::{register_lazy_service, ProcessState};
 use anyhow::{Context, Result};
+use binder::{register_lazy_service, ProcessState};
 use log::{error, info};
 use std::panic;
 use std::sync::Arc;
diff --git a/compos/composd/src/instance_manager.rs b/compos/composd/src/instance_manager.rs
index 9a0935c..75671d7 100644
--- a/compos/composd/src/instance_manager.rs
+++ b/compos/composd/src/instance_manager.rs
@@ -20,7 +20,7 @@
 use crate::instance_starter::{CompOsInstance, InstanceStarter};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice;
 use anyhow::{bail, Result};
-use compos_aidl_interface::binder::Strong;
+use binder::Strong;
 use compos_common::compos_client::VmParameters;
 use compos_common::{
     CURRENT_INSTANCE_DIR, DEX2OAT_CPU_SET_PROP_NAME, DEX2OAT_THREADS_PROP_NAME,
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 111c719..aaa4695 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -21,9 +21,8 @@
     IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
 };
 use anyhow::{Context, Result};
-use binder_common::lazy_service::LazyServiceGuard;
+use binder::{LazyServiceGuard, ParcelFileDescriptor, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::ICompOsService;
-use compos_aidl_interface::binder::{ParcelFileDescriptor, Strong};
 use compos_common::compos_client::{ComposClient, VmParameters};
 use compos_common::{COMPOS_DATA_ROOT, IDSIG_FILE, IDSIG_MANIFEST_APK_FILE, INSTANCE_IMAGE_FILE};
 use log::info;
diff --git a/compos/composd/src/odrefresh_task.rs b/compos/composd/src/odrefresh_task.rs
index 8fd574c..100fc50 100644
--- a/compos/composd/src/odrefresh_task.rs
+++ b/compos/composd/src/odrefresh_task.rs
@@ -22,8 +22,8 @@
     ICompilationTask::ICompilationTask,
     ICompilationTaskCallback::{FailureReason::FailureReason, ICompilationTaskCallback},
 };
-use android_system_composd::binder::{Interface, Result as BinderResult, Strong};
 use anyhow::{Context, Result};
+use binder::{Interface, Result as BinderResult, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
     CompilationMode::CompilationMode, ICompOsService,
 };
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index a9b8202..49cfd3a 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -26,10 +26,8 @@
         ApexSource::ApexSource, BnIsolatedCompilationService, IIsolatedCompilationService,
     },
 };
-use android_system_composd::binder::{
-    self, BinderFeatures, ExceptionCode, Interface, Status, Strong, ThreadState,
-};
 use anyhow::{Context, Result};
+use binder::{self, BinderFeatures, ExceptionCode, Interface, Status, Strong, ThreadState};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::CompilationMode::CompilationMode;
 use compos_common::binder::to_binder_result;
 use compos_common::odrefresh::{PENDING_ARTIFACTS_SUBDIR, TEST_ARTIFACTS_SUBDIR};
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index e14cd94..ab228e1 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -32,7 +32,7 @@
     },
     IAuthFsService::IAuthFsService,
 };
-use authfs_aidl_interface::binder::Strong;
+use binder::Strong;
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::CompilationMode::CompilationMode;
 use compos_common::odrefresh::ExitCode;
 
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 5d58221..baf444e 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -19,7 +19,6 @@
 //! actual compiler.
 
 use anyhow::{bail, Context, Result};
-use binder_common::new_binder_exception;
 use log::{error, info};
 use rustutils::system_properties;
 use std::default::Default;
@@ -31,12 +30,10 @@
 use crate::artifact_signer::ArtifactSigner;
 use crate::compilation::{odrefresh, OdrefreshContext};
 use crate::compos_key;
+use binder::{BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Status, Strong};
 use compos_aidl_interface::aidl::com::android::compos::ICompOsService::{
     BnCompOsService, CompilationMode::CompilationMode, ICompOsService,
 };
-use compos_aidl_interface::binder::{
-    BinderFeatures, ExceptionCode, Interface, Result as BinderResult, Strong,
-};
 use compos_common::binder::to_binder_result;
 use compos_common::odrefresh::{is_system_property_interesting, ODREFRESH_PATH};
 
@@ -67,28 +64,28 @@
     fn initializeSystemProperties(&self, names: &[String], values: &[String]) -> BinderResult<()> {
         let mut initialized = self.initialized.write().unwrap();
         if initialized.is_some() {
-            return Err(new_binder_exception(
+            return Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_STATE,
-                format!("Already initialized: {:?}", initialized),
+                Some(format!("Already initialized: {:?}", initialized)),
             ));
         }
         *initialized = Some(false);
 
         if names.len() != values.len() {
-            return Err(new_binder_exception(
+            return Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_ARGUMENT,
-                format!(
+                Some(format!(
                     "Received inconsistent number of keys ({}) and values ({})",
                     names.len(),
                     values.len()
-                ),
+                )),
             ));
         }
         for (name, value) in zip(names, values) {
             if !is_system_property_interesting(name) {
-                return Err(new_binder_exception(
+                return Err(Status::new_exception_str(
                     ExceptionCode::ILLEGAL_ARGUMENT,
-                    format!("Received invalid system property {}", &name),
+                    Some(format!("Received invalid system property {}", &name)),
                 ));
             }
             let result = system_properties::write(name, value);
@@ -113,9 +110,9 @@
     ) -> BinderResult<i8> {
         let initialized = *self.initialized.read().unwrap();
         if !initialized.unwrap_or(false) {
-            return Err(new_binder_exception(
+            return Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_STATE,
-                "Service has not been initialized",
+                Some("Service has not been initialized"),
             ));
         }
 
@@ -129,7 +126,7 @@
             system_server_compiler_filter,
         ))?;
 
-        let authfs_service = authfs_aidl_interface::binder::get_interface(AUTHFS_SERVICE_NAME)?;
+        let authfs_service = binder::get_interface(AUTHFS_SERVICE_NAME)?;
         let exit_code = to_binder_result(
             odrefresh(&self.odrefresh_path, context, authfs_service, |output_dir| {
                 // authfs only shows us the files we created, so it's ok to just sign everything
diff --git a/compos/verify/verify.rs b/compos/verify/verify.rs
index 224cde7..2ece8f5 100644
--- a/compos/verify/verify.rs
+++ b/compos/verify/verify.rs
@@ -19,7 +19,7 @@
 
 use android_logger::LogId;
 use anyhow::{bail, Context, Result};
-use compos_aidl_interface::binder::ProcessState;
+use binder::ProcessState;
 use compos_common::compos_client::{ComposClient, VmParameters};
 use compos_common::odrefresh::{
     CURRENT_ARTIFACTS_SUBDIR, ODREFRESH_OUTPUT_ROOT_DIR, PENDING_ARTIFACTS_SUBDIR,
diff --git a/libs/binder_common/Android.bp b/libs/binder_common/Android.bp
index 209955d..47a2f21 100644
--- a/libs/binder_common/Android.bp
+++ b/libs/binder_common/Android.bp
@@ -10,7 +10,6 @@
     rustlibs: [
         "libbinder_rs",
         "libbinder_rpc_unstable_bindgen",
-        "liblazy_static",
     ],
     apex_available: [
         "com.android.compos",
diff --git a/libs/binder_common/lazy_service.rs b/libs/binder_common/lazy_service.rs
deleted file mode 100644
index 9d605b6..0000000
--- a/libs/binder_common/lazy_service.rs
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * Copyright (C) 2021 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.
- */
-
-//! Rust API for lazy (aka dynamic) AIDL services.
-//! See https://source.android.com/devices/architecture/aidl/dynamic-aidl.
-
-use binder::force_lazy_services_persist;
-use lazy_static::lazy_static;
-use std::sync::Mutex;
-
-// TODO(b/200924402): Move this class to libbinder_rs once the infrastructure needed exists.
-
-/// An RAII object to ensure a server of lazy services is not killed. During the lifetime of any of
-/// these objects the service manager will not not kill the current process even if none of its
-/// lazy services are in use.
-#[must_use]
-#[derive(Debug)]
-pub struct LazyServiceGuard {
-    // Prevent construction outside this module.
-    _private: (),
-}
-
-lazy_static! {
-    // Count of how many LazyServiceGuard objects are in existence.
-    static ref GUARD_COUNT: Mutex<u64> = Mutex::new(0);
-}
-
-impl LazyServiceGuard {
-    /// Create a new LazyServiceGuard to prevent the service manager prematurely killing this
-    /// process.
-    pub fn new() -> Self {
-        let mut count = GUARD_COUNT.lock().unwrap();
-        *count += 1;
-        if *count == 1 {
-            // It's important that we make this call with the mutex held, to make sure
-            // that multiple calls (e.g. if the count goes 1 -> 0 -> 1) are correctly
-            // sequenced. (That also means we can't just use an AtomicU64.)
-            force_lazy_services_persist(true);
-        }
-        Self { _private: () }
-    }
-}
-
-impl Drop for LazyServiceGuard {
-    fn drop(&mut self) {
-        let mut count = GUARD_COUNT.lock().unwrap();
-        *count -= 1;
-        if *count == 0 {
-            force_lazy_services_persist(false);
-        }
-    }
-}
-
-impl Clone for LazyServiceGuard {
-    fn clone(&self) -> Self {
-        Self::new()
-    }
-}
-
-impl Default for LazyServiceGuard {
-    fn default() -> Self {
-        Self::new()
-    }
-}
diff --git a/libs/binder_common/lib.rs b/libs/binder_common/lib.rs
index fd81da5..14dd9f2 100644
--- a/libs/binder_common/lib.rs
+++ b/libs/binder_common/lib.rs
@@ -16,27 +16,5 @@
 
 //! Common items useful for binder clients and/or servers.
 
-pub mod lazy_service;
 pub mod rpc_client;
 pub mod rpc_server;
-
-use binder::{ExceptionCode, Status};
-use std::ffi::CString;
-
-/// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
-pub fn new_binder_exception<T: AsRef<str>>(exception: ExceptionCode, message: T) -> Status {
-    match exception {
-        ExceptionCode::SERVICE_SPECIFIC => new_binder_service_specific_error(-1, message),
-        _ => Status::new_exception(exception, to_cstring(message).as_deref()),
-    }
-}
-
-/// Constructs a Binder `Status` representing a service-specific exception with the given code and
-/// message.
-pub fn new_binder_service_specific_error<T: AsRef<str>>(code: i32, message: T) -> Status {
-    Status::new_service_specific_error(code, to_cstring(message).as_deref())
-}
-
-fn to_cstring<T: AsRef<str>>(message: T) -> Option<CString> {
-    CString::new(message.as_ref()).ok()
-}
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index b6ccd9e..fb6a1ad 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -66,7 +66,7 @@
         platformVersion: "~1.0".to_string(),
         taskProfiles: vec![],
     });
-    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log))
+    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
         .context("Failed to create VM")?;
 
     vm.start().context("Failed to start VM")?;
diff --git a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
index 90aac1e..f6e9a71 100644
--- a/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
+++ b/tests/benchmark/src/java/com/android/microdroid/benchmark/MicrodroidBenchmarks.java
@@ -122,6 +122,7 @@
 
         final int trialCount = 10;
 
+        List<Double> vmStartingTimeMetrics = new ArrayList<>();
         List<Double> bootTimeMetrics = new ArrayList<>();
         List<Double> bootloaderTimeMetrics = new ArrayList<>();
         List<Double> kernelBootTimeMetrics = new ArrayList<>();
@@ -139,13 +140,15 @@
             BootResult result = tryBootVm(TAG, "test_vm_boot_time");
             assertThat(result.payloadStarted).isTrue();
 
-            final Double nanoToMilli = 1000000.0;
+            final double nanoToMilli = 1000000.0;
+            vmStartingTimeMetrics.add(result.getVMStartingElapsedNanoTime() / nanoToMilli);
             bootTimeMetrics.add(result.endToEndNanoTime / nanoToMilli);
             bootloaderTimeMetrics.add(result.getBootloaderElapsedNanoTime() / nanoToMilli);
             kernelBootTimeMetrics.add(result.getKernelElapsedNanoTime() / nanoToMilli);
             userspaceBootTimeMetrics.add(result.getUserspaceElapsedNanoTime() / nanoToMilli);
         }
 
+        reportMetrics(vmStartingTimeMetrics,    "avf_perf/microdroid/vm_starting_time_",    "_ms");
         reportMetrics(bootTimeMetrics,          "avf_perf/microdroid/boot_time_",           "_ms");
         reportMetrics(bootloaderTimeMetrics,    "avf_perf/microdroid/bootloader_time_",     "_ms");
         reportMetrics(kernelBootTimeMetrics,    "avf_perf/microdroid/kernel_boot_time_",    "_ms");
diff --git a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
index 84e189a..efd7c85 100644
--- a/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
+++ b/tests/helper/src/java/com/android/microdroid/test/MicrodroidDeviceTestBase.java
@@ -234,6 +234,7 @@
     public static class BootResult {
         public final boolean payloadStarted;
         public final int deathReason;
+        public final long apiCallNanoTime;
         public final long endToEndNanoTime;
 
         public final OptionalLong vcpuStartedNanoTime;
@@ -243,11 +244,13 @@
 
         BootResult(boolean payloadStarted,
                 int deathReason,
+                long apiCallNanoTime,
                 long endToEndNanoTime,
                 OptionalLong vcpuStartedNanoTime,
                 OptionalLong kernelStartedNanoTime,
                 OptionalLong initStartedNanoTime,
                 OptionalLong payloadStartedNanoTime) {
+            this.apiCallNanoTime = apiCallNanoTime;
             this.payloadStarted = payloadStarted;
             this.deathReason = deathReason;
             this.endToEndNanoTime = endToEndNanoTime;
@@ -273,6 +276,10 @@
             return payloadStartedNanoTime.getAsLong();
         }
 
+        public long getVMStartingElapsedNanoTime() {
+            return getVcpuStartedNanoTime() - apiCallNanoTime;
+        }
+
         public long getBootloaderElapsedNanoTime() {
             return getKernelStartedNanoTime() - getVcpuStartedNanoTime();
         }
@@ -307,12 +314,13 @@
                         super.onDied(vm, reason);
                     }
                 };
-        long beginTime = System.nanoTime();
+        long apiCallNanoTime = System.nanoTime();
         listener.runToFinish(logTag, vm);
         return new BootResult(
                 payloadStarted.getNow(false),
                 deathReason.getNow(DeathReason.INFRASTRUCTURE_ERROR),
-                endTime.getNow(beginTime) - beginTime,
+                apiCallNanoTime,
+                endTime.getNow(apiCallNanoTime) - apiCallNanoTime,
                 listener.getVcpuStartedNanoTime(),
                 listener.getKernelStartedNanoTime(),
                 listener.getInitStartedNanoTime(),
diff --git a/tests/hostside/Android.bp b/tests/hostside/Android.bp
index b7f34e7..5ce19bd 100644
--- a/tests/hostside/Android.bp
+++ b/tests/hostside/Android.bp
@@ -14,6 +14,7 @@
     ],
     static_libs: [
         "MicrodroidHostTestHelper",
+        "compatibility-host-util",
     ],
     per_testcase_directory: true,
     data: [
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
index 07b8679..722ec74 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidTestCase.java
@@ -31,6 +31,7 @@
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.result.TestDescription;
 import com.android.tradefed.result.TestResult;
@@ -103,6 +104,11 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-1-2",
+            "9.17/C-1-4"
+    })
     public void testCreateVmRequiresPermission() throws Exception {
         // Revoke the MANAGE_VIRTUAL_MACHINE permission for the test app
         CommandRunner android = new CommandRunner(getDevice());
@@ -330,6 +336,11 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-2-1",
+            "9.17/C-2-2",
+            "9.17/C-2-6"
+    })
     public void testBootFailsWhenProtectedVmStartsWithImagesSignedWithDifferentKey()
             throws Exception {
         assumeTrue(isProtectedVmSupported());
@@ -345,6 +356,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-2-2",
+            "9.17/C-2-6"
+    })
     public void testBootSucceedsWhenNonProtectedVmStartsWithImagesSignedWithDifferentKey()
             throws Exception {
         File key = findTestFile("test.com.android.virt.pem");
@@ -360,6 +375,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-2-2",
+            "9.17/C-2-6"
+    })
     public void testBootFailsWhenBootloaderAndVbMetaAreSignedWithDifferentKeys()
             throws Exception {
         // Sign everything with key1 except vbmeta
@@ -380,6 +399,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-2-2",
+            "9.17/C-2-6"
+    })
     public void testBootSucceedsWhenBootloaderAndVbmetaHaveSameSigningKeys()
             throws Exception {
         // Sign everything with key1 except bootloader and vbmeta
@@ -441,6 +464,11 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-1-2",
+            "9.17/C/1-3"
+    })
     public void testMicrodroidBoots() throws Exception {
         final String configPath = "assets/vm_config.json"; // path inside the APK
         final String cid =
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index d468d76..29a74ca 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -17,6 +17,7 @@
         "cbor-java",
         "com.android.microdroid.testservice-java",
         "truth-prebuilt",
+        "compatibility-common-util-devicesidelib",
     ],
     libs: ["android.system.virtualmachine"],
     jni_libs: ["MicrodroidTestNativeLib"],
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 b429e4d..db2ca0f 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -30,6 +30,7 @@
 import android.system.virtualmachine.VirtualMachineException;
 import android.util.Log;
 
+import com.android.compatibility.common.util.CddTest;
 import com.android.microdroid.testservice.ITestService;
 
 import org.junit.Before;
@@ -80,6 +81,10 @@
     private static final int MIN_MEM_X86_64 = 196;
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-1"
+    })
     public void connectToVmService() throws VirtualMachineException, InterruptedException {
         assume()
             .withMessage("SKip on 5.4 kernel. b/218303240")
@@ -177,6 +182,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void changingDebugLevelInvalidatesVmIdentity()
             throws VirtualMachineException, InterruptedException, IOException {
         assume()
@@ -252,6 +261,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void instancesOfSameVmHaveDifferentCdis()
             throws VirtualMachineException, InterruptedException {
         assume()
@@ -276,6 +289,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void sameInstanceKeepsSameCdis()
             throws VirtualMachineException, InterruptedException {
         assume()
@@ -297,6 +314,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void bccIsSuperficiallyWellFormed()
             throws VirtualMachineException, InterruptedException, CborException {
         assume()
@@ -417,12 +438,20 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void bootFailsWhenMicrodroidDataIsCompromised()
             throws VirtualMachineException, InterruptedException, IOException {
         assertThatBootFailsAfterCompromisingPartition(MICRODROID_PARTITION_UUID);
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void bootFailsWhenUBootAvbDataIsCompromised()
             throws VirtualMachineException, InterruptedException, IOException {
         if (mProtectedVm) {
@@ -434,6 +463,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void bootFailsWhenUBootEnvDataIsCompromised()
             throws VirtualMachineException, InterruptedException, IOException {
         if (mProtectedVm) {
@@ -445,6 +478,10 @@
     }
 
     @Test
+    @CddTest(requirements = {
+            "9.17/C-1-1",
+            "9.17/C-2-7"
+    })
     public void bootFailsWhenPvmFwDataIsCompromised()
             throws VirtualMachineException, InterruptedException, IOException {
         if (mProtectedVm) {
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index cc8d8a3..bba75ac 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -34,9 +34,9 @@
     VirtualMachineRawConfig::VirtualMachineRawConfig,
     VirtualMachineState::VirtualMachineState,
 };
-use android_system_virtualizationservice::binder::{
-    self, BinderFeatures, ExceptionCode, Interface, ParcelFileDescriptor, SpIBinder, Status,
-    StatusCode, Strong, ThreadState,
+use binder::{
+    self, BinderFeatures, ExceptionCode, Interface, LazyServiceGuard, ParcelFileDescriptor,
+    SpIBinder, Status, StatusCode, Strong, ThreadState,
 };
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::{
     IVirtualMachineService::{
@@ -45,7 +45,7 @@
     },
 };
 use anyhow::{anyhow, bail, Context, Result};
-use binder_common::{lazy_service::LazyServiceGuard, new_binder_exception, rpc_server::run_rpc_server_with_factory};
+use binder_common::rpc_server::run_rpc_server_with_factory;
 use disk::QcowFile;
 use idsig::{HashAlgorithm, V4Signature};
 use log::{debug, error, info, warn, trace};
@@ -160,23 +160,23 @@
     ) -> binder::Result<()> {
         check_manage_access()?;
         let size = size.try_into().map_err(|e| {
-            new_binder_exception(
+            Status::new_exception_str(
                 ExceptionCode::ILLEGAL_ARGUMENT,
-                format!("Invalid size {}: {}", size, e),
+                Some(format!("Invalid size {}: {}", size, e)),
             )
         })?;
         let image = clone_file(image_fd)?;
         // initialize the file. Any data in the file will be erased.
         image.set_len(0).map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to reset a file: {}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to reset a file: {}", e)),
             )
         })?;
         let mut part = QcowFile::new(image, size).map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to create QCOW2 image: {}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to create QCOW2 image: {}", e)),
             )
         })?;
 
@@ -189,9 +189,9 @@
             )),
         }
         .map_err(|e| {
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to initialize partition as {:?}: {}", partition_type, e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to initialize partition as {:?}: {}", partition_type, e)),
             )
         })?;
 
@@ -379,12 +379,12 @@
                 "Failed to create temporary directory {:?} for VM files: {:?}",
                 temporary_directory, e
             );
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!(
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!(
                     "Failed to create temporary directory {:?} for VM files: {}",
                     temporary_directory, e
-                ),
+                )),
             )
         })?;
 
@@ -395,9 +395,12 @@
                 load_app_config(config, &temporary_directory).map_err(|e| {
                     error!("Failed to load app config from {}: {:?}", &config.configPath, e);
                     *is_protected = config.protectedVm;
-                    new_binder_exception(
-                        ExceptionCode::SERVICE_SPECIFIC,
-                        format!("Failed to load app config from {}: {}", &config.configPath, e),
+                    Status::new_service_specific_error_str(
+                        -1,
+                        Some(format!(
+                            "Failed to load app config from {}: {}",
+                            &config.configPath, e
+                        )),
                     )
                 })?,
             ),
@@ -423,14 +426,14 @@
                 }
             })
             .try_for_each(check_label_for_partition)
-            .map_err(|e| new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string()))?;
+            .map_err(|e| Status::new_service_specific_error_str(-1, Some(e.to_string())))?;
 
         let zero_filler_path = temporary_directory.join("zero.img");
         write_zero_filler(&zero_filler_path).map_err(|e| {
             error!("Failed to make composite image: {:?}", e);
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to make composite image: {}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to make composite image: {}", e)),
             )
         })?;
 
@@ -456,9 +459,9 @@
         let ramdump_path = temporary_directory.join("ramdump");
         let ramdump = prepare_ramdump_file(&ramdump_path).map_err(|e| {
             error!("Failed to prepare ramdump file: {}", e);
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to prepare ramdump file: {}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to prepare ramdump file: {}", e)),
             )
         })?;
 
@@ -492,9 +495,9 @@
             )
             .map_err(|e| {
                 error!("Failed to create VM with config {:?}: {:?}", config, e);
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to create VM: {}", e),
+                Status::new_service_specific_error_str(
+                    -1,
+                    Some(format!("Failed to create VM: {}", e)),
                 )
             })?,
         );
@@ -575,9 +578,9 @@
     let image = if !disk.partitions.is_empty() {
         if disk.image.is_some() {
             warn!("DiskImage {:?} contains both image and partitions.", disk);
-            return Err(new_binder_exception(
+            return Err(Status::new_exception_str(
                 ExceptionCode::ILLEGAL_ARGUMENT,
-                "DiskImage contains both image and partitions.",
+                Some("DiskImage contains both image and partitions."),
             ));
         }
 
@@ -592,9 +595,9 @@
         )
         .map_err(|e| {
             error!("Failed to make composite image with config {:?}: {:?}", disk, e);
-            new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("Failed to make composite image: {}", e),
+            Status::new_service_specific_error_str(
+                -1,
+                Some(format!("Failed to make composite image: {}", e)),
             )
         })?;
 
@@ -607,9 +610,9 @@
         clone_file(image)?
     } else {
         warn!("DiskImage {:?} didn't contain image or partitions.", disk);
-        return Err(new_binder_exception(
+        return Err(Status::new_exception_str(
             ExceptionCode::ILLEGAL_ARGUMENT,
-            "DiskImage didn't contain image or partitions.",
+            Some("DiskImage didn't contain image or partitions."),
         ));
     };
 
@@ -700,15 +703,15 @@
                 Ok(sid) => Ok(sid.to_owned()),
                 Err(e) => {
                     error!("SID was not valid UTF-8: {}", e);
-                    Err(new_binder_exception(
+                    Err(Status::new_exception_str(
                         ExceptionCode::ILLEGAL_ARGUMENT,
-                        format!("SID was not valid UTF-8: {}", e),
+                        Some(format!("SID was not valid UTF-8: {}", e)),
                     ))
                 }
             }
         } else {
             error!("Missing SID on createVm");
-            Err(new_binder_exception(ExceptionCode::SECURITY, "Missing SID on createVm"))
+            Err(Status::new_exception_str(ExceptionCode::SECURITY, Some("Missing SID on createVm")))
         }
     })
 }
@@ -726,9 +729,9 @@
     if perm_svc.checkPermission(perm, calling_pid, calling_uid as i32)? {
         Ok(())
     } else {
-        Err(new_binder_exception(
+        Err(Status::new_exception_str(
             ExceptionCode::SECURITY,
-            format!("does not have the {} permission", perm),
+            Some(format!("does not have the {} permission", perm)),
         ))
     }
 }
@@ -804,26 +807,26 @@
     fn start(&self) -> binder::Result<()> {
         self.instance.start().map_err(|e| {
             error!("Error starting VM with CID {}: {:?}", self.instance.cid, e);
-            new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string())
+            Status::new_service_specific_error_str(-1, Some(e.to_string()))
         })
     }
 
     fn stop(&self) -> binder::Result<()> {
         self.instance.kill().map_err(|e| {
             error!("Error stopping VM with CID {}: {:?}", self.instance.cid, e);
-            new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, e.to_string())
+            Status::new_service_specific_error_str(-1, Some(e.to_string()))
         })
     }
 
     fn connectVsock(&self, port: i32) -> binder::Result<ParcelFileDescriptor> {
         if !matches!(&*self.instance.vm_state.lock().unwrap(), VmState::Running { .. }) {
-            return Err(new_binder_exception(ExceptionCode::SERVICE_SPECIFIC, "VM is not running"));
+            return Err(Status::new_service_specific_error_str(-1, Some("VM is not running")));
         }
         let stream =
             VsockStream::connect_with_cid_port(self.instance.cid, port as u32).map_err(|e| {
-                new_binder_exception(
-                    ExceptionCode::SERVICE_SPECIFIC,
-                    format!("Failed to connect: {}", e),
+                Status::new_service_specific_error_str(
+                    -1,
+                    Some(format!("Failed to connect: {}", e)),
                 )
             })?;
         Ok(vsock_stream_to_pfd(stream))
@@ -1002,9 +1005,9 @@
 /// Converts a `&ParcelFileDescriptor` to a `File` by cloning the file.
 fn clone_file(file: &ParcelFileDescriptor) -> Result<File, Status> {
     file.as_ref().try_clone().map_err(|e| {
-        new_binder_exception(
+        Status::new_exception_str(
             ExceptionCode::BAD_PARCELABLE,
-            format!("Failed to clone File from ParcelFileDescriptor: {}", e),
+            Some(format!("Failed to clone File from ParcelFileDescriptor: {}", e)),
         )
     })
 }
@@ -1024,9 +1027,9 @@
 /// Parses the platform version requirement string.
 fn parse_platform_version_req(s: &str) -> Result<VersionReq, Status> {
     VersionReq::parse(s).map_err(|e| {
-        new_binder_exception(
+        Status::new_exception_str(
             ExceptionCode::BAD_PARCELABLE,
-            format!("Invalid platform version requirement {}: {}", s, e),
+            Some(format!("Invalid platform version requirement {}: {}", s, e)),
         )
     })
 }
@@ -1061,16 +1064,17 @@
         let cid = self.cid;
         if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
             info!("VM having CID {} started payload", cid);
-            vm.update_payload_state(PayloadState::Started)
-                .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))?;
+            vm.update_payload_state(PayloadState::Started).map_err(|e| {
+                Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+            })?;
             let stream = vm.stream.lock().unwrap().take();
             vm.callbacks.notify_payload_started(cid, stream);
             Ok(())
         } else {
             error!("notifyPayloadStarted is called from an unknown CID {}", cid);
-            Err(new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("cannot find a VM with CID {}", cid),
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
             ))
         }
     }
@@ -1079,15 +1083,16 @@
         let cid = self.cid;
         if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
             info!("VM having CID {} payload is ready", cid);
-            vm.update_payload_state(PayloadState::Ready)
-                .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))?;
+            vm.update_payload_state(PayloadState::Ready).map_err(|e| {
+                Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+            })?;
             vm.callbacks.notify_payload_ready(cid);
             Ok(())
         } else {
             error!("notifyPayloadReady is called from an unknown CID {}", cid);
-            Err(new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("cannot find a VM with CID {}", cid),
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
             ))
         }
     }
@@ -1096,15 +1101,16 @@
         let cid = self.cid;
         if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
             info!("VM having CID {} finished payload", cid);
-            vm.update_payload_state(PayloadState::Finished)
-                .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))?;
+            vm.update_payload_state(PayloadState::Finished).map_err(|e| {
+                Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+            })?;
             vm.callbacks.notify_payload_finished(cid, exit_code);
             Ok(())
         } else {
             error!("notifyPayloadFinished is called from an unknown CID {}", cid);
-            Err(new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("cannot find a VM with CID {}", cid),
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
             ))
         }
     }
@@ -1113,15 +1119,16 @@
         let cid = self.cid;
         if let Some(vm) = self.state.lock().unwrap().get_vm(cid) {
             info!("VM having CID {} encountered an error", cid);
-            vm.update_payload_state(PayloadState::Finished)
-                .map_err(|e| new_binder_exception(ExceptionCode::ILLEGAL_STATE, e.to_string()))?;
+            vm.update_payload_state(PayloadState::Finished).map_err(|e| {
+                Status::new_exception_str(ExceptionCode::ILLEGAL_STATE, Some(e.to_string()))
+            })?;
             vm.callbacks.notify_error(cid, error_code, message);
             Ok(())
         } else {
             error!("notifyError is called from an unknown CID {}", cid);
-            Err(new_binder_exception(
-                ExceptionCode::SERVICE_SPECIFIC,
-                format!("cannot find a VM with CID {}", cid),
+            Err(Status::new_service_specific_error_str(
+                -1,
+                Some(format!("cannot find a VM with CID {}", cid)),
             ))
         }
     }
diff --git a/virtualizationservice/src/crosvm.rs b/virtualizationservice/src/crosvm.rs
index 95c5c53..3e1a151 100644
--- a/virtualizationservice/src/crosvm.rs
+++ b/virtualizationservice/src/crosvm.rs
@@ -36,7 +36,7 @@
 use std::thread;
 use vsock::VsockStream;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::DeathReason::DeathReason;
-use android_system_virtualmachineservice::binder::Strong;
+use binder::Strong;
 use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
 
 const CROSVM_PATH: &str = "/apex/com.android.virt/bin/crosvm";
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index 7bfb531..3b0adb9 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -22,7 +22,7 @@
 
 use crate::aidl::{VirtualizationService, BINDER_SERVICE_IDENTIFIER, TEMPORARY_DIRECTORY};
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::BnVirtualizationService;
-use android_system_virtualizationservice::binder::{register_lazy_service, BinderFeatures, ProcessState};
+use binder::{register_lazy_service, BinderFeatures, ProcessState};
 use anyhow::Error;
 use log::{info, Level};
 use std::fs::{remove_dir_all, remove_file, read_dir};
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index c0153b9..42c51a1 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -19,9 +19,8 @@
     VirtualMachineAppConfig::VirtualMachineAppConfig,
     VirtualMachineRawConfig::VirtualMachineRawConfig,
 };
-use android_system_virtualizationservice::binder::ParcelFileDescriptor;
 use anyhow::{anyhow, bail, Context, Result};
-use binder::wait_for_interface;
+use binder::{wait_for_interface, ParcelFileDescriptor};
 use log::{info, warn};
 use microdroid_metadata::{ApexPayload, ApkPayload, Metadata};
 use microdroid_payload_config::{ApexConfig, VmPayloadConfig};
diff --git a/vm/Android.bp b/vm/Android.bp
index 2b83ca7..f9eac4d 100644
--- a/vm/Android.bp
+++ b/vm/Android.bp
@@ -11,6 +11,7 @@
     rustlibs: [
         "android.system.virtualizationservice-rust",
         "libanyhow",
+        "libbinder_rs",
         "libenv_logger",
         "liblibc",
         "liblog_rust",
diff --git a/vm/src/create_idsig.rs b/vm/src/create_idsig.rs
index fe7cd82..9a66228 100644
--- a/vm/src/create_idsig.rs
+++ b/vm/src/create_idsig.rs
@@ -15,7 +15,7 @@
 //! Command to create or update an idsig for APK
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
-use android_system_virtualizationservice::binder::ParcelFileDescriptor;
+use binder::ParcelFileDescriptor;
 use anyhow::{Context, Error};
 use std::fs::{File, OpenOptions};
 use std::path::Path;
diff --git a/vm/src/create_partition.rs b/vm/src/create_partition.rs
index 049c201..affa28e 100644
--- a/vm/src/create_partition.rs
+++ b/vm/src/create_partition.rs
@@ -16,7 +16,7 @@
 
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::IVirtualizationService::IVirtualizationService;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::PartitionType::PartitionType;
-use android_system_virtualizationservice::binder::ParcelFileDescriptor;
+use binder::ParcelFileDescriptor;
 use anyhow::{Context, Error};
 use std::convert::TryInto;
 use std::fs::OpenOptions;
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 60786ac..c421b04 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -22,8 +22,8 @@
     IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
     VirtualMachineAppConfig::DebugLevel::DebugLevel,
 };
-use android_system_virtualizationservice::binder::ProcessState;
 use anyhow::{Context, Error};
+use binder::ProcessState;
 use create_idsig::command_create_idsig;
 use create_partition::command_create_partition;
 use run::{command_run, command_run_app};
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 44eb27a..9bd7863 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -16,19 +16,13 @@
 
 use crate::create_partition::command_create_partition;
 use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
-    DeathReason::DeathReason,
-    IVirtualMachineCallback::{BnVirtualMachineCallback, IVirtualMachineCallback},
-    IVirtualizationService::IVirtualizationService,
-    PartitionType::PartitionType,
+    IVirtualizationService::IVirtualizationService, PartitionType::PartitionType,
     VirtualMachineAppConfig::DebugLevel::DebugLevel,
-    VirtualMachineAppConfig::VirtualMachineAppConfig,
-    VirtualMachineConfig::VirtualMachineConfig,
+    VirtualMachineAppConfig::VirtualMachineAppConfig, VirtualMachineConfig::VirtualMachineConfig,
     VirtualMachineState::VirtualMachineState,
 };
-use android_system_virtualizationservice::binder::{
-    BinderFeatures, Interface, ParcelFileDescriptor, Result as BinderResult,
-};
 use anyhow::{bail, Context, Error};
+use binder::ParcelFileDescriptor;
 use microdroid_payload_config::VmPayloadConfig;
 use std::fs::File;
 use std::io::{self, BufRead, BufReader};
@@ -197,10 +191,9 @@
         Some(duplicate_stdout()?)
     };
 
-    let vm = VmInstance::create(service, config, console, log).context("Failed to create VM")?;
-    let callback =
-        BnVirtualMachineCallback::new_binder(VirtualMachineCallback {}, BinderFeatures::default());
-    vm.vm.registerCallback(&callback)?;
+    let callback = Box::new(Callback {});
+    let vm = VmInstance::create(service, config, console, log, Some(callback))
+        .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
 
     println!(
@@ -246,20 +239,13 @@
     Ok(config.extra_apks.into_iter().map(|x| x.path).collect())
 }
 
-#[derive(Debug)]
-struct VirtualMachineCallback {}
+struct Callback {}
 
-impl Interface for VirtualMachineCallback {}
-
-impl IVirtualMachineCallback for VirtualMachineCallback {
-    fn onPayloadStarted(
-        &self,
-        _cid: i32,
-        stream: Option<&ParcelFileDescriptor>,
-    ) -> BinderResult<()> {
+impl vmclient::VmCallback for Callback {
+    fn on_payload_started(&self, _cid: i32, stream: Option<&File>) {
         // Show the output of the payload
         if let Some(stream) = stream {
-            let mut reader = BufReader::new(stream.as_ref());
+            let mut reader = BufReader::new(stream);
             loop {
                 let mut s = String::new();
                 match reader.read_line(&mut s) {
@@ -269,31 +255,18 @@
                 };
             }
         }
-        Ok(())
     }
 
-    fn onPayloadReady(&self, _cid: i32) -> BinderResult<()> {
+    fn on_payload_ready(&self, _cid: i32) {
         eprintln!("payload is ready");
-        Ok(())
     }
 
-    fn onPayloadFinished(&self, _cid: i32, exit_code: i32) -> BinderResult<()> {
+    fn on_payload_finished(&self, _cid: i32, exit_code: i32) {
         eprintln!("payload finished with exit code {}", exit_code);
-        Ok(())
     }
 
-    fn onError(&self, _cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
+    fn on_error(&self, _cid: i32, error_code: i32, message: &str) {
         eprintln!("VM encountered an error: code={}, message={}", error_code, message);
-        Ok(())
-    }
-
-    fn onRamdump(&self, _cid: i32, _stream: &ParcelFileDescriptor) -> BinderResult<()> {
-        // Do nothing. We get ramdump from the vmclient library.
-        Ok(())
-    }
-
-    fn onDied(&self, _cid: i32, _reason: DeathReason) -> BinderResult<()> {
-        Ok(())
     }
 }
 
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
index 4928846..fd6eb8c 100644
--- a/vmbase/example/tests/test.rs
+++ b/vmbase/example/tests/test.rs
@@ -63,7 +63,7 @@
     });
     let console = duplicate_stdout()?;
     let log = duplicate_stdout()?;
-    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log))
+    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log), None)
         .context("Failed to create VM")?;
     vm.start().context("Failed to start VM")?;
     info!("Started example VM.");
diff --git a/vmclient/src/lib.rs b/vmclient/src/lib.rs
index b3bb635..129e6c3 100644
--- a/vmclient/src/lib.rs
+++ b/vmclient/src/lib.rs
@@ -64,6 +64,32 @@
     _death_recipient: DeathRecipient,
 }
 
+/// A trait to be implemented by clients to handle notification of significant changes to the VM
+/// state. Default implementations of all functions are provided so clients only need to handle the
+/// notifications they are interested in.
+#[allow(unused_variables)]
+pub trait VmCallback {
+    /// Called when the payload has been started within the VM. If present, `stream` is connected
+    /// to the stdin/stdout of the payload.
+    fn on_payload_started(&self, cid: i32, stream: Option<&File>) {}
+
+    /// Callend when the payload has notified Virtualization Service that it is ready to serve
+    /// clients.
+    fn on_payload_ready(&self, cid: i32) {}
+
+    /// Called when the payload has exited in the VM. `exit_code` is the exit code of the payload
+    /// process.
+    fn on_payload_finished(&self, cid: i32, exit_code: i32) {}
+
+    /// Called when an error has occurred in the VM. The `error_code` and `message` may give
+    /// further details.
+    fn on_error(&self, cid: i32, error_code: i32, message: &str) {}
+
+    /// Called when the VM has exited, all resources have been freed, and any logs have been
+    /// written. `death_reason` gives an indication why the VM exited.
+    fn on_died(&self, cid: i32, death_reason: DeathReason) {}
+}
+
 impl VmInstance {
     /// Creates (but doesn't start) a new VM with the given configuration.
     pub fn create(
@@ -71,6 +97,7 @@
         config: &VirtualMachineConfig,
         console: Option<File>,
         log: Option<File>,
+        callback: Option<Box<dyn VmCallback + Send + Sync>>,
     ) -> BinderResult<Self> {
         let console = console.map(ParcelFileDescriptor::new);
         let log = log.map(ParcelFileDescriptor::new);
@@ -82,7 +109,7 @@
         // Register callback before starting VM, in case it dies immediately.
         let state = Arc::new(Monitor::new(VmState::default()));
         let callback = BnVirtualMachineCallback::new_binder(
-            VirtualMachineCallback { state: state.clone() },
+            VirtualMachineCallback { state: state.clone(), client_callback: callback },
             BinderFeatures::default(),
         );
         vm.registerCallback(&callback)?;
@@ -219,9 +246,21 @@
     }
 }
 
-#[derive(Debug)]
 struct VirtualMachineCallback {
     state: Arc<Monitor<VmState>>,
+    client_callback: Option<Box<dyn VmCallback + Send + Sync>>,
+}
+
+impl Debug for VirtualMachineCallback {
+    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
+        fmt.debug_struct("VirtualMachineCallback")
+            .field("state", &self.state)
+            .field(
+                "client_callback",
+                &if self.client_callback.is_some() { "Some(...)" } else { "None" },
+            )
+            .finish()
+    }
 }
 
 impl Interface for VirtualMachineCallback {}
@@ -229,25 +268,37 @@
 impl IVirtualMachineCallback for VirtualMachineCallback {
     fn onPayloadStarted(
         &self,
-        _cid: i32,
-        _stream: Option<&ParcelFileDescriptor>,
+        cid: i32,
+        stream: Option<&ParcelFileDescriptor>,
     ) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::STARTED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_started(cid, stream.map(ParcelFileDescriptor::as_ref));
+        }
         Ok(())
     }
 
-    fn onPayloadReady(&self, _cid: i32) -> BinderResult<()> {
+    fn onPayloadReady(&self, cid: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::READY);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_ready(cid);
+        }
         Ok(())
     }
 
-    fn onPayloadFinished(&self, _cid: i32, _exit_code: i32) -> BinderResult<()> {
+    fn onPayloadFinished(&self, cid: i32, exit_code: i32) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::FINISHED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_payload_finished(cid, exit_code);
+        }
         Ok(())
     }
 
-    fn onError(&self, _cid: i32, _error_code: i32, _message: &str) -> BinderResult<()> {
+    fn onError(&self, cid: i32, error_code: i32, message: &str) -> BinderResult<()> {
         self.state.notify_state(VirtualMachineState::FINISHED);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_error(cid, error_code, message);
+        }
         Ok(())
     }
 
@@ -257,8 +308,12 @@
         Ok(())
     }
 
-    fn onDied(&self, _cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
-        self.state.notify_death(reason.into());
+    fn onDied(&self, cid: i32, reason: AidlDeathReason) -> BinderResult<()> {
+        let reason = reason.into();
+        self.state.notify_death(reason);
+        if let Some(ref callback) = self.client_callback {
+            callback.on_died(cid, reason);
+        }
         Ok(())
     }
 }