Add a test wakeupclient service implementation.
The test implementation will send out a remote task to remote access
HAL every 5s.
Test: Manually test on gcar_emu.
adb root
adb remount
adb reboot
adb root
adb remount
m -j TestWakeupClientServer
cd out/target/product/emulator_car64_x86_64
adb push ./vendor/bin/TestWakeupClientServer /vendor/bin
adb shell
In the shell:
su
/vendor/bin/TestWakeupClientServer
Check adb logcat, verify tasks are received.
Bug: 246841306
Change-Id: Idaf198662f7004e3a9e77d75caeffc00cda49218
diff --git a/automotive/remoteaccess/hal/default/Android.bp b/automotive/remoteaccess/hal/default/Android.bp
new file mode 100644
index 0000000..25bda3e
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/Android.bp
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_binary {
+ name: "android.hardware.automotive.remoteaccess@V1-default-service",
+ vendor: true,
+ vintf_fragments: ["remoteaccess-default-service.xml"],
+ init_rc: ["remoteaccess-default-service.rc"],
+ relative_install_path: "hw",
+ srcs: ["src/RemoteAccessImpl.cpp"],
+ whole_static_libs: [
+ "RemoteAccessService",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"",
+ ],
+}
+
+cc_library {
+ name: "RemoteAccessService",
+ vendor: true,
+ local_include_dirs: ["include"],
+ export_include_dirs: ["include"],
+ srcs: [
+ "src/RemoteAccessService.cpp",
+ ],
+ whole_static_libs: [
+ "android.hardware.automotive.remoteaccess-V1-ndk",
+ "wakeup_client_protos",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+}
diff --git a/automotive/remoteaccess/hal/default/include/RemoteAccessService.h b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h
new file mode 100644
index 0000000..806440a
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteAccess.h>
+#include <aidl/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.h>
+#include <android-base/thread_annotations.h>
+#include <wakeup_client.grpc.pb.h>
+
+#include <string>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+class RemoteAccessService
+ : public aidl::android::hardware::automotive::remoteaccess::BnRemoteAccess {
+ public:
+ explicit RemoteAccessService(WakeupClient::StubInterface* grpcStub);
+
+ ~RemoteAccessService();
+
+ ndk::ScopedAStatus getDeviceId(std::string* deviceId) override;
+
+ ndk::ScopedAStatus getWakeupServiceName(std::string* wakeupServiceName) override;
+
+ ndk::ScopedAStatus setRemoteTaskCallback(
+ const std::shared_ptr<
+ aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback>&
+ callback) override;
+
+ ndk::ScopedAStatus clearRemoteTaskCallback() override;
+
+ ndk::ScopedAStatus notifyApStateChange(
+ const aidl::android::hardware::automotive::remoteaccess::ApState& newState) override;
+
+ private:
+ // For testing.
+ friend class RemoteAccessServiceUnitTest;
+
+ WakeupClient::StubInterface* mGrpcStub;
+ std::thread mThread;
+ std::mutex mLock;
+ std::condition_variable mCv;
+ std::shared_ptr<aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback>
+ mRemoteTaskCallback GUARDED_BY(mLock);
+ std::unique_ptr<grpc::ClientContext> mGetRemoteTasksContext GUARDED_BY(mLock);
+ // Associated with mCv to notify the task loop to stop waiting and exit.
+ bool mTaskWaitStopped GUARDED_BY(mLock);
+ // A mutex to make sure startTaskLoop does not overlap with stopTaskLoop.
+ std::mutex mStartStopTaskLoopLock;
+ bool mTaskLoopRunning GUARDED_BY(mStartStopTaskLoopLock);
+ // Default wait time before retry connecting to remote access client is 10s.
+ size_t mRetryWaitInMs = 10'000;
+
+ void runTaskLoop();
+ void maybeStartTaskLoop();
+ void maybeStopTaskLoop();
+
+ void setRetryWaitInMs(size_t retryWaitInMs) { mRetryWaitInMs = retryWaitInMs; }
+};
+
+} // namespace remoteaccess
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/remoteaccess/hal/default/proto/Android.bp b/automotive/remoteaccess/hal/default/proto/Android.bp
new file mode 100644
index 0000000..31b9d0e
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/proto/Android.bp
@@ -0,0 +1,81 @@
+// Copyright (C) 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.
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "hardware_interfaces_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["hardware_interfaces_license"],
+}
+
+genrule {
+ name: "wakeup_client_pb_h",
+ tools: [
+ "aprotoc",
+ "protoc-gen-grpc-cpp-plugin",
+ ],
+ cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+ srcs: [
+ "wakeup_client.proto",
+ ],
+ out: [
+ "wakeup_client.pb.h",
+ "wakeup_client.grpc.pb.h",
+ ],
+ vendor: true,
+}
+
+genrule {
+ name: "wakeup_client_pb_cc",
+ tools: [
+ "aprotoc",
+ "protoc-gen-grpc-cpp-plugin",
+ ],
+ cmd: "$(location aprotoc) -I$$(dirname $(in)) -Iexternal/protobuf/src --plugin=protoc-gen-grpc=$(location protoc-gen-grpc-cpp-plugin) $(in) --grpc_out=$(genDir) --cpp_out=$(genDir)",
+ srcs: [
+ "wakeup_client.proto",
+ ],
+ out: [
+ "wakeup_client.pb.cc",
+ "wakeup_client.grpc.pb.cc",
+ ],
+ vendor: true,
+}
+
+cc_library_static {
+ name: "wakeup_client_protos",
+ vendor: true,
+ host_supported: true,
+ include_dirs: [
+ "external/protobuf/src",
+ ],
+ generated_headers: [
+ "wakeup_client_pb_h",
+ ],
+ export_generated_headers: [
+ "wakeup_client_pb_h",
+ ],
+ generated_sources: [
+ "wakeup_client_pb_cc",
+ ],
+ shared_libs: [
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+}
diff --git a/automotive/remoteaccess/hal/default/proto/wakeup_client.proto b/automotive/remoteaccess/hal/default/proto/wakeup_client.proto
new file mode 100644
index 0000000..4fe0d01
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/proto/wakeup_client.proto
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 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.
+ */
+
+syntax = "proto3";
+
+package android.hardware.automotive.remoteaccess;
+
+/**
+ * Service provided by a wakeup client running on TCU.
+ */
+service WakeupClient {
+ /**
+ * Establish a long-live connection to receive remote tasks.
+ *
+ * <p>For the server, whenever a remote task arrives, if the connection is
+ * alive, it will use the return stream to return a task's information.
+ *
+ * <p>If the connection is not alive, the server must stores the remote task
+ * until a new connection is established (which means AP is ready to
+ * receive remote task again) and send the stored tasks.
+ *
+ * <p>If the server closes the connection, the client will try to
+ * reestablish the connection.
+ */
+ rpc GetRemoteTasks(GetRemoteTasksRequest) returns (stream GetRemoteTasksResponse) {}
+
+ /**
+ * Notifies whether AP is required to be waken up when remote task arrives.
+ *
+ * <p>Wakeup client should store and use this state until a new call with a
+ * different state arrives.
+ *
+ * <p>If {@code isWakeupRequired} in the request is true, it must wake up AP
+ * when a remote task arrives.
+ *
+ * <p>If {@code isWakeupRequired} in the request is false, it must not try
+ * to wake up AP.
+ */
+ rpc NotifyWakeupRequired(NotifyWakeupRequiredRequest) returns (NotifyWakeupRequiredResponse) {}
+}
+
+message GetRemoteTasksRequest {}
+
+message GetRemoteTasksResponse {
+ string clientId = 1;
+ bytes data = 2;
+}
+
+message NotifyWakeupRequiredRequest {
+ bool isWakeupRequired = 1;
+}
+
+message NotifyWakeupRequiredResponse {}
diff --git a/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc b/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc
new file mode 100644
index 0000000..b7a9cdc
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/remoteaccess-default-service.rc
@@ -0,0 +1,4 @@
+service vendor.remoteaccess-default /vendor/bin/hw/android.hardware.automotive.remoteaccess@V1-default-service
+ class hal
+ user vehicle_network
+ group system inet
diff --git a/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml b/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml
new file mode 100644
index 0000000..d050a1b
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/remoteaccess-default-service.xml
@@ -0,0 +1,7 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.automotive.remoteaccess</name>
+ <version>1</version>
+ <fqname>IRemoteAccess/default</fqname>
+ </hal>
+</manifest>
diff --git a/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp b/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp
new file mode 100644
index 0000000..8720c2f
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/src/RemoteAccessImpl.cpp
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#define LOG_TAG "RemoteAccessImpl"
+
+#include "RemoteAccessService.h"
+
+#include <android/binder_manager.h>
+#include <android/binder_process.h>
+#include <grpcpp/create_channel.h>
+#include <stdlib.h>
+#include <utils/Log.h>
+
+constexpr char SERVICE_NAME[] = "android.hardware.automotive.remoteaccess.IRemoteAccess/default";
+
+int main(int /* argc */, char* /* argv */[]) {
+ ALOGI("Registering RemoteAccessService as service...");
+
+#ifndef GRPC_SERVICE_ADDRESS
+ ALOGE("GRPC_SERVICE_ADDRESS is not defined, exiting");
+ exit(1);
+#endif
+ auto channel = grpc::CreateChannel(GRPC_SERVICE_ADDRESS, grpc::InsecureChannelCredentials());
+ auto clientStub = android::hardware::automotive::remoteaccess::WakeupClient::NewStub(channel);
+ auto service = ndk::SharedRefBase::make<
+ android::hardware::automotive::remoteaccess::RemoteAccessService>(clientStub.get());
+
+ binder_exception_t err = AServiceManager_addService(service->asBinder().get(), SERVICE_NAME);
+ if (err != EX_NONE) {
+ ALOGE("failed to register android.hardware.automotive.remote.IRemoteAccess service, "
+ "exception: %d",
+ err);
+ exit(1);
+ }
+
+ if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) {
+ ALOGE("%s", "failed to set thread pool max thread count");
+ exit(1);
+ }
+ ABinderProcess_startThreadPool();
+
+ ALOGI("RemoteAccess service Ready");
+
+ ABinderProcess_joinThreadPool();
+
+ ALOGW("Should not reach here");
+
+ return 0;
+}
diff --git a/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp b/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp
new file mode 100644
index 0000000..6c297e3
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "RemoteAccessService.h"
+
+#include <android/binder_status.h>
+#include <grpc++/grpc++.h>
+#include <utils/Log.h>
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+namespace {
+
+using ::aidl::android::hardware::automotive::remoteaccess::ApState;
+using ::aidl::android::hardware::automotive::remoteaccess::IRemoteTaskCallback;
+using ::android::base::ScopedLockAssertion;
+using ::grpc::ClientContext;
+using ::grpc::ClientReaderInterface;
+using ::grpc::Status;
+using ::grpc::StatusCode;
+using ::ndk::ScopedAStatus;
+
+const std::string WAKEUP_SERVICE_NAME = "com.google.vehicle.wakeup";
+
+std::vector<uint8_t> stringToBytes(const std::string& s) {
+ const char* data = s.data();
+ return std::vector<uint8_t>(data, data + s.size());
+}
+
+ScopedAStatus rpcStatusToScopedAStatus(const Status& status, const std::string& errorMsg) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ status.error_code(), (errorMsg + ", error: " + status.error_message()).c_str());
+}
+
+} // namespace
+
+RemoteAccessService::RemoteAccessService(WakeupClient::StubInterface* grpcStub)
+ : mGrpcStub(grpcStub){};
+
+RemoteAccessService::~RemoteAccessService() {
+ maybeStopTaskLoop();
+}
+
+void RemoteAccessService::maybeStartTaskLoop() {
+ std::lock_guard<std::mutex> lockGuard(mStartStopTaskLoopLock);
+ if (mTaskLoopRunning) {
+ return;
+ }
+
+ mThread = std::thread([this]() { runTaskLoop(); });
+
+ mTaskLoopRunning = true;
+}
+
+void RemoteAccessService::maybeStopTaskLoop() {
+ std::lock_guard<std::mutex> lockGuard(mStartStopTaskLoopLock);
+ if (!mTaskLoopRunning) {
+ return;
+ }
+
+ {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ // Try to stop the reading stream.
+ if (mGetRemoteTasksContext) {
+ mGetRemoteTasksContext->TryCancel();
+ mGetRemoteTasksContext.reset();
+ }
+ mTaskWaitStopped = true;
+ mCv.notify_all();
+ }
+ if (mThread.joinable()) {
+ mThread.join();
+ }
+
+ mTaskLoopRunning = false;
+}
+
+void RemoteAccessService::runTaskLoop() {
+ GetRemoteTasksRequest request = {};
+ std::unique_ptr<ClientReaderInterface<GetRemoteTasksResponse>> reader;
+ while (true) {
+ {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ mGetRemoteTasksContext.reset(new ClientContext());
+ reader = mGrpcStub->GetRemoteTasks(mGetRemoteTasksContext.get(), request);
+ }
+ GetRemoteTasksResponse response;
+ while (reader->Read(&response)) {
+ ALOGI("Receiving one task from remote task client");
+
+ std::shared_ptr<IRemoteTaskCallback> callback;
+ {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ callback = mRemoteTaskCallback;
+ }
+ if (callback == nullptr) {
+ ALOGD("No callback registered, task ignored");
+ continue;
+ }
+ ALOGD("Calling onRemoteTaskRequested callback for client ID: %s",
+ response.clientid().c_str());
+ ScopedAStatus callbackStatus = callback->onRemoteTaskRequested(
+ response.clientid(), stringToBytes(response.data()));
+ if (!callbackStatus.isOk()) {
+ ALOGE("Failed to call onRemoteTaskRequested callback, status: %d, message: %s",
+ callbackStatus.getStatus(), callbackStatus.getMessage());
+ }
+ }
+ Status status = reader->Finish();
+
+ ALOGE("GetRemoteTasks stream breaks, code: %d, message: %s, sleeping for 10s and retry",
+ status.error_code(), status.error_message().c_str());
+ // The long lasting connection should not return. But if the server returns, retry after
+ // 10s.
+ {
+ std::unique_lock lk(mLock);
+ if (mCv.wait_for(lk, std::chrono::milliseconds(mRetryWaitInMs), [this] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mTaskWaitStopped;
+ })) {
+ // If the stopped flag is set, we are quitting, exit the loop.
+ break;
+ }
+ }
+ }
+}
+
+ScopedAStatus RemoteAccessService::getDeviceId(std::string* deviceId) {
+ // TODO(b/241483300): Call VHAL to get VIN.
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::getWakeupServiceName(std::string* wakeupServiceName) {
+ *wakeupServiceName = WAKEUP_SERVICE_NAME;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::setRemoteTaskCallback(
+ [[maybe_unused]] const std::shared_ptr<IRemoteTaskCallback>& callback) {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ mRemoteTaskCallback = callback;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::clearRemoteTaskCallback() {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ mRemoteTaskCallback.reset();
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::notifyApStateChange(const ApState& newState) {
+ ClientContext context;
+ NotifyWakeupRequiredRequest request = {};
+ request.set_iswakeuprequired(newState.isWakeupRequired);
+ NotifyWakeupRequiredResponse response = {};
+ Status status = mGrpcStub->NotifyWakeupRequired(&context, request, &response);
+ if (!status.ok()) {
+ return rpcStatusToScopedAStatus(status, "Failed to notify isWakeupRequired");
+ }
+
+ if (newState.isReadyForRemoteTask) {
+ maybeStartTaskLoop();
+ } else {
+ maybeStopTaskLoop();
+ }
+ return ScopedAStatus::ok();
+}
+
+} // namespace remoteaccess
+} // namespace automotive
+} // namespace hardware
+} // namespace android
diff --git a/automotive/remoteaccess/hal/default/test/Android.bp b/automotive/remoteaccess/hal/default/test/Android.bp
new file mode 100644
index 0000000..e92440f
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/test/Android.bp
@@ -0,0 +1,43 @@
+// Copyright (C) 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.
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+cc_test {
+ name: "RemoteAccessServiceUnitTest",
+ vendor: true,
+ srcs: ["*.cpp"],
+ whole_static_libs: [
+ "RemoteAccessService",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ // libgrpc++.so is installed as root, require root to access it.
+ require_root: true,
+ static_libs: [
+ "libgtest",
+ "libgmock",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+ test_suites: ["device-tests"],
+}
diff --git a/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp b/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp
new file mode 100644
index 0000000..11523f6
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (C) 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.
+ */
+
+#include "RemoteAccessService.h"
+
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteTaskCallback.h>
+#include <gmock/gmock.h>
+#include <grpcpp/test/mock_stream.h>
+#include <gtest/gtest.h>
+#include <wakeup_client.grpc.pb.h>
+#include <chrono>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+using ::android::base::ScopedLockAssertion;
+
+using ::aidl::android::hardware::automotive::remoteaccess::ApState;
+using ::aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback;
+
+using ::grpc::ClientAsyncReaderInterface;
+using ::grpc::ClientAsyncResponseReaderInterface;
+using ::grpc::ClientContext;
+using ::grpc::ClientReader;
+using ::grpc::ClientReaderInterface;
+using ::grpc::CompletionQueue;
+using ::grpc::Status;
+using ::grpc::testing::MockClientReader;
+using ::ndk::ScopedAStatus;
+using ::testing::_;
+using ::testing::DoAll;
+using ::testing::Return;
+using ::testing::SetArgPointee;
+
+class MockGrpcClientStub : public WakeupClient::StubInterface {
+ public:
+ MOCK_METHOD(ClientReaderInterface<GetRemoteTasksResponse>*, GetRemoteTasksRaw,
+ (ClientContext * context, const GetRemoteTasksRequest& request));
+ MOCK_METHOD(Status, NotifyWakeupRequired,
+ (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+ NotifyWakeupRequiredResponse* response));
+ // Async methods which we do not care.
+ MOCK_METHOD(ClientAsyncReaderInterface<GetRemoteTasksResponse>*, AsyncGetRemoteTasksRaw,
+ (ClientContext * context, const GetRemoteTasksRequest& request, CompletionQueue* cq,
+ void* tag));
+ MOCK_METHOD(ClientAsyncReaderInterface<GetRemoteTasksResponse>*, PrepareAsyncGetRemoteTasksRaw,
+ (ClientContext * context, const GetRemoteTasksRequest& request,
+ CompletionQueue* cq));
+ MOCK_METHOD(ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>*,
+ AsyncNotifyWakeupRequiredRaw,
+ (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+ CompletionQueue* cq));
+ MOCK_METHOD(ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>*,
+ PrepareAsyncNotifyWakeupRequiredRaw,
+ (ClientContext * context, const NotifyWakeupRequiredRequest& request,
+ CompletionQueue* cq));
+};
+
+class FakeRemoteTaskCallback : public BnRemoteTaskCallback {
+ public:
+ ScopedAStatus onRemoteTaskRequested(const std::string& clientId,
+ const std::vector<uint8_t>& data) override {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ mDataByClientId[clientId] = data;
+ mTaskCount++;
+ mCv.notify_all();
+ return ScopedAStatus::ok();
+ }
+
+ std::vector<uint8_t> getData(const std::string& clientId) { return mDataByClientId[clientId]; }
+
+ bool wait(size_t taskCount, size_t timeoutInSec) {
+ std::unique_lock<std::mutex> lock(mLock);
+ return mCv.wait_for(lock, std::chrono::seconds(timeoutInSec), [taskCount, this] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mTaskCount >= taskCount;
+ });
+ }
+
+ private:
+ std::mutex mLock;
+ std::unordered_map<std::string, std::vector<uint8_t>> mDataByClientId GUARDED_BY(mLock);
+ size_t mTaskCount GUARDED_BY(mLock) = 0;
+ std::condition_variable mCv;
+};
+
+class RemoteAccessServiceUnitTest : public ::testing::Test {
+ public:
+ virtual void SetUp() override {
+ mGrpcWakeupClientStub = std::make_unique<MockGrpcClientStub>();
+ mService = ndk::SharedRefBase::make<RemoteAccessService>(mGrpcWakeupClientStub.get());
+ }
+
+ MockGrpcClientStub* getGrpcWakeupClientStub() { return mGrpcWakeupClientStub.get(); }
+
+ RemoteAccessService* getService() { return mService.get(); }
+
+ void setRetryWaitInMs(size_t retryWaitInMs) { mService->setRetryWaitInMs(retryWaitInMs); }
+
+ private:
+ std::unique_ptr<MockGrpcClientStub> mGrpcWakeupClientStub;
+ std::shared_ptr<RemoteAccessService> mService;
+ MockClientReader<GetRemoteTasksResponse>* mMockTaskReader;
+};
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetWakeupServiceName) {
+ std::string serviceName;
+
+ ScopedAStatus status = getService()->getWakeupServiceName(&serviceName);
+
+ EXPECT_TRUE(status.isOk());
+ EXPECT_EQ(serviceName, "com.google.vehicle.wakeup");
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestNotifyApStateChangeWakeupRequired) {
+ bool isWakeupRequired = false;
+ EXPECT_CALL(*getGrpcWakeupClientStub(), NotifyWakeupRequired)
+ .WillOnce([&isWakeupRequired]([[maybe_unused]] ClientContext* context,
+ const NotifyWakeupRequiredRequest& request,
+ [[maybe_unused]] NotifyWakeupRequiredResponse* response) {
+ isWakeupRequired = request.iswakeuprequired();
+ return Status();
+ });
+
+ ApState newState = {
+ .isWakeupRequired = true,
+ };
+ ScopedAStatus status = getService()->notifyApStateChange(newState);
+
+ EXPECT_TRUE(status.isOk());
+ EXPECT_TRUE(isWakeupRequired);
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasks) {
+ GetRemoteTasksResponse response1;
+ std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+ response1.set_clientid("1");
+ response1.set_data(testData.data(), testData.size());
+ GetRemoteTasksResponse response2;
+ response2.set_clientid("2");
+ std::shared_ptr<FakeRemoteTaskCallback> callback =
+ ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+ ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+ .WillByDefault(
+ [response1, response2]([[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request) {
+ // mockReader ownership will be transferred to the client so we don't own it
+ // here.
+ MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+ new MockClientReader<GetRemoteTasksResponse>();
+ EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+ EXPECT_CALL(*mockClientReader, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true)))
+ .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true)))
+ .WillRepeatedly(Return(false));
+ return mockClientReader;
+ });
+
+ getService()->setRemoteTaskCallback(callback);
+ // Start the long live connection to receive tasks.
+ ApState newState = {
+ .isReadyForRemoteTask = true,
+ };
+ ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+ ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+ << "Did not receive enough tasks";
+ EXPECT_EQ(callback->getData("1"), testData);
+ EXPECT_EQ(callback->getData("2"), std::vector<uint8_t>());
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksRetryConnection) {
+ GetRemoteTasksResponse response;
+ std::shared_ptr<FakeRemoteTaskCallback> callback =
+ ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+ ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+ .WillByDefault([response]([[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request) {
+ // mockReader ownership will be transferred to the client so we don't own it here.
+ MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+ new MockClientReader<GetRemoteTasksResponse>();
+ EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+ // Connection fails after receiving one task. Should retry after some time.
+ EXPECT_CALL(*mockClientReader, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(response), Return(true)))
+ .WillRepeatedly(Return(false));
+ return mockClientReader;
+ });
+
+ getService()->setRemoteTaskCallback(callback);
+ setRetryWaitInMs(100);
+ // Start the long live connection to receive tasks.
+ ApState newState = {
+ .isReadyForRemoteTask = true,
+ };
+ ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+ ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+ << "Did not receive enough tasks";
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksDefaultNotReady) {
+ GetRemoteTasksResponse response1;
+ std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+ response1.set_clientid("1");
+ response1.set_data(testData.data(), testData.size());
+ GetRemoteTasksResponse response2;
+ response2.set_clientid("2");
+ std::shared_ptr<FakeRemoteTaskCallback> callback =
+ ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+ EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(0);
+
+ // Default state is not ready for remote tasks, so no callback will be called.
+ getService()->setRemoteTaskCallback(callback);
+
+ std::this_thread::sleep_for(std::chrono::milliseconds(100));
+}
+
+TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksNotReadyAfterReady) {
+ GetRemoteTasksResponse response1;
+ std::vector<uint8_t> testData = {0xde, 0xad, 0xbe, 0xef};
+ response1.set_clientid("1");
+ response1.set_data(testData.data(), testData.size());
+ GetRemoteTasksResponse response2;
+ response2.set_clientid("2");
+ std::shared_ptr<FakeRemoteTaskCallback> callback =
+ ndk::SharedRefBase::make<FakeRemoteTaskCallback>();
+
+ ON_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw)
+ .WillByDefault(
+ [response1, response2]([[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request) {
+ // mockReader ownership will be transferred to the client so we don't own it
+ // here.
+ MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+ new MockClientReader<GetRemoteTasksResponse>();
+ EXPECT_CALL(*mockClientReader, Finish()).WillOnce(Return(Status::OK));
+ EXPECT_CALL(*mockClientReader, Read(_))
+ .WillOnce(DoAll(SetArgPointee<0>(response1), Return(true)))
+ .WillOnce(DoAll(SetArgPointee<0>(response2), Return(true)))
+ .WillRepeatedly(Return(false));
+ return mockClientReader;
+ });
+ // Should only be called once when is is ready for remote task.
+ EXPECT_CALL(*getGrpcWakeupClientStub(), GetRemoteTasksRaw).Times(1);
+
+ getService()->setRemoteTaskCallback(callback);
+ setRetryWaitInMs(100);
+ // Start the long live connection to receive tasks.
+ ApState newState = {
+ .isReadyForRemoteTask = true,
+ };
+ ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+ ASSERT_TRUE(callback->wait(/*taskCount=*/2, /*timeoutInSec=*/10))
+ << "Did not receive enough tasks";
+
+ // Stop the long live connection.
+ newState.isReadyForRemoteTask = false;
+ ASSERT_TRUE(getService()->notifyApStateChange(newState).isOk());
+
+ // Wait for the retry delay, but the loop should already exit.
+ std::this_thread::sleep_for(std::chrono::milliseconds(150));
+}
+
+} // namespace remoteaccess
+} // namespace automotive
+} // namespace hardware
+} // namespace android