Add com.android.virt.accessor_demo

This provides a reference IAccessor implementation.

Test: atest vm_accessor_test
Bug: 349578050
Change-Id: I956e8e7706c43c8e672ec669d4c8912f09635061
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 3651dfa..762c3bf 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -64,6 +64,9 @@
     {
       "name": "AvfRkpdAppGoogleIntegrationTests",
       "keywords": ["internal"]
+    },
+    {
+      "name": "vm_accessor_test"
     }
   ],
   "ferrochrome-postsubmit": [
diff --git a/demo_accessor/README.md b/demo_accessor/README.md
new file mode 100644
index 0000000..a3959a5
--- /dev/null
+++ b/demo_accessor/README.md
@@ -0,0 +1,40 @@
+# Demo for serving a service in a VM
+
+You can implement a service in a VM, and let client in the Android can use it
+as if it's in the Android. To do so, implement IAccessor.
+
+IAccessor allows AIDL service in a VM can be accessed via servicemanager.
+To do so, VM owners should also provide IAccessor implementation. servicemanager
+will connect to the IAccessor and get the binder of the service in a VM with it.
+
+com.android.virt.accessor_demo apex contains the minimum setup for IAccessor as
+follows:
+  - accessor_demo: Sample implementation of IAccessor, which is expected to
+      launch VM and returns the Vsock connection of service in the VM.
+  - AccessorVmApp: Sample app that conatins VM payload. Provides the actual
+      implementation of service in a VM.
+
+## Build
+
+You need to do envsetup.sh
+```shell
+m com.android.virt.accessor_demo
+```
+
+## Install (requires userdebug build)
+
+For very first install,
+
+```shell
+adb remount -R || adb wait-for-device  # Remount to push apex to /system_ext
+adb root && adb remount                # Ensure it's rebooted.
+adb push $ANDROID_PRODUCT_OUT/system_ext/com.android.virt.accessor_demo.apex /system_ext/apex
+adb reboot && adb wait-for-device      # Ensure that newly pushed apex at /system_ext is installed
+```
+
+Once it's installed, you can simply use `adb install` for update.
+
+```shell
+adb install $ANDROID_PRODUCT_OUT/system_ext/com.android.virt.accessor_demo.apex
+adb reboot
+```
diff --git a/demo_accessor/accessor/Android.bp b/demo_accessor/accessor/Android.bp
new file mode 100644
index 0000000..d9d1026
--- /dev/null
+++ b/demo_accessor/accessor/Android.bp
@@ -0,0 +1,31 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "accessor_demo",
+    crate_name: "accessor_demo",
+    srcs: ["src/main.rs"],
+    edition: "2021",
+    prefer_rlib: true,
+    defaults: ["avf_build_flags_rust"], // for reading llvm_changes
+    apex_available: [
+        "com.android.virt.accessor_demo",
+    ],
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "android.os.accessor-rust",
+        "libanyhow",
+        "libandroid_logger",
+        "libbinder_rs",
+        "libenv_logger",
+        "libglob",
+        "libhypervisor_props",
+        "liblibc",
+        "liblog_rust",
+        "libmicrodroid_payload_config",
+        "librand",
+        "libvmconfig",
+        "libvmclient",
+    ],
+}
diff --git a/demo_accessor/accessor/src/accessor.rs b/demo_accessor/accessor/src/accessor.rs
new file mode 100644
index 0000000..6a9ced6
--- /dev/null
+++ b/demo_accessor/accessor/src/accessor.rs
@@ -0,0 +1,52 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! IAcessor implementation.
+//! TODO: Keep this in proper places, so other pVMs can use this.
+//! TODO: Allows to customize VMs for launching. (e.g. port, ...)
+
+use android_os_accessor::aidl::android::os::IAccessor::IAccessor;
+use binder::{self, Interface, ParcelFileDescriptor};
+use log::info;
+use std::time::Duration;
+use vmclient::VmInstance;
+
+// Note: Do not use LazyServiceGuard here, to make this service and VM are quit
+//       when nobody references it.
+// TODO(b/353492849): Do not use IAccessor directly.
+#[derive(Debug)]
+pub struct Accessor {
+    // Note: we can't simply keep reference by specifying lifetime to Accessor,
+    //       because 'trait Interface' requires 'static.
+    vm: VmInstance,
+    port: i32,
+}
+
+impl Accessor {
+    pub fn new(vm: VmInstance, port: i32) -> Self {
+        Self { vm, port }
+    }
+}
+
+impl Interface for Accessor {}
+
+impl IAccessor for Accessor {
+    fn addConnection(&self) -> binder::Result<ParcelFileDescriptor> {
+        self.vm.wait_until_ready(Duration::from_secs(10)).unwrap();
+
+        info!("VM is ready. Connecting to service via port {}", self.port);
+
+        self.vm.vm.connectVsock(self.port)
+    }
+}
diff --git a/demo_accessor/accessor/src/main.rs b/demo_accessor/accessor/src/main.rs
new file mode 100644
index 0000000..27ce415
--- /dev/null
+++ b/demo_accessor/accessor/src/main.rs
@@ -0,0 +1,55 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Android VM control tool.
+
+mod accessor;
+mod run;
+
+use accessor::Accessor;
+use android_os_accessor::aidl::android::os::IAccessor::BnAccessor;
+use anyhow::Error;
+use anyhow::{anyhow, bail};
+use binder::{BinderFeatures, ProcessState};
+use log::info;
+use run::run_vm;
+
+// Private contract between IAccessor impl and VM service.
+const PORT: i32 = 5678;
+
+// MUST match with VINTF and init.rc
+// TODO(b/354632613): Get this from VINTF
+const SERVICE_NAME: &str = "android.os.IAccessor/IAccessorVmService/default";
+
+fn main() -> Result<(), Error> {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("accessor_demo")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+
+    let vm = run_vm()?;
+
+    // If you want to serve multiple services in a VM, then register Accessor impls multiple times.
+    let accessor = Accessor::new(vm, PORT);
+    let accessor_binder = BnAccessor::new_binder(accessor, BinderFeatures::default());
+    binder::register_lazy_service(SERVICE_NAME, accessor_binder.as_binder()).map_err(|e| {
+        anyhow!("Failed to register lazy service, service={SERVICE_NAME}, err={e:?}",)
+    })?;
+    info!("service {SERVICE_NAME} is registered as lazy service");
+
+    ProcessState::join_thread_pool();
+
+    bail!("Thread pool unexpectedly ended")
+}
diff --git a/demo_accessor/accessor/src/run.rs b/demo_accessor/accessor/src/run.rs
new file mode 100644
index 0000000..03aa80d
--- /dev/null
+++ b/demo_accessor/accessor/src/run.rs
@@ -0,0 +1,175 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Command to run a VM.
+
+use android_system_virtualizationservice::aidl::android::system::virtualizationservice::{
+    IVirtualizationService::IVirtualizationService,
+    PartitionType::PartitionType,
+    VirtualMachineAppConfig::{DebugLevel::DebugLevel, Payload::Payload, VirtualMachineAppConfig},
+    VirtualMachineConfig::VirtualMachineConfig,
+    VirtualMachinePayloadConfig::VirtualMachinePayloadConfig,
+};
+use anyhow::{bail, Context, Error};
+use binder::{ParcelFileDescriptor, Strong};
+use glob::glob;
+use log::{error, info};
+use rand::{distributions::Alphanumeric, Rng};
+use std::fs;
+use std::fs::File;
+use std::io;
+use std::os::unix::io::{AsRawFd, FromRawFd};
+use std::path::PathBuf;
+use vmclient::{ErrorCode, VmInstance};
+use vmconfig::open_parcel_file;
+
+// These are private contract between IAccessor impl and VM service.
+const PAYLOAD_BINARY_NAME: &str = "libaccessor_vm_payload.so";
+const VM_OS_NAME: &str = "microdroid";
+
+const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
+
+fn get_service() -> Result<Strong<dyn IVirtualizationService>, Error> {
+    let virtmgr =
+        vmclient::VirtualizationService::new().context("Failed to spawn VirtualizationService")?;
+    virtmgr.connect().context("Failed to connect to VirtualizationService")
+}
+
+fn find_vm_apk_path() -> Result<PathBuf, Error> {
+    const GLOB_PATTERN: &str = "/apex/com.android.virt.accessor_demo/app/**/AccessorVmApp*.apk";
+    let mut entries: Vec<PathBuf> =
+        glob(GLOB_PATTERN).context("failed to glob")?.filter_map(|e| e.ok()).collect();
+    if entries.len() > 1 {
+        bail!("Found more than one apk matching {}", GLOB_PATTERN);
+    }
+    if let Some(path) = entries.pop() {
+        info!("Found accessor apk at {path:?}");
+        Ok(path)
+    } else {
+        bail!("No apks match {}", GLOB_PATTERN)
+    }
+}
+
+fn create_work_dir() -> Result<PathBuf, Error> {
+    let s: String =
+        rand::thread_rng().sample_iter(&Alphanumeric).take(17).map(char::from).collect();
+    let work_dir = PathBuf::from("/data/local/tmp/microdroid").join(s);
+    info!("creating work dir {}", work_dir.display());
+    fs::create_dir_all(&work_dir).context("failed to mkdir")?;
+    Ok(work_dir)
+}
+
+/// Run a VM with Microdroid
+pub fn run_vm() -> Result<VmInstance, Error> {
+    let service = get_service()?;
+
+    let apk = File::open(find_vm_apk_path()?).context("Failed to open APK file")?;
+    let apk_fd = ParcelFileDescriptor::new(apk);
+
+    let work_dir = create_work_dir()?;
+    info!("work dir: {}", work_dir.display());
+
+    let idsig =
+        File::create_new(work_dir.join("apk.idsig")).context("Failed to create idsig file")?;
+    let idsig_fd = ParcelFileDescriptor::new(idsig);
+    service.createOrUpdateIdsigFile(&apk_fd, &idsig_fd)?;
+
+    let instance_img_path = work_dir.join("instance.img");
+    let instance_img =
+        File::create_new(&instance_img_path).context("Failed to create instance.img file")?;
+    service.initializeWritablePartition(
+        &ParcelFileDescriptor::new(instance_img),
+        INSTANCE_FILE_SIZE.try_into()?,
+        PartitionType::ANDROID_VM_INSTANCE,
+    )?;
+    info!("created instance image at: {instance_img_path:?}");
+
+    let instance_id = if cfg!(llpvm_changes) {
+        let id = service.allocateInstanceId().context("Failed to allocate instance_id")?;
+        fs::write(work_dir.join("instance_id"), id)?;
+        id
+    } else {
+        // if llpvm feature flag is disabled, instance_id is not used.
+        [0u8; 64]
+    };
+
+    let payload = Payload::PayloadConfig(VirtualMachinePayloadConfig {
+        payloadBinaryName: PAYLOAD_BINARY_NAME.to_owned(),
+        extraApks: Default::default(),
+    });
+
+    let vm_config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
+        name: String::from("AccessorVm"),
+        apk: apk_fd.into(),
+        idsig: idsig_fd.into(),
+        extraIdsigs: Default::default(),
+        instanceImage: open_parcel_file(&instance_img_path, true /* writable */)?.into(),
+        instanceId: instance_id,
+        payload,
+        osName: VM_OS_NAME.to_owned(),
+        debugLevel: DebugLevel::FULL,
+        ..Default::default()
+    });
+
+    info!("creating VM");
+    let vm = VmInstance::create(
+        service.as_ref(),
+        &vm_config,
+        Some(duplicate_fd(io::stdout())?), /* console_out */
+        None,                              /* console_in */
+        Some(duplicate_fd(io::stdout())?), /* log */
+        Some(Box::new(Callback {})),
+    )
+    .context("Failed to create VM")?;
+    vm.start().context("Failed to start VM")?;
+
+    info!("started IAccessor VM with CID {}", vm.cid());
+
+    Ok(vm)
+}
+
+struct Callback {}
+
+impl vmclient::VmCallback for Callback {
+    fn on_payload_started(&self, _cid: i32) {
+        info!("payload started");
+    }
+
+    fn on_payload_ready(&self, _cid: i32) {
+        info!("payload is ready");
+    }
+
+    fn on_payload_finished(&self, _cid: i32, exit_code: i32) {
+        info!("payload finished with exit code {}", exit_code);
+    }
+
+    fn on_error(&self, _cid: i32, error_code: ErrorCode, message: &str) {
+        error!("VM encountered an error: code={:?}, message={}", error_code, message);
+    }
+}
+
+/// Safely duplicate the file descriptor.
+fn duplicate_fd<T: AsRawFd>(file: T) -> io::Result<File> {
+    let fd = file.as_raw_fd();
+    // SAFETY: This just duplicates a file descriptor which we know to be valid, and we check for an
+    // an error.
+    let dup_fd = unsafe { libc::dup(fd) };
+    if dup_fd < 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        // SAFETY: We have just duplicated the file descriptor so we own it, and `from_raw_fd` takes
+        // ownership of it.
+        Ok(unsafe { File::from_raw_fd(dup_fd) })
+    }
+}
diff --git a/demo_accessor/accessor_vm/Android.bp b/demo_accessor/accessor_vm/Android.bp
new file mode 100644
index 0000000..bbd15bd
--- /dev/null
+++ b/demo_accessor/accessor_vm/Android.bp
@@ -0,0 +1,30 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+android_app {
+    name: "AccessorVmApp",
+    installable: true,
+    jni_libs: ["libaccessor_vm_payload"],
+    jni_uses_platform_apis: true,
+    use_embedded_native_libs: true,
+    sdk_version: "system_current",
+    compile_multilib: "first",
+    apex_available: ["com.android.virt.accessor_demo"],
+}
+
+rust_ffi {
+    name: "libaccessor_vm_payload",
+    crate_name: "accessor_vm_payload",
+    defaults: ["avf_build_flags_rust"],
+    srcs: ["src/main.rs"],
+    prefer_rlib: true,
+    rustlibs: [
+        "com.android.virt.accessor_demo.vm_service-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "liblog_rust",
+        "libvm_payload_rs",
+    ],
+    apex_available: ["com.android.virt.accessor_demo"],
+}
diff --git a/demo_accessor/accessor_vm/AndroidManifest.xml b/demo_accessor/accessor_vm/AndroidManifest.xml
new file mode 100644
index 0000000..429e08a
--- /dev/null
+++ b/demo_accessor/accessor_vm/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+      package="com.android.virtualization.accessor_demo">
+     <application android:hasCode="false"/>
+</manifest>
diff --git a/demo_accessor/accessor_vm/assets/config.json b/demo_accessor/accessor_vm/assets/config.json
new file mode 100644
index 0000000..f921ec1
--- /dev/null
+++ b/demo_accessor/accessor_vm/assets/config.json
@@ -0,0 +1,10 @@
+{
+    "os": {
+        "name": "microdroid"
+    },
+    "task": {
+        "type": "microdroid_launcher",
+        "command": "accessor_vm.so"
+    },
+    "export_tombstones": true
+}
diff --git a/demo_accessor/accessor_vm/src/main.rs b/demo_accessor/accessor_vm/src/main.rs
new file mode 100644
index 0000000..bd83cc1
--- /dev/null
+++ b/demo_accessor/accessor_vm/src/main.rs
@@ -0,0 +1,64 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! VM with the simplest service for IAccessor demo
+
+use anyhow::Result;
+use com_android_virt_accessor_demo_vm_service::{
+    aidl::com::android::virt::accessor_demo::vm_service::IAccessorVmService::{
+        BnAccessorVmService, IAccessorVmService,
+    },
+    binder::{self, BinderFeatures, Interface, Strong},
+};
+use log::{error, info};
+
+// Private contract between IAccessor impl and VM service.
+const PORT: u32 = 5678;
+
+vm_payload::main!(main);
+
+// Entry point of the Service VM client.
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("accessor_vm")
+            .with_max_level(log::LevelFilter::Debug),
+    );
+    if let Err(e) = try_main() {
+        error!("failed with {:?}", e);
+        std::process::exit(1);
+    }
+}
+
+fn try_main() -> Result<()> {
+    info!("Starting stub payload for IAccessor demo");
+
+    vm_payload::run_single_vsock_service(AccessorVmService::new_binder(), PORT)
+}
+
+struct AccessorVmService {}
+
+impl Interface for AccessorVmService {}
+
+impl AccessorVmService {
+    fn new_binder() -> Strong<dyn IAccessorVmService> {
+        BnAccessorVmService::new_binder(AccessorVmService {}, BinderFeatures::default())
+    }
+}
+
+impl IAccessorVmService for AccessorVmService {
+    fn add(&self, a: i32, b: i32) -> binder::Result<i32> {
+        Ok(a + b)
+    }
+}
diff --git a/demo_accessor/aidl/Android.bp b/demo_accessor/aidl/Android.bp
new file mode 100644
index 0000000..0078608
--- /dev/null
+++ b/demo_accessor/aidl/Android.bp
@@ -0,0 +1,20 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "com.android.virt.accessor_demo.vm_service",
+    srcs: ["**/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            gen_rpc: true,
+        },
+        rust: {
+            enabled: true,
+            apex_available: [
+                "com.android.virt.accessor_demo",
+            ],
+        },
+    },
+}
diff --git a/demo_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl b/demo_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl
new file mode 100644
index 0000000..29bf979
--- /dev/null
+++ b/demo_accessor/aidl/com/android/virt/accessor_demo/vm_service/IAccessorVmService.aidl
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.virt.accessor_demo.vm_service;
+
+/** {@hide} */
+// TODO(b/349578050): Add more methods that take or return another binder.
+interface IAccessorVmService {
+    int add(int a, int b);
+}
diff --git a/demo_accessor/apex/Android.bp b/demo_accessor/apex/Android.bp
new file mode 100644
index 0000000..e954572
--- /dev/null
+++ b/demo_accessor/apex/Android.bp
@@ -0,0 +1,52 @@
+// Copyright 2024 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+// apex_test allows to skips apex_available checks for dependencies.
+// However, real apex should add itself to apex_available for all dependencies.
+apex_test {
+    name: "com.android.virt.accessor_demo",
+    manifest: "manifest.json",
+    file_contexts: "accessor_demo-file_contexts",
+
+    // You probably need your own key
+    key: "com.android.virt.key",
+
+    updatable: false,
+    future_updatable: false,
+    platform_apis: true,
+    system_ext_specific: true,
+
+    binaries: ["accessor_demo"],
+    apps: ["AccessorVmApp"],
+    prebuilts: [
+        "accessor_demo.init.rc",
+        "accessor_demo.xml",
+    ],
+}
+
+prebuilt_etc {
+    name: "accessor_demo.init.rc",
+    src: "accessor_demo.init.rc",
+    installable: false,
+}
+
+prebuilt_etc {
+    name: "accessor_demo.xml",
+    src: "accessor_demo.xml",
+    sub_dir: "vintf",
+    installable: false,
+}
diff --git a/demo_accessor/apex/accessor_demo-file_contexts b/demo_accessor/apex/accessor_demo-file_contexts
new file mode 100644
index 0000000..2007157
--- /dev/null
+++ b/demo_accessor/apex/accessor_demo-file_contexts
@@ -0,0 +1,3 @@
+# TODO: Give proper label
+(/.*)?                         u:object_r:system_file:s0
+/bin/accessor_demo             u:object_r:virtualizationservice_exec:s0
diff --git a/demo_accessor/apex/accessor_demo.init.rc b/demo_accessor/apex/accessor_demo.init.rc
new file mode 100644
index 0000000..f3dfae9
--- /dev/null
+++ b/demo_accessor/apex/accessor_demo.init.rc
@@ -0,0 +1,20 @@
+# Copyright 2024 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Register this as lazy service, so IAccessor and its VM is only running while binder is in use.
+service accessor_demo /apex/com.android.virt.accessor_demo/bin/accessor_demo
+    disabled
+    oneshot
+    # MUST match with VINTF and accessor/src/main.rs
+    interface aidl android.os.IAccessor/IAccessorVmService/default
diff --git a/demo_accessor/apex/accessor_demo.xml b/demo_accessor/apex/accessor_demo.xml
new file mode 100644
index 0000000..e9df3df
--- /dev/null
+++ b/demo_accessor/apex/accessor_demo.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest version="1.0" type="framework">
+    <hal format="aidl">
+        <name>com.android.virt.accessor_demo.vm_service</name>
+        <version>1</version>
+        <fqname>IAccessorVmService/default</fqname>
+        <!-- MUST match with init.rc and accessor/src/main.rs -->
+        <accessor>android.os.IAccessor/IAccessorVmService/default</accessor>
+    </hal>
+</manifest>
\ No newline at end of file
diff --git a/demo_accessor/apex/manifest.json b/demo_accessor/apex/manifest.json
new file mode 100644
index 0000000..a09523a
--- /dev/null
+++ b/demo_accessor/apex/manifest.json
@@ -0,0 +1,4 @@
+{
+  "name": "com.android.virt.accessor_demo",
+  "version": 1
+}
diff --git a/demo_accessor/test/Android.bp b/demo_accessor/test/Android.bp
new file mode 100644
index 0000000..71746c7
--- /dev/null
+++ b/demo_accessor/test/Android.bp
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package {
+    default_team: "trendy_team_virtualization",
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_test {
+    name: "vm_accessor_test",
+    srcs: ["src/test.rs"],
+    defaults: [
+        "rdroidtest.defaults",
+    ],
+    test_suites: [
+        "general-tests",
+    ],
+    test_config: "AndroidTest.xml",
+    rustlibs: [
+        "com.android.virt.accessor_demo.vm_service-rust",
+        "libbinder_rs",
+        "liblog_rust",
+    ],
+    data: [":com.android.virt.accessor_demo"],
+    compile_multilib: "first",
+}
diff --git a/demo_accessor/test/AndroidTest.xml b/demo_accessor/test/AndroidTest.xml
new file mode 100644
index 0000000..4cbf9ec
--- /dev/null
+++ b/demo_accessor/test/AndroidTest.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright 2024 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Accessor demo test">
+  <option name="config-descriptor:metadata" key="parameter" value="not_instant_app" />
+  <option name="config-descriptor:metadata" key="parameter" value="not_multi_abi" />
+  <option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+
+  <!-- Install APEX begins -->
+  <!-- Step 0: adb reboot, so PushFilePreparer can remount system if needed -->
+  <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer">
+    <option name="force-root" value="true"/>
+  </target_preparer>
+  <!-- Step 1: Push for the very first install. -->
+  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+    <option name="abort-on-push-failure" value="true" />
+    <!-- Disclaimer: 'remount-system' remounts all partitions (adb remount),
+         but do so after checking the verity of /system partition.
+         This works for now, but may misbehave in the future. -->
+    <option name="remount-system" value="true" />
+    <option name="push-file" key="com.android.virt.accessor_demo.apex" value="/system_ext/apex/com.android.virt.accessor_demo.apex" />
+  </target_preparer>
+  <!-- Step 2: Reboot for pushed APEX to be installed. -->
+  <target_preparer class="com.android.tradefed.targetprep.RebootTargetPreparer" />
+
+  <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" />
+  <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+    <option name="abort-on-push-failure" value="true" />
+    <option name="push-file" key="vm_accessor_test" value="/data/local/tmp/vm_accessor_test" />
+  </target_preparer>
+
+  <!-- TODO(b/346763236): Remove this -->
+  <target_preparer class="com.android.tradefed.targetprep.DisableSELinuxTargetPreparer" />
+
+  <test class="com.android.tradefed.testtype.rust.RustBinaryTest" >
+    <option name="test-device-path" value="/data/local/tmp" />
+    <option name="module-name" value="vm_accessor_test" />
+  </test>
+</configuration>
diff --git a/demo_accessor/test/src/test.rs b/demo_accessor/test/src/test.rs
new file mode 100644
index 0000000..d521acf
--- /dev/null
+++ b/demo_accessor/test/src/test.rs
@@ -0,0 +1,85 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Test end-to-end IAccessor implementation with accessor_demo.
+
+use com_android_virt_accessor_demo_vm_service::aidl::com::android::virt::accessor_demo::vm_service::IAccessorVmService::IAccessorVmService;
+use binder::{Strong, ProcessState};
+use rdroidtest::rdroidtest;
+
+const VM_SERVICE: &str = "com.android.virt.accessor_demo.vm_service.IAccessorVmService/default";
+
+fn init() {
+    ProcessState::set_thread_pool_max_thread_count(5);
+    ProcessState::start_thread_pool();
+}
+
+fn wait_for_interface() -> Strong<dyn IAccessorVmService> {
+    binder::wait_for_interface(VM_SERVICE).unwrap()
+}
+
+fn get_interface() -> Strong<dyn IAccessorVmService> {
+    binder::get_interface(VM_SERVICE).unwrap()
+}
+
+fn check_interface() -> Strong<dyn IAccessorVmService> {
+    binder::check_interface(VM_SERVICE).unwrap()
+}
+
+#[rdroidtest]
+fn test_wait_for_interface() {
+    init();
+
+    let service = wait_for_interface();
+    let sum = service.add(11, 12).unwrap();
+
+    assert_eq!(sum, 23);
+}
+
+#[rdroidtest]
+fn test_wait_for_interface_twice() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = wait_for_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+#[rdroidtest]
+fn test_wait_and_get_interface() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = get_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+#[rdroidtest]
+fn test_wait_and_check_interface() {
+    init();
+
+    let service1 = wait_for_interface();
+    let service2 = check_interface();
+
+    assert_eq!(service1.add(11, 12).unwrap(), 23);
+    assert_eq!(service2.add(11, 12).unwrap(), 23);
+}
+
+rdroidtest::test_main!();