diff --git a/automotive/remoteaccess/impl/default/client/Android.bp b/automotive/remoteaccess/impl/default/client/Android.bp new file mode 100644 index 0000000000..6327637941 --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/Android.bp @@ -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. + */ + +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", + ], +} + +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/impl/default/client/include/RemoteAccessService.h b/automotive/remoteaccess/impl/default/client/include/RemoteAccessService.h new file mode 100644 index 0000000000..806440a2df --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/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 +#include +#include +#include +#include + +#include +#include + +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 + mRemoteTaskCallback GUARDED_BY(mLock); + std::unique_ptr 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/impl/default/client/remoteaccess-default-service.rc b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.rc new file mode 100644 index 0000000000..b7a9cdc82a --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/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/impl/default/client/remoteaccess-default-service.xml b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.xml new file mode 100644 index 0000000000..d050a1b646 --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/remoteaccess-default-service.xml @@ -0,0 +1,7 @@ + + + android.hardware.automotive.remoteaccess + 1 + IRemoteAccess/default + + diff --git a/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp b/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp new file mode 100644 index 0000000000..743189854b --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/src/RemoteAccessImpl.cpp @@ -0,0 +1,54 @@ +/* + * 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 +#include +#include + +int main(int /* argc */, char* /* argv */[]) { + ALOGI("Registering RemoteAccessService as service..."); + + // TODO(b/241483300): Create GrpcClientStub here. + auto service = ndk::SharedRefBase::make< + android::hardware::automotive::remoteaccess::RemoteAccessService>(nullptr); + + binder_exception_t err = AServiceManager_addService( + service->asBinder().get(), "android.hardware.automotive.remote.IRemoteAccess/default"); + if (err != EX_NONE) { + ALOGE("failed to register android.hardware.automotive.remote.IRemoteAccess service, " + "exception: %d", + err); + return 1; + } + + if (!ABinderProcess_setThreadPoolMaxThreadCount(1)) { + ALOGE("%s", "failed to set thread pool max thread count"); + return 1; + } + ABinderProcess_startThreadPool(); + + ALOGI("RemoteAccess service Ready"); + + ABinderProcess_joinThreadPool(); + + ALOGW("Should not reach here"); + + return 0; +} diff --git a/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp b/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp new file mode 100644 index 0000000000..6b97840999 --- /dev/null +++ b/automotive/remoteaccess/impl/default/client/src/RemoteAccessService.cpp @@ -0,0 +1,185 @@ +/* + * 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 +#include +#include +#include +#include + +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 stringToBytes(const std::string& s) { + const char* data = s.data(); + return std::vector(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 lockGuard(mStartStopTaskLoopLock); + if (mTaskLoopRunning) { + return; + } + + mThread = std::thread([this]() { runTaskLoop(); }); + + mTaskLoopRunning = true; +} + +void RemoteAccessService::maybeStopTaskLoop() { + std::lock_guard lockGuard(mStartStopTaskLoopLock); + if (!mTaskLoopRunning) { + return; + } + + { + std::lock_guard 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> reader; + while (true) { + { + std::lock_guard lockGuard(mLock); + mGetRemoteTasksContext.reset(new ClientContext()); + reader = mGrpcStub->GetRemoteTasks(mGetRemoteTasksContext.get(), request); + } + GetRemoteTasksResponse response; + while (reader->Read(&response)) { + std::shared_ptr callback; + { + std::lock_guard lockGuard(mLock); + callback = mRemoteTaskCallback; + } + if (callback == nullptr) { + continue; + } + 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& callback) { + std::lock_guard lockGuard(mLock); + mRemoteTaskCallback = callback; + return ScopedAStatus::ok(); +} + +ScopedAStatus RemoteAccessService::clearRemoteTaskCallback() { + std::lock_guard 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/grpc/proto/Android.bp b/automotive/remoteaccess/impl/default/proto/Android.bp similarity index 94% rename from automotive/remoteaccess/grpc/proto/Android.bp rename to automotive/remoteaccess/impl/default/proto/Android.bp index c882f55446..d3c75a65f2 100644 --- a/automotive/remoteaccess/grpc/proto/Android.bp +++ b/automotive/remoteaccess/impl/default/proto/Android.bp @@ -24,7 +24,9 @@ genrule { ], out: [ "wakeup_client.pb.h", + "wakeup_client.grpc.pb.h", ], + vendor: true, } genrule { @@ -39,7 +41,9 @@ genrule { ], out: [ "wakeup_client.pb.cc", + "wakeup_client.grpc.pb.cc", ], + vendor: true, } cc_library_static { diff --git a/automotive/remoteaccess/grpc/proto/wakeup_client.proto b/automotive/remoteaccess/impl/default/proto/wakeup_client.proto similarity index 100% rename from automotive/remoteaccess/grpc/proto/wakeup_client.proto rename to automotive/remoteaccess/impl/default/proto/wakeup_client.proto diff --git a/automotive/remoteaccess/impl/default/test/Android.bp b/automotive/remoteaccess/impl/default/test/Android.bp new file mode 100644 index 0000000000..e92440f55f --- /dev/null +++ b/automotive/remoteaccess/impl/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/impl/default/test/RemoteAccessServiceUnitTest.cpp b/automotive/remoteaccess/impl/default/test/RemoteAccessServiceUnitTest.cpp new file mode 100644 index 0000000000..11523f69db --- /dev/null +++ b/automotive/remoteaccess/impl/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 +#include +#include +#include +#include +#include +#include +#include + +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*, 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*, AsyncGetRemoteTasksRaw, + (ClientContext * context, const GetRemoteTasksRequest& request, CompletionQueue* cq, + void* tag)); + MOCK_METHOD(ClientAsyncReaderInterface*, PrepareAsyncGetRemoteTasksRaw, + (ClientContext * context, const GetRemoteTasksRequest& request, + CompletionQueue* cq)); + MOCK_METHOD(ClientAsyncResponseReaderInterface*, + AsyncNotifyWakeupRequiredRaw, + (ClientContext * context, const NotifyWakeupRequiredRequest& request, + CompletionQueue* cq)); + MOCK_METHOD(ClientAsyncResponseReaderInterface*, + PrepareAsyncNotifyWakeupRequiredRaw, + (ClientContext * context, const NotifyWakeupRequiredRequest& request, + CompletionQueue* cq)); +}; + +class FakeRemoteTaskCallback : public BnRemoteTaskCallback { + public: + ScopedAStatus onRemoteTaskRequested(const std::string& clientId, + const std::vector& data) override { + std::lock_guard lockGuard(mLock); + mDataByClientId[clientId] = data; + mTaskCount++; + mCv.notify_all(); + return ScopedAStatus::ok(); + } + + std::vector getData(const std::string& clientId) { return mDataByClientId[clientId]; } + + bool wait(size_t taskCount, size_t timeoutInSec) { + std::unique_lock 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> 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(); + mService = ndk::SharedRefBase::make(mGrpcWakeupClientStub.get()); + } + + MockGrpcClientStub* getGrpcWakeupClientStub() { return mGrpcWakeupClientStub.get(); } + + RemoteAccessService* getService() { return mService.get(); } + + void setRetryWaitInMs(size_t retryWaitInMs) { mService->setRetryWaitInMs(retryWaitInMs); } + + private: + std::unique_ptr mGrpcWakeupClientStub; + std::shared_ptr mService; + MockClientReader* 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 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 callback = + ndk::SharedRefBase::make(); + + 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* mockClientReader = + new MockClientReader(); + 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()); +} + +TEST_F(RemoteAccessServiceUnitTest, TestGetRemoteTasksRetryConnection) { + GetRemoteTasksResponse response; + std::shared_ptr callback = + ndk::SharedRefBase::make(); + + 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* mockClientReader = + new MockClientReader(); + 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 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 callback = + ndk::SharedRefBase::make(); + + 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 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 callback = + ndk::SharedRefBase::make(); + + 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* mockClientReader = + new MockClientReader(); + 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