Add an example for onPayloadReady and VM service
This adds a simple integer addition service to the testapk's binary. The
demo app tries connecting the VM service once onPayloadReady is
triggered. Users can see the VM service's output.
Bug: 195381416
Test: launch MicrodroidDemoApp
Test: atest MicrodroidHostTestCases
Change-Id: I346084d08f753772cc00aa4c052e0b9b41d460ce
diff --git a/demo/Android.bp b/demo/Android.bp
index 37fc676..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: [
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/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;
+ }
}