Add remote access HAL interface and ref impl.
Add remote access HAL interface and reference implementation. This
CL is a merge of multiple CLs commited in internal master.
Test: Presubmit
Bug: 241170646
Change-Id: I55ba98015055d779a362cac05a9f68650b5b92ab
Merged-In: I332221b303274463dfa5b46d78cf0d81f6045e4b
diff --git a/automotive/remoteaccess/hal/default/Android.bp b/automotive/remoteaccess/hal/default/Android.bp
new file mode 100644
index 0000000..a2bf86c
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/Android.bp
@@ -0,0 +1,109 @@
+/*
+ * 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: [
+ "libbase",
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ defaults: [
+ "vhalclient_defaults",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"",
+ ],
+}
+
+cc_library {
+ name: "RemoteAccessService",
+ vendor_available: 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",
+ "libvhalclient",
+ ],
+ defaults: [
+ "vhalclient_defaults",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "libcutils",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ ],
+}
+
+cc_fuzz {
+ name: "android.hardware.automotive.remoteaccess@V1-default-service.aidl_fuzzer",
+ srcs: ["fuzzer/fuzzer.cpp"],
+ whole_static_libs: [
+ "RemoteAccessService",
+ ],
+ static_libs: [
+ "libgtest",
+ "libgmock",
+ ],
+ shared_libs: [
+ "libbase",
+ "libbinder_ndk",
+ "liblog",
+ "libutils",
+ "libgrpc++",
+ "libprotobuf-cpp-full",
+ ],
+ defaults: [
+ "vhalclient_defaults",
+ "service_fuzzer_defaults",
+ ],
+ cflags: [
+ "-Wno-unused-parameter",
+ "-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"",
+ ],
+ fuzz_config: {
+ cc: [
+ "shanyu@google.com",
+ ],
+ },
+}
diff --git a/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp b/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp
new file mode 100644
index 0000000..292c80e
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/fuzzer/fuzzer.cpp
@@ -0,0 +1,101 @@
+/*
+ * 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 <fuzzbinder/libbinder_ndk_driver.h>
+#include <fuzzer/FuzzedDataProvider.h>
+#include <gmock/gmock.h>
+#include <grpcpp/test/mock_stream.h>
+#include <wakeup_client.grpc.pb.h>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+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 ::testing::_;
+using ::testing::Return;
+
+class MockGrpcClientStub : public WakeupClient::StubInterface {
+ public:
+ ClientReaderInterface<GetRemoteTasksResponse>* GetRemoteTasksRaw(
+ [[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request) override {
+ MockClientReader<GetRemoteTasksResponse>* mockClientReader =
+ new MockClientReader<GetRemoteTasksResponse>();
+ ON_CALL(*mockClientReader, Finish()).WillByDefault(Return(Status::OK));
+ ON_CALL(*mockClientReader, Read(_)).WillByDefault(Return(false));
+ return mockClientReader;
+ }
+
+ Status NotifyWakeupRequired([[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const NotifyWakeupRequiredRequest& request,
+ [[maybe_unused]] NotifyWakeupRequiredResponse* response) {
+ return Status::OK;
+ }
+
+ // Async methods which we do not care.
+ ClientAsyncReaderInterface<GetRemoteTasksResponse>* AsyncGetRemoteTasksRaw(
+ [[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request,
+ [[maybe_unused]] CompletionQueue* cq, [[maybe_unused]] void* tag) {
+ return nullptr;
+ }
+
+ ClientAsyncReaderInterface<GetRemoteTasksResponse>* PrepareAsyncGetRemoteTasksRaw(
+ [[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const GetRemoteTasksRequest& request,
+ [[maybe_unused]] CompletionQueue* cq) {
+ return nullptr;
+ }
+
+ ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>* AsyncNotifyWakeupRequiredRaw(
+ [[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const NotifyWakeupRequiredRequest& request,
+ [[maybe_unused]] CompletionQueue* cq) {
+ return nullptr;
+ }
+
+ ClientAsyncResponseReaderInterface<NotifyWakeupRequiredResponse>*
+ PrepareAsyncNotifyWakeupRequiredRaw([[maybe_unused]] ClientContext* context,
+ [[maybe_unused]] const NotifyWakeupRequiredRequest& request,
+ [[maybe_unused]] CompletionQueue* c) {
+ return nullptr;
+ }
+};
+
+} // namespace remoteaccess
+} // namespace automotive
+} // namespace hardware
+} // namespace android
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ android::hardware::automotive::remoteaccess::MockGrpcClientStub stub;
+ std::shared_ptr<android::hardware::automotive::remoteaccess::RemoteAccessService> service =
+ ndk::SharedRefBase::make<
+ android::hardware::automotive::remoteaccess::RemoteAccessService>(&stub);
+ android::fuzzService(service->asBinder().get(), FuzzedDataProvider(data, size));
+
+ return 0;
+}
diff --git a/automotive/remoteaccess/hal/default/include/RemoteAccessService.h b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h
new file mode 100644
index 0000000..74c2af4
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/include/RemoteAccessService.h
@@ -0,0 +1,116 @@
+/*
+ * 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 <IVhalClient.h>
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteAccess.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteTaskCallback.h>
+#include <aidl/android/hardware/automotive/remoteaccess/IRemoteTaskCallback.h>
+#include <android-base/thread_annotations.h>
+#include <android/binder_auto_utils.h>
+#include <utils/SystemClock.h>
+#include <wakeup_client.grpc.pb.h>
+
+#include <string>
+#include <thread>
+
+namespace android {
+namespace hardware {
+namespace automotive {
+namespace remoteaccess {
+
+// A IRemoteTaskCallback implementation for debug purpose.
+class DebugRemoteTaskCallback final
+ : public aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback {
+ public:
+ DebugRemoteTaskCallback() { mStartTimeMillis = android::uptimeMillis(); };
+
+ ndk::ScopedAStatus onRemoteTaskRequested(const std::string& clientId,
+ const std::vector<uint8_t>& data) override;
+ std::string printTasks();
+
+ private:
+ struct TaskData {
+ std::string clientId;
+ std::vector<uint8_t> data;
+ };
+
+ std::mutex mLock;
+ int64_t mStartTimeMillis;
+ std::vector<TaskData> mTasks;
+};
+
+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;
+
+ binder_status_t dump(int fd, const char** args, uint32_t numArgs) override;
+
+ private:
+ // For testing.
+ friend class RemoteAccessServiceUnitTest;
+
+ static bool checkDumpPermission();
+
+ 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;
+ std::shared_ptr<DebugRemoteTaskCallback> mDebugCallback;
+
+ void runTaskLoop();
+ void maybeStartTaskLoop();
+ void maybeStopTaskLoop();
+ ndk::ScopedAStatus getDeviceIdWithClient(
+ android::frameworks::automotive::vhal::IVhalClient& client, std::string* deviceId);
+
+ void setRetryWaitInMs(size_t retryWaitInMs) { mRetryWaitInMs = retryWaitInMs; }
+ void dumpHelp(int fd);
+};
+
+} // 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..3e0dba1
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/proto/Android.bp
@@ -0,0 +1,79 @@
+// 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",
+ ],
+}
+
+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",
+ ],
+}
+
+cc_library_static {
+ name: "wakeup_client_protos",
+ vendor_available: 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..5cd58d3
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/src/RemoteAccessService.cpp
@@ -0,0 +1,355 @@
+/*
+ * 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 <VehicleUtils.h>
+#include <aidl/android/hardware/automotive/vehicle/VehicleProperty.h>
+#include <android-base/stringprintf.h>
+#include <android/binder_status.h>
+#include <grpc++/grpc++.h>
+#include <private/android_filesystem_config.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 ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
+using ::android::base::ScopedLockAssertion;
+using ::android::base::StringAppendF;
+using ::android::base::StringPrintf;
+using ::android::frameworks::automotive::vhal::IVhalClient;
+using ::android::hardware::automotive::vehicle::toInt;
+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";
+constexpr char COMMAND_SET_AP_STATE[] = "--set-ap-state";
+constexpr char COMMAND_START_DEBUG_CALLBACK[] = "--start-debug-callback";
+constexpr char COMMAND_STOP_DEBUG_CALLBACK[] = "--stop-debug-callback";
+constexpr char COMMAND_SHOW_TASK[] = "--show-task";
+constexpr char COMMAND_GET_DEVICE_ID[] = "--get-device-id";
+
+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());
+}
+
+std::string printBytes(const std::vector<uint8_t>& bytes) {
+ std::string s;
+ for (size_t i = 0; i < bytes.size(); i++) {
+ StringAppendF(&s, "%02x", bytes[i]);
+ }
+ return s;
+}
+
+bool checkBoolFlag(const char* flag) {
+ return !strcmp(flag, "1") || !strcmp(flag, "0");
+}
+
+void dprintErrorStatus(int fd, const char* detail, const ScopedAStatus& status) {
+ dprintf(fd, "%s, code: %d, error: %s\n", detail, status.getStatus(), status.getMessage());
+}
+
+} // 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) {
+#ifndef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
+ auto vhalClient = IVhalClient::tryCreate();
+ if (vhalClient == nullptr) {
+ ALOGE("Failed to connect to VHAL");
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ /*errorCode=*/0, "Failed to connect to VHAL to get device ID");
+ }
+ return getDeviceIdWithClient(*vhalClient.get(), deviceId);
+#else
+ // Don't use VHAL client in fuzzing since IPC is not allowed.
+ return ScopedAStatus::ok();
+#endif
+}
+
+ScopedAStatus RemoteAccessService::getDeviceIdWithClient(IVhalClient& vhalClient,
+ std::string* deviceId) {
+ auto result = vhalClient.getValueSync(
+ *vhalClient.createHalPropValue(toInt(VehicleProperty::INFO_VIN)));
+ if (!result.ok()) {
+ return ScopedAStatus::fromServiceSpecificErrorWithMessage(
+ /*errorCode=*/0,
+ ("failed to get INFO_VIN from VHAL: " + result.error().message()).c_str());
+ }
+ *deviceId = (*result)->getStringValue();
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::getWakeupServiceName(std::string* wakeupServiceName) {
+ *wakeupServiceName = WAKEUP_SERVICE_NAME;
+ return ScopedAStatus::ok();
+}
+
+ScopedAStatus RemoteAccessService::setRemoteTaskCallback(
+ 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();
+}
+
+bool RemoteAccessService::checkDumpPermission() {
+ uid_t uid = AIBinder_getCallingUid();
+ return uid == AID_ROOT || uid == AID_SHELL || uid == AID_SYSTEM;
+}
+
+void RemoteAccessService::dumpHelp(int fd) {
+ dprintf(fd, "%s",
+ (std::string("RemoteAccess HAL debug interface, Usage: \n") + COMMAND_SET_AP_STATE +
+ " [0/1](isReadyForRemoteTask) [0/1](isWakeupRequired) Set the new AP state\n" +
+ COMMAND_START_DEBUG_CALLBACK +
+ " Start a debug callback that will record the received tasks\n" +
+ COMMAND_STOP_DEBUG_CALLBACK + " Stop the debug callback\n" + COMMAND_SHOW_TASK +
+ " Show tasks received by debug callback\n" + COMMAND_GET_DEVICE_ID +
+ " Get device id\n")
+ .c_str());
+}
+
+binder_status_t RemoteAccessService::dump(int fd, const char** args, uint32_t numArgs) {
+ if (!checkDumpPermission()) {
+ dprintf(fd, "Caller must be root, system or shell\n");
+ return STATUS_PERMISSION_DENIED;
+ }
+
+ if (numArgs == 0) {
+ dumpHelp(fd);
+ return STATUS_OK;
+ }
+
+ if (!strcmp(args[0], COMMAND_SET_AP_STATE)) {
+ if (numArgs < 3) {
+ dumpHelp(fd);
+ return STATUS_OK;
+ }
+ ApState apState = {};
+ const char* remoteTaskFlag = args[1];
+ if (!strcmp(remoteTaskFlag, "1") && !strcmp(remoteTaskFlag, "0")) {
+ dumpHelp(fd);
+ return STATUS_OK;
+ }
+ if (!checkBoolFlag(args[1])) {
+ dumpHelp(fd);
+ return STATUS_OK;
+ }
+ if (!strcmp(args[1], "1")) {
+ apState.isReadyForRemoteTask = true;
+ }
+ if (!checkBoolFlag(args[2])) {
+ dumpHelp(fd);
+ return STATUS_OK;
+ }
+ if (!strcmp(args[2], "1")) {
+ apState.isWakeupRequired = true;
+ }
+ auto status = notifyApStateChange(apState);
+ if (!status.isOk()) {
+ dprintErrorStatus(fd, "Failed to set AP state", status);
+ } else {
+ dprintf(fd, "successfully set the new AP state\n");
+ }
+ } else if (!strcmp(args[0], COMMAND_START_DEBUG_CALLBACK)) {
+ mDebugCallback = ndk::SharedRefBase::make<DebugRemoteTaskCallback>();
+ setRemoteTaskCallback(mDebugCallback);
+ dprintf(fd, "Debug callback registered\n");
+ } else if (!strcmp(args[0], COMMAND_STOP_DEBUG_CALLBACK)) {
+ if (mDebugCallback) {
+ mDebugCallback.reset();
+ }
+ clearRemoteTaskCallback();
+ dprintf(fd, "Debug callback unregistered\n");
+ } else if (!strcmp(args[0], COMMAND_SHOW_TASK)) {
+ if (mDebugCallback) {
+ dprintf(fd, "%s", mDebugCallback->printTasks().c_str());
+ } else {
+ dprintf(fd, "Debug callback is not currently used, use \"%s\" first.\n",
+ COMMAND_START_DEBUG_CALLBACK);
+ }
+ } else if (!strcmp(args[0], COMMAND_GET_DEVICE_ID)) {
+ std::string deviceId;
+ auto status = getDeviceId(&deviceId);
+ if (!status.isOk()) {
+ dprintErrorStatus(fd, "Failed to get device ID", status);
+ } else {
+ dprintf(fd, "Device Id: %s\n", deviceId.c_str());
+ }
+ } else {
+ dumpHelp(fd);
+ }
+
+ return STATUS_OK;
+}
+
+ScopedAStatus DebugRemoteTaskCallback::onRemoteTaskRequested(const std::string& clientId,
+ const std::vector<uint8_t>& data) {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ mTasks.push_back({
+ .clientId = clientId,
+ .data = data,
+ });
+ return ScopedAStatus::ok();
+}
+
+std::string DebugRemoteTaskCallback::printTasks() {
+ std::lock_guard<std::mutex> lockGuard(mLock);
+ std::string s = StringPrintf("Received %zu tasks in %f seconds", mTasks.size(),
+ (android::uptimeMillis() - mStartTimeMillis) / 1000.);
+ for (size_t i = 0; i < mTasks.size(); i++) {
+ StringAppendF(&s, "Client Id: %s, Data: %s\n", mTasks[i].clientId.c_str(),
+ printBytes(mTasks[i].data).c_str());
+ }
+ return s;
+}
+
+} // 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..227175a
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/test/Android.bp
@@ -0,0 +1,47 @@
+// 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: [
+ "libbase",
+ "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",
+ ],
+ defaults: [
+ "vhalclient_defaults",
+ ],
+ 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..a220aeb
--- /dev/null
+++ b/automotive/remoteaccess/hal/default/test/RemoteAccessServiceUnitTest.cpp
@@ -0,0 +1,375 @@
+/*
+ * 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 <AidlHalPropValue.h>
+#include <IVhalClient.h>
+#include <aidl/android/hardware/automotive/remoteaccess/ApState.h>
+#include <aidl/android/hardware/automotive/remoteaccess/BnRemoteTaskCallback.h>
+#include <aidl/android/hardware/automotive/vehicle/VehiclePropValue.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 {
+
+namespace {
+
+using ::android::base::ScopedLockAssertion;
+using ::android::frameworks::automotive::vhal::AidlHalPropValue;
+using ::android::frameworks::automotive::vhal::IHalPropConfig;
+using ::android::frameworks::automotive::vhal::IHalPropValue;
+using ::android::frameworks::automotive::vhal::ISubscriptionCallback;
+using ::android::frameworks::automotive::vhal::ISubscriptionClient;
+using ::android::frameworks::automotive::vhal::IVhalClient;
+
+using ::aidl::android::hardware::automotive::remoteaccess::ApState;
+using ::aidl::android::hardware::automotive::remoteaccess::BnRemoteTaskCallback;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
+
+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;
+
+constexpr char kTestVin[] = "test_VIN";
+
+} // namespace
+
+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 FakeVhalClient final : public android::frameworks::automotive::vhal::IVhalClient {
+ public:
+ template <class T>
+ using VhalClientResult = android::hardware::automotive::vehicle::VhalResult<T>;
+
+ inline bool isAidlVhal() { return true; }
+
+ VhalClientResult<std::unique_ptr<IHalPropValue>> getValueSync(
+ const IHalPropValue& requestValue) override {
+ auto propValue = std::make_unique<AidlHalPropValue>(requestValue.getPropId());
+ propValue->setStringValue(kTestVin);
+ return propValue;
+ }
+
+ std::unique_ptr<IHalPropValue> createHalPropValue(int32_t propId) override {
+ return std::make_unique<AidlHalPropValue>(propId);
+ }
+
+ // Functions we do not care.
+ std::unique_ptr<IHalPropValue> createHalPropValue([[maybe_unused]] int32_t propId,
+ [[maybe_unused]] int32_t areaId) override {
+ return nullptr;
+ }
+
+ void getValue([[maybe_unused]] const IHalPropValue& requestValue,
+ [[maybe_unused]] std::shared_ptr<GetValueCallbackFunc> callback) override {}
+
+ void setValue([[maybe_unused]] const IHalPropValue& requestValue,
+ [[maybe_unused]] std::shared_ptr<SetValueCallbackFunc> callback) override {}
+
+ VhalClientResult<void> setValueSync([[maybe_unused]] const IHalPropValue& requestValue) {
+ return {};
+ }
+
+ VhalClientResult<void> addOnBinderDiedCallback(
+ [[maybe_unused]] std::shared_ptr<OnBinderDiedCallbackFunc> callback) override {
+ return {};
+ }
+
+ VhalClientResult<void> removeOnBinderDiedCallback(
+ [[maybe_unused]] std::shared_ptr<OnBinderDiedCallbackFunc> callback) override {
+ return {};
+ }
+
+ VhalClientResult<std::vector<std::unique_ptr<IHalPropConfig>>> getAllPropConfigs() override {
+ return std::vector<std::unique_ptr<IHalPropConfig>>();
+ }
+
+ VhalClientResult<std::vector<std::unique_ptr<IHalPropConfig>>> getPropConfigs(
+ [[maybe_unused]] std::vector<int32_t> propIds) override {
+ return std::vector<std::unique_ptr<IHalPropConfig>>();
+ }
+
+ std::unique_ptr<ISubscriptionClient> getSubscriptionClient(
+ [[maybe_unused]] std::shared_ptr<ISubscriptionCallback> callback) override {
+ return nullptr;
+ }
+};
+
+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); }
+
+ ScopedAStatus getDeviceIdWithClient(IVhalClient& vhalClient, std::string* deviceId) {
+ return mService->getDeviceIdWithClient(vhalClient, deviceId);
+ }
+
+ private:
+ std::unique_ptr<MockGrpcClientStub> mGrpcWakeupClientStub;
+ std::shared_ptr<RemoteAccessService> mService;
+};
+
+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));
+}
+
+TEST_F(RemoteAccessServiceUnitTest, testGetDeviceId) {
+ std::string deviceId;
+
+ FakeVhalClient vhalClient;
+
+ ASSERT_TRUE(getDeviceIdWithClient(vhalClient, &deviceId).isOk());
+ ASSERT_EQ(deviceId, kTestVin);
+}
+
+} // namespace remoteaccess
+} // namespace automotive
+} // namespace hardware
+} // namespace android