diff --git a/tests/aidl/Android.bp b/tests/aidl/Android.bp index d59ca7e2..ed4e8ff6 100644 --- a/tests/aidl/Android.bp +++ b/tests/aidl/Android.bp @@ -6,6 +6,10 @@ aidl_interface { name: "com.android.microdroid.testservice", srcs: ["com/android/microdroid/testservice/**/*.aidl"], unstable: true, + flags: [ + "-Werror", + "-Wno-mixed-oneway", + ], backend: { java: { gen_rpc: true, diff --git a/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl b/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl new file mode 100644 index 00000000..98590909 --- /dev/null +++ b/tests/aidl/com/android/microdroid/testservice/IAppCallback.aidl @@ -0,0 +1,31 @@ +/* + * Copyright 2023 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; + +import com.android.microdroid.testservice.IVmCallback; + +/** + * An interface exposed by the app for callbacks from the VM. + * + * {@hide} + */ +interface IAppCallback { + /** Invites the app to call vmCallback#echoMessage() */ + void setVmCallback(IVmCallback vmCallback); + + /** Asynchronusly called by the VM in response to a call to echoMessage(). */ + void onEchoRequestReceived(String message); +} diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl index 66dbe4b6..36c3aaf4 100644 --- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl +++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl @@ -15,7 +15,12 @@ */ package com.android.microdroid.testservice; -/** {@hide} */ +import com.android.microdroid.testservice.IAppCallback; + +/** + * This is the service exposed by the test payload, called by the test app. + * {@hide} + */ interface ITestService { const long SERVICE_PORT = 5678; @@ -62,6 +67,9 @@ interface ITestService { /** Returns flags for the given mountPoint. */ int getMountFlags(String mountPoint); + /** Requests the VM to asynchronously call appCallback.setVmCallback() */ + void requestCallback(IAppCallback appCallback); + /** * Request the service to exit, triggering the termination of the VM. This may cause any * requests in flight to fail. diff --git a/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl b/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl new file mode 100644 index 00000000..617d1842 --- /dev/null +++ b/tests/aidl/com/android/microdroid/testservice/IVmCallback.aidl @@ -0,0 +1,26 @@ +/* + * Copyright 2023 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; + +/** + * An interface exposed by the VM for callbacks from the app. + * + * {@hide} + */ +interface IVmCallback { + /** Requests the VM to asynchronously call the app's onEchoRequestReceived() callback. */ + void echoMessage(String message); +} 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 d05d9b3b..e20be9a0 100644 --- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java +++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java @@ -58,7 +58,9 @@ import android.util.Log; import com.android.compatibility.common.util.CddTest; import com.android.microdroid.test.device.MicrodroidDeviceTestBase; import com.android.microdroid.test.vmshare.IVmShareTestService; +import com.android.microdroid.testservice.IAppCallback; import com.android.microdroid.testservice.ITestService; +import com.android.microdroid.testservice.IVmCallback; import org.junit.After; import org.junit.Before; @@ -399,6 +401,59 @@ public class MicrodroidTests extends MicrodroidDeviceTestBase { assertThat(response.get()).isEqualTo(new StringBuilder(request).reverse().toString()); } + @Test + @CddTest(requirements = {"9.17/C-1-1"}) + public void binderCallbacksWork() throws Exception { + assumeSupportedKernel(); + + VirtualMachineConfig config = + newVmConfigBuilder() + .setPayloadBinaryName("MicrodroidTestNativeLib.so") + .setMemoryBytes(minMemoryRequired()) + .setDebugLevel(DEBUG_LEVEL_FULL) + .build(); + VirtualMachine vm = forceCreateNewVirtualMachine("test_vm", config); + + String request = "Hello"; + CompletableFuture response = new CompletableFuture<>(); + + IAppCallback appCallback = + new IAppCallback.Stub() { + @Override + public void setVmCallback(IVmCallback vmCallback) { + // Do this on a separate thread to simulate an asynchronous trigger, + // and to make sure it doesn't happen in the context of an inbound binder + // call. + new Thread() { + @Override + public void run() { + try { + vmCallback.echoMessage(request); + } catch (Exception e) { + response.completeExceptionally(e); + } + } + }.start(); + } + + @Override + public void onEchoRequestReceived(String message) { + response.complete(message); + } + }; + + TestResults testResults = + runVmTestService( + TAG, + vm, + (service, results) -> { + service.requestCallback(appCallback); + response.get(10, TimeUnit.SECONDS); + }); + testResults.assertNoException(); + assertThat(response.getNow("no response")).isEqualTo("Received: " + request); + } + @Test @CddTest(requirements = {"9.17/C-1-1"}) public void vmConfigGetAndSetTests() { diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp index 285dae91..d24ddfd1 100644 --- a/tests/testapk/src/native/testbinary.cpp +++ b/tests/testapk/src/native/testbinary.cpp @@ -15,6 +15,8 @@ */ #include +#include +#include #include #include #include @@ -47,6 +49,8 @@ using android::fs_mgr::GetEntryForMountPoint; using android::fs_mgr::ReadFstabFromFile; using aidl::com::android::microdroid::testservice::BnTestService; +using aidl::com::android::microdroid::testservice::BnVmCallback; +using aidl::com::android::microdroid::testservice::IAppCallback; using ndk::ScopedAStatus; extern void testlib_sub(); @@ -144,7 +148,25 @@ Result start_echo_reverse_server() { } Result start_test_service() { + class VmCallbackImpl : public BnVmCallback { + private: + std::shared_ptr mAppCallback; + + public: + explicit VmCallbackImpl(const std::shared_ptr& appCallback) + : mAppCallback(appCallback) {} + + ScopedAStatus echoMessage(const std::string& message) override { + std::thread callback_thread{[=, appCallback = mAppCallback] { + appCallback->onEchoRequestReceived("Received: " + message); + }}; + callback_thread.detach(); + return ScopedAStatus::ok(); + } + }; + class TestService : public BnTestService { + public: ScopedAStatus addInteger(int32_t a, int32_t b, int32_t* out) override { *out = a + b; return ScopedAStatus::ok(); @@ -226,7 +248,7 @@ Result start_test_service() { return ScopedAStatus::ok(); } - virtual ::ScopedAStatus runEchoReverseServer() override { + ScopedAStatus runEchoReverseServer() override { auto result = start_echo_reverse_server(); if (result.ok()) { return ScopedAStatus::ok(); @@ -284,6 +306,13 @@ Result start_test_service() { return ScopedAStatus::ok(); } + ScopedAStatus requestCallback(const std::shared_ptr& appCallback) { + auto vmCallback = ndk::SharedRefBase::make(appCallback); + std::thread callback_thread{[=] { appCallback->setVmCallback(vmCallback); }}; + callback_thread.detach(); + return ScopedAStatus::ok(); + } + ScopedAStatus quit() override { exit(0); } }; auto testService = ndk::SharedRefBase::make(); diff --git a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java index 467b98b4..edd6bf52 100644 --- a/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java +++ b/tests/vmshareapp/src/java/com/android/microdroid/test/sharevm/VmShareServiceImpl.java @@ -29,6 +29,7 @@ import android.util.Log; import com.android.microdroid.test.vmshare.IVmShareTestService; import com.android.microdroid.testservice.ITestService; +import com.android.microdroid.testservice.IAppCallback; import java.util.UUID; import java.util.concurrent.CountDownLatch; @@ -239,6 +240,11 @@ public class VmShareServiceImpl extends Service { throw new UnsupportedOperationException("Not supported"); } + @Override + public void requestCallback(IAppCallback appCallback) { + throw new UnsupportedOperationException("Not supported"); + } + @Override public void quit() throws RemoteException { throw new UnsupportedOperationException("Not supported");