Merge "Add VirtualMachineState enum rather than isRunning bool."
diff --git a/apex/Android.bp b/apex/Android.bp
index 0e2d2d4..c568ae2 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -50,6 +50,9 @@
     java_libs: [
         "android.system.virtualmachine",
     ],
+    jni_libs: [
+        "libvirtualmachine_jni",
+    ],
     apps: [
         "android.system.virtualmachine.res",
     ],
diff --git a/demo/Android.bp b/demo/Android.bp
index 77049de..749ca90 100644
--- a/demo/Android.bp
+++ b/demo/Android.bp
@@ -9,6 +9,7 @@
     static_libs: [
         "androidx-constraintlayout_constraintlayout",
         "androidx.appcompat_appcompat",
+        "com.android.microdroid.testservice-java",
         "com.google.android.material_material",
     ],
     libs: [
@@ -18,4 +19,5 @@
     platform_apis: true,
     use_embedded_native_libs: true,
     v4_signature: true,
+    certificate: "platform",
 }
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index 6a46f73..ce21fdf 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -18,7 +18,9 @@
 
 import android.app.Application;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
 import android.system.virtualmachine.VirtualMachineConfig;
@@ -38,6 +40,8 @@
 import androidx.lifecycle.Observer;
 import androidx.lifecycle.ViewModelProvider;
 
+import com.android.microdroid.testservice.ITestService;
+
 import java.io.BufferedReader;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -45,6 +49,7 @@
 import java.io.InputStreamReader;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 /**
  * This app is to demonstrate the use of APIs in the android.system.virtualmachine library.
@@ -141,7 +146,7 @@
         public void run(boolean debug) {
             // Create a VM and run it.
             // TODO(jiyong): remove the call to idsigPath
-            mExecutorService = Executors.newFixedThreadPool(2);
+            mExecutorService = Executors.newFixedThreadPool(3);
 
             VirtualMachineCallback callback =
                     new VirtualMachineCallback() {
@@ -177,9 +182,55 @@
 
                         @Override
                         public void onPayloadReady(VirtualMachine vm) {
-                            // This check doesn't 100% prevent race condition, but is fine for demo.
-                            if (!mService.isShutdown()) {
-                                mPayloadOutput.postValue("(Payload is ready)");
+                            // This check doesn't 100% prevent race condition or UI hang.
+                            // However, it's fine for demo.
+                            if (mService.isShutdown()) {
+                                return;
+                            }
+                            mPayloadOutput.postValue("(Payload is ready. Testing VM service...)");
+
+                            Future<IBinder> service;
+                            try {
+                                service = vm.connectToVsockServer(ITestService.SERVICE_PORT);
+                            } catch (VirtualMachineException e) {
+                                mPayloadOutput.postValue(
+                                        String.format(
+                                                "(Exception while connecting VM's binder"
+                                                        + " service: %s)",
+                                                e.getMessage()));
+                                return;
+                            }
+
+                            mService.execute(() -> testVMService(service));
+                        }
+
+                        private void testVMService(Future<IBinder> service) {
+                            IBinder binder;
+                            try {
+                                binder = service.get();
+                            } catch (Exception e) {
+                                if (!Thread.interrupted()) {
+                                    mPayloadOutput.postValue(
+                                            String.format(
+                                                    "(VM service connection failed: %s)",
+                                                    e.getMessage()));
+                                }
+                                return;
+                            }
+
+                            try {
+                                ITestService testService = ITestService.Stub.asInterface(binder);
+                                int ret = testService.addInteger(123, 456);
+                                mPayloadOutput.postValue(
+                                        String.format(
+                                                "(VM payload service: %d + %d = %d)",
+                                                123, 456, ret));
+                            } catch (RemoteException e) {
+                                mPayloadOutput.postValue(
+                                        String.format(
+                                                "(Exception while testing VM's binder service:"
+                                                        + " %s)",
+                                                e.getMessage()));
                             }
                         }
 
diff --git a/javalib/AndroidManifest.xml b/javalib/AndroidManifest.xml
index 21857f8..f96b39f 100644
--- a/javalib/AndroidManifest.xml
+++ b/javalib/AndroidManifest.xml
@@ -18,7 +18,7 @@
   package="com.android.virtualmachine.res">
 
   <permission android:name="android.permission.MANAGE_VIRTUAL_MACHINE"
-      android:protectionLevel="normal" />
+      android:protectionLevel="signature" />
 
   <permission android:name="android.permission.DEBUG_VIRTUAL_MACHINE"
       android:protectionLevel="signature" />
diff --git a/javalib/jni/Android.bp b/javalib/jni/Android.bp
new file mode 100644
index 0000000..e141297
--- /dev/null
+++ b/javalib/jni/Android.bp
@@ -0,0 +1,12 @@
+cc_library_shared {
+    name: "libvirtualmachine_jni",
+    srcs: ["android_system_virtualmachine_VirtualMachine.cpp"],
+    apex_available: ["com.android.virt"],
+    shared_libs: [
+        "android.system.virtualizationservice-ndk",
+        "libbinder_ndk",
+        "libbinder_rpc_unstable",
+        "liblog",
+        "libnativehelper",
+    ],
+}
diff --git a/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
new file mode 100644
index 0000000..7234dad
--- /dev/null
+++ b/javalib/jni/android_system_virtualmachine_VirtualMachine.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright 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.
+ */
+
+#define LOG_TAG "VirtualMachine"
+
+#include <tuple>
+
+#include <log/log.h>
+
+#include <aidl/android/system/virtualizationservice/IVirtualMachine.h>
+#include <android/binder_auto_utils.h>
+#include <android/binder_ibinder_jni.h>
+#include <binder_rpc_unstable.hpp>
+
+#include <jni.h>
+
+JNIEXPORT jobject JNICALL android_system_virtualmachine_VirtualMachine_connectToVsockServer(
+        JNIEnv* env, [[maybe_unused]] jclass clazz, jobject vmBinder, jint port) {
+    using aidl::android::system::virtualizationservice::IVirtualMachine;
+    using ndk::ScopedFileDescriptor;
+    using ndk::SpAIBinder;
+
+    auto vm = IVirtualMachine::fromBinder(SpAIBinder{AIBinder_fromJavaBinder(env, vmBinder)});
+
+    std::tuple args{env, vm.get(), port};
+    using Args = decltype(args);
+
+    auto requestFunc = [](void* param) {
+        auto [env, vm, port] = *static_cast<Args*>(param);
+
+        ScopedFileDescriptor fd;
+        if (auto status = vm->connectVsock(port, &fd); !status.isOk()) {
+            env->ThrowNew(env->FindClass("android/system/virtualmachine/VirtualMachineException"),
+                          ("Failed to connect vsock: " + status.getDescription()).c_str());
+            return -1;
+        }
+
+        // take ownership
+        int ret = fd.get();
+        *fd.getR() = -1;
+
+        return ret;
+    };
+
+    return AIBinder_toJavaBinder(env, RpcPreconnectedClient(requestFunc, &args));
+}
+
+JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* /*reserved*/) {
+    JNIEnv* env;
+    if (vm->GetEnv(reinterpret_cast<void**>(&env), JNI_VERSION_1_6) != JNI_OK) {
+        ALOGE("%s: Failed to get the environment", __FUNCTION__);
+        return JNI_ERR;
+    }
+
+    jclass c = env->FindClass("android/system/virtualmachine/VirtualMachine");
+    if (c == nullptr) {
+        ALOGE("%s: Failed to find class android.system.virtualmachine.VirtualMachine",
+              __FUNCTION__);
+        return JNI_ERR;
+    }
+
+    // Register your class' native methods.
+    static const JNINativeMethod methods[] = {
+            {"nativeConnectToVsockServer", "(Landroid/os/IBinder;I)Landroid/os/IBinder;",
+             reinterpret_cast<void*>(
+                     android_system_virtualmachine_VirtualMachine_connectToVsockServer)},
+    };
+    int rc = env->RegisterNatives(c, methods, sizeof(methods) / sizeof(JNINativeMethod));
+    if (rc != JNI_OK) {
+        ALOGE("%s: Failed to register natives", __FUNCTION__);
+        return rc;
+    }
+
+    return JNI_VERSION_1_6;
+}
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index af53dae..1a752e3 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -42,6 +42,9 @@
 import java.nio.file.FileAlreadyExistsException;
 import java.nio.file.Files;
 import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 
 /**
  * A handle to the virtual machine. The virtual machine is local to the app which created the
@@ -110,6 +113,12 @@
     private @Nullable ParcelFileDescriptor mConsoleReader;
     private @Nullable ParcelFileDescriptor mConsoleWriter;
 
+    private final ExecutorService mExecutorService = Executors.newCachedThreadPool();
+
+    static {
+        System.loadLibrary("virtualmachine_jni");
+    }
+
     private VirtualMachine(
             @NonNull Context context, @NonNull String name, @NonNull VirtualMachineConfig config) {
         mPackageName = context.getPackageName();
@@ -439,6 +448,25 @@
         return oldConfig;
     }
 
+    private static native IBinder nativeConnectToVsockServer(IBinder vmBinder, int port);
+
+    /**
+     * Connects to a VM's RPC server via vsock, and returns a root IBinder object. Guest VMs are
+     * expected to set up vsock servers in their payload. After the host app receives onPayloadReady
+     * callback, the host app can use this method to establish an RPC session to the guest VMs.
+     *
+     * <p>If the connection succeeds, the root IBinder object will be returned via {@link
+     * VirtualMachineCallback.onVsockServerReady()}. If the connection fails, {@link
+     * VirtualMachineCallback.onVsockServerConnectionFailed()} will be called.
+     */
+    public Future<IBinder> connectToVsockServer(int port) throws VirtualMachineException {
+        if (getStatus() != Status.RUNNING) {
+            throw new VirtualMachineException("VM is not running");
+        }
+        return mExecutorService.submit(
+                () -> nativeConnectToVsockServer(mVirtualMachine.asBinder(), port));
+    }
+
     @Override
     public String toString() {
         StringBuilder sb = new StringBuilder();
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 1638f6f..e4334cb 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -101,7 +101,10 @@
     multilib: {
         common: {
             deps: [
+                // non-updatable & mandatory apexes
+                "com.android.i18n",
                 "com.android.runtime",
+
                 "microdroid_plat_sepolicy.cil",
                 "microdroid_plat_mapping_file",
             ],
diff --git a/microdroid/init.rc b/microdroid/init.rc
index f6a7ecc..347f514 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -19,7 +19,6 @@
 
     mkdir /mnt/apk 0755 system system
     start microdroid_manager
-    # TODO(b/190343842) verify apexes/apk before mounting them.
 
     # Exec apexd in the VM mode to avoid unnecessary overhead of normal mode.
     # (e.g. session management)
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 104c04a..9957689 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -32,6 +32,7 @@
         "libuuid",
         "libvsock",
         "librand",
+        "libzip",
     ],
     shared_libs: [
         "libbinder_rpc_unstable",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 51d74b0..73983a7 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -311,16 +311,22 @@
     ret
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
 pub struct MicrodroidData {
     pub apk_data: ApkData,
-    // TODO(b/197053593) add data for APEXes
+    pub apex_data: Vec<ApexData>,
 }
 
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
 pub struct ApkData {
     pub root_hash: Box<RootHash>,
     // TODO(b/199143508) add cert
 }
 
 pub type RootHash = [u8];
+
+#[derive(Debug, Serialize, Deserialize, PartialEq)]
+pub struct ApexData {
+    pub name: String,
+    pub pubkey: Vec<u8>,
+}
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 4f192dc..2e80b90 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -16,7 +16,7 @@
 
 mod instance;
 mod ioutil;
-mod metadata;
+mod payload;
 
 use crate::instance::{ApkData, InstanceDisk, MicrodroidData, RootHash};
 use anyhow::{anyhow, bail, Context, Result};
@@ -25,8 +25,10 @@
 use binder::{FromIBinder, Strong};
 use idsig::V4Signature;
 use log::{error, info, warn};
+use microdroid_metadata::Metadata;
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
 use nix::ioctl_read_bad;
+use payload::{get_apex_data_from_payload, load_metadata};
 use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
 use std::fs::{self, File, OpenOptions};
@@ -90,26 +92,23 @@
     kernlog::init()?;
     info!("started.");
 
-    let metadata = metadata::load()?;
+    let metadata = load_metadata()?;
 
     let mut instance = InstanceDisk::new()?;
-    let data = instance.read_microdroid_data().context("Failed to read identity data")?;
-    let saved_root_hash: Option<&[u8]> =
-        if let Some(data) = data.as_ref() { Some(&data.apk_data.root_hash) } else { None };
+    let saved_data = instance.read_microdroid_data().context("Failed to read identity data")?;
 
     // Verify the payload before using it.
-    let verified_root_hash =
-        verify_payload(saved_root_hash).context("Payload verification failed")?;
-    if let Some(saved_root_hash) = saved_root_hash {
-        if saved_root_hash == verified_root_hash.as_ref() {
-            info!("Saved root_hash is verified.");
+    let verified_data =
+        verify_payload(&metadata, saved_data.as_ref()).context("Payload verification failed")?;
+    if let Some(saved_data) = saved_data {
+        if saved_data == verified_data {
+            info!("Saved data is verified.");
         } else {
-            bail!("Detected an update of the APK which isn't supported yet.");
+            bail!("Detected an update of the payload which isn't supported yet.");
         }
     } else {
-        info!("Saving APK root_hash: {}", to_hex_string(verified_root_hash.as_ref()));
-        let data = MicrodroidData { apk_data: ApkData { root_hash: verified_root_hash } };
-        instance.write_microdroid_data(&data).context("Failed to write identity data")?;
+        info!("Saving verified data.");
+        instance.write_microdroid_data(&verified_data).context("Failed to write identity data")?;
     }
 
     wait_for_apex_config_done()?;
@@ -138,12 +137,17 @@
     Ok(())
 }
 
-// Verify payload before executing it. Full verification (which is slow) is done when the root_hash
-// values from the idsig file and the instance disk are different. This function returns the
-// verified root hash that can be saved to the instance disk.
-fn verify_payload(root_hash: Option<&RootHash>) -> Result<Box<RootHash>> {
+// Verify payload before executing it. For APK payload, Full verification (which is slow) is done
+// when the root_hash values from the idsig file and the instance disk are different. This function
+// returns the verified root hash (for APK payload) and pubkeys (for APEX payloads) that can be
+// saved to the instance disk.
+fn verify_payload(
+    metadata: &Metadata,
+    saved_data: Option<&MicrodroidData>,
+) -> Result<MicrodroidData> {
     let start_time = SystemTime::now();
 
+    let root_hash = saved_data.map(|d| &d.apk_data.root_hash);
     let root_hash_from_idsig = get_apk_root_hash_from_idsig()?;
     let root_hash_trustful = root_hash == Some(&root_hash_from_idsig);
 
@@ -156,6 +160,11 @@
 
     // Start apkdmverity and wait for the dm-verify block
     system_properties::write("ctl.start", "apkdmverity")?;
+
+    // While waiting for apkdmverity to mount APK, gathers APEX pubkeys used by APEXd.
+    // These will be compared ones from instance.img.
+    let apex_data_from_payload = get_apex_data_from_payload(metadata)?;
+
     ioutil::wait_for_file(DM_MOUNTED_APK_PATH, WAIT_TIMEOUT)?;
 
     // Do the full verification if the root_hash is un-trustful. This requires the full scanning of
@@ -171,7 +180,10 @@
 
     // At this point, we can ensure that the root_hash from the idsig file is trusted, either by
     // fully verifying the APK or by comparing it with the saved root_hash.
-    Ok(root_hash_from_idsig)
+    Ok(MicrodroidData {
+        apk_data: ApkData { root_hash: root_hash_from_idsig },
+        apex_data: apex_data_from_payload,
+    })
 }
 
 // Waits until linker config is generated
diff --git a/microdroid_manager/src/metadata.rs b/microdroid_manager/src/metadata.rs
deleted file mode 100644
index 432a134..0000000
--- a/microdroid_manager/src/metadata.rs
+++ /dev/null
@@ -1,32 +0,0 @@
-// Copyright 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.
-
-//! Payload metadata from /dev/block/by-name/payload-metadata
-
-use crate::ioutil;
-
-use anyhow::Result;
-use log::info;
-use microdroid_metadata::{read_metadata, Metadata};
-use std::time::Duration;
-
-const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
-const PAYLOAD_METADATA_PATH: &str = "/dev/block/by-name/payload-metadata";
-
-/// loads payload metadata from /dev/block/by-name/paylaod-metadata
-pub fn load() -> Result<Metadata> {
-    info!("loading payload metadata...");
-    let file = ioutil::wait_for_file(PAYLOAD_METADATA_PATH, WAIT_TIMEOUT)?;
-    read_metadata(file)
-}
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
new file mode 100644
index 0000000..bfc6c09
--- /dev/null
+++ b/microdroid_manager/src/payload.rs
@@ -0,0 +1,61 @@
+// Copyright 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.
+
+//! Routines for handling payload
+
+use crate::instance::ApexData;
+use crate::ioutil::wait_for_file;
+use anyhow::Result;
+use log::info;
+use microdroid_metadata::{read_metadata, Metadata};
+use std::fs::File;
+use std::io::Read;
+use std::time::Duration;
+use zip::ZipArchive;
+
+const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
+const PAYLOAD_METADATA_PATH: &str = "/dev/block/by-name/payload-metadata";
+const WAIT_TIMEOUT: Duration = Duration::from_secs(10);
+
+/// Loads payload metadata from /dev/block/by-name/payload-metadata
+pub fn load_metadata() -> Result<Metadata> {
+    info!("loading payload metadata...");
+    let file = wait_for_file(PAYLOAD_METADATA_PATH, WAIT_TIMEOUT)?;
+    read_metadata(file)
+}
+
+/// Loads (name, pubkey) from payload apexes and returns them as sorted by name.
+pub fn get_apex_data_from_payload(metadata: &Metadata) -> Result<Vec<ApexData>> {
+    let mut apex_data: Vec<ApexData> = metadata
+        .apexes
+        .iter()
+        .map(|apex| {
+            let name = apex.name.clone();
+            let partition = format!("/dev/block/by-name/{}", apex.partition_name);
+            let pubkey = get_pubkey_from_apex(&partition)?;
+            Ok(ApexData { name, pubkey })
+        })
+        .collect::<Result<Vec<_>>>()?;
+    apex_data.sort_by(|a, b| a.name.cmp(&b.name));
+    Ok(apex_data)
+}
+
+fn get_pubkey_from_apex(path: &str) -> Result<Vec<u8>> {
+    let f = File::open(path)?;
+    let mut z = ZipArchive::new(f)?;
+    let mut pubkey_file = z.by_name(APEX_PUBKEY_ENTRY)?;
+    let mut pubkey = Vec::new();
+    pubkey_file.read_to_end(&mut pubkey)?;
+    Ok(pubkey)
+}
diff --git a/tests/aidl/Android.bp b/tests/aidl/Android.bp
new file mode 100644
index 0000000..893ec0b
--- /dev/null
+++ b/tests/aidl/Android.bp
@@ -0,0 +1,18 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aidl_interface {
+    name: "com.android.microdroid.testservice",
+    srcs: ["com/android/microdroid/testservice/**/*.aidl"],
+    unstable: true,
+    backend: {
+        java: {
+            platform_apis: true,
+            gen_rpc: true,
+        },
+        cpp: {
+            enabled: true,
+        },
+    },
+}
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
new file mode 100644
index 0000000..cdcb2bd
--- /dev/null
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -0,0 +1,24 @@
+/*
+ * Copyright 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.
+ */
+package com.android.microdroid.testservice;
+
+/** {@hide} */
+interface ITestService {
+    const int SERVICE_PORT = 5678;
+
+    /* add two integers. */
+    int addInteger(int a, int b);
+}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index f545f8e..0b0810f 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -19,8 +19,11 @@
     srcs: ["src/native/testbinary.cpp"],
     shared_libs: [
         "android.system.keystore2-V1-ndk",
+        "android.system.virtualmachineservice-ndk",
+        "com.android.microdroid.testservice-ndk",
         "libbase",
         "libbinder_ndk",
+        "libbinder_rpc_unstable",
         "MicrodroidTestNativeLibSub",
     ],
 }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 1572021..2903a08 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -14,11 +14,21 @@
  * limitations under the License.
  */
 #include <aidl/android/system/keystore2/IKeystoreService.h>
+#include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
+#include <aidl/com/android/microdroid/testservice/BnTestService.h>
 #include <android-base/result.h>
+#include <android-base/unique_fd.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_manager.h>
+#include <fcntl.h>
+#include <linux/vm_sockets.h>
+#include <stdint.h>
 #include <stdio.h>
+#include <sys/ioctl.h>
 #include <sys/system_properties.h>
+#include <unistd.h>
+
+#include <binder_rpc_unstable.hpp>
 
 using aidl::android::hardware::security::keymint::Algorithm;
 using aidl::android::hardware::security::keymint::Digest;
@@ -35,8 +45,12 @@
 using aidl::android::system::keystore2::KeyDescriptor;
 using aidl::android::system::keystore2::KeyMetadata;
 
+using aidl::android::system::virtualmachineservice::IVirtualMachineService;
+
+using android::base::ErrnoError;
 using android::base::Error;
 using android::base::Result;
+using android::base::unique_fd;
 
 extern void testlib_sub();
 
@@ -184,9 +198,63 @@
     return result;
 }
 
+Result<unsigned> get_local_cid() {
+    // TODO: remove this after VS can check the peer addresses of binder clients
+    unique_fd fd(open("/dev/vsock", O_RDONLY));
+    if (fd.get() == -1) {
+        return ErrnoError() << "failed to open /dev/vsock";
+    }
+
+    unsigned cid;
+    if (ioctl(fd.get(), IOCTL_VM_SOCKETS_GET_LOCAL_CID, &cid) == -1) {
+        return ErrnoError() << "failed to IOCTL_VM_SOCKETS_GET_LOCAL_CID";
+    }
+
+    return cid;
+}
+
+Result<void> start_test_service() {
+    class TestService : public aidl::com::android::microdroid::testservice::BnTestService {
+        ndk::ScopedAStatus addInteger(int32_t a, int32_t b, int32_t* out) override {
+            *out = a + b;
+            return ndk::ScopedAStatus::ok();
+        }
+    };
+    auto testService = ndk::SharedRefBase::make<TestService>();
+
+    auto callback = []([[maybe_unused]] void* param) {
+        // Tell microdroid_manager that we're ready.
+        // Failing to notify is not a fatal error; the payload can continue.
+        ndk::SpAIBinder binder(
+                RpcClient(VMADDR_CID_HOST, IVirtualMachineService::VM_BINDER_SERVICE_PORT));
+        auto virtualMachineService = IVirtualMachineService::fromBinder(binder);
+        if (virtualMachineService == nullptr) {
+            std::cerr << "failed to connect VirtualMachineService";
+            return;
+        }
+        if (auto res = get_local_cid(); !res.ok()) {
+            std::cerr << "failed to get local cid: " << res.error();
+        } else if (!virtualMachineService->notifyPayloadReady(res.value()).isOk()) {
+            std::cerr << "failed to notify payload ready to virtualizationservice";
+        }
+    };
+
+    if (!RunRpcServerCallback(testService->asBinder().get(), testService->SERVICE_PORT, callback,
+                              nullptr)) {
+        return Error() << "RPC Server failed to run";
+    }
+
+    return {};
+}
+
 } // Anonymous namespace
 
 extern "C" int android_native_main(int argc, char* argv[]) {
+    // disable buffering to communicate seamlessly
+    setvbuf(stdin, nullptr, _IONBF, 0);
+    setvbuf(stdout, nullptr, _IONBF, 0);
+    setvbuf(stderr, nullptr, _IONBF, 0);
+
     printf("Hello Microdroid ");
     for (int i = 0; i < argc; i++) {
         printf("%s", argv[i]);
@@ -201,5 +269,10 @@
     __system_property_set("debug.microdroid.app.run", "true");
     if (!report_test("keystore", test_keystore()).ok()) return 1;
 
-    return 0;
+    if (auto res = start_test_service(); res.ok()) {
+        return 0;
+    } else {
+        std::cerr << "starting service failed: " << res.error();
+        return 1;
+    }
 }
diff --git a/virtualizationservice/aidl/Android.bp b/virtualizationservice/aidl/Android.bp
index 8096cf7..571cc5d 100644
--- a/virtualizationservice/aidl/Android.bp
+++ b/virtualizationservice/aidl/Android.bp
@@ -18,6 +18,7 @@
         },
         ndk: {
             apex_available: [
+                "com.android.virt",
                 "com.android.compos",
             ],
         },
diff --git a/virtualizationservice/src/payload.rs b/virtualizationservice/src/payload.rs
index 75ba6c7..9662fa3 100644
--- a/virtualizationservice/src/payload.rs
+++ b/virtualizationservice/src/payload.rs
@@ -31,8 +31,7 @@
 
 /// The list of APEXes which microdroid requires.
 // TODO(b/192200378) move this to microdroid.json?
-const MICRODROID_REQUIRED_APEXES: [&str; 3] =
-    ["com.android.adbd", "com.android.i18n", "com.android.os.statsd"];
+const MICRODROID_REQUIRED_APEXES: [&str; 2] = ["com.android.adbd", "com.android.os.statsd"];
 
 const APEX_INFO_LIST_PATH: &str = "/apex/apex-info-list.xml";