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
This commit is contained in:
Yu Shan 2022-10-25 18:01:05 -07:00
parent c1c823a9c1
commit 7a5283fda1
25 changed files with 2410 additions and 0 deletions

View file

@ -0,0 +1,39 @@
// 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: ["hardware_interfaces_license"],
}
aidl_interface {
name: "android.hardware.automotive.remoteaccess",
vendor_available: true,
srcs: [
"android/hardware/automotive/remoteaccess/**/*.aidl",
],
stability: "vintf",
backend: {
cpp: {
enabled: false,
},
java: {
sdk_version: "module_current",
min_sdk_version: "31",
apex_available: [
"//apex_available:platform",
"com.android.car.framework",
],
},
},
}

View file

@ -0,0 +1,2 @@
ericjeong@google.com
shanyu@google.com

View file

@ -0,0 +1,39 @@
/*
* 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.automotive.remoteaccess;
@VintfStability
parcelable ApState {
boolean isReadyForRemoteTask;
boolean isWakeupRequired;
}

View file

@ -0,0 +1,42 @@
/*
* 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.automotive.remoteaccess;
@VintfStability
interface IRemoteAccess {
String getDeviceId();
String getWakeupServiceName();
void setRemoteTaskCallback(android.hardware.automotive.remoteaccess.IRemoteTaskCallback callback);
void clearRemoteTaskCallback();
void notifyApStateChange(in android.hardware.automotive.remoteaccess.ApState state);
}

View file

@ -0,0 +1,38 @@
/*
* 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.
*/
///////////////////////////////////////////////////////////////////////////////
// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. //
///////////////////////////////////////////////////////////////////////////////
// This file is a snapshot of an AIDL file. Do not edit it manually. There are
// two cases:
// 1). this is a frozen version file - do not edit this in any case.
// 2). this is a 'current' file. If you make a backwards compatible change to
// the interface (from the latest frozen version), the build system will
// prompt you to update this file with `m <name>-update-api`.
//
// You must not make a backward incompatible change to any AIDL file built
// with the aidl_interface module type with versions property set. The module
// type is used to build AIDL files in a way that they can be used across
// independently updatable components of the system. If a device is shipped
// with such a backward incompatible change, it has a high risk of breaking
// later when a module using the interface is updated, e.g., Mainline modules.
package android.hardware.automotive.remoteaccess;
@VintfStability
interface IRemoteTaskCallback {
oneway void onRemoteTaskRequested(String clientId, in byte[] data);
}

View file

@ -0,0 +1,38 @@
/*
* 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 android.hardware.automotive.remoteaccess;
@VintfStability
parcelable ApState {
/**
* Whether AP (application processor) is ready to receive remote tasks.
*
* If this is true. AP is powered on and the car service is ready to handle
* remote tasks.
*/
boolean isReadyForRemoteTask;
/**
* Whether AP (application processor) needs to be woken up.
*
* While the AP is shutting down, this will be set to false to prevent the
* wakeup signal to interrupt the shutdown process. At the last step of the
* shutdown process, this will be set to true so that AP will be waken
* up when task arrives. After AP starts up, this will be set to false
* to prevent unnecessary wakeup signal.
*/
boolean isWakeupRequired;
}

View file

@ -0,0 +1,88 @@
/*
* 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 android.hardware.automotive.remoteaccess;
import android.hardware.automotive.remoteaccess.ApState;
import android.hardware.automotive.remoteaccess.IRemoteTaskCallback;
/**
* Interface representing a remote wakeup client.
*
* A wakeup client is a binary outside Android framework that communicates with
* a wakeup server and receives wake up command.
*/
@VintfStability
interface IRemoteAccess {
/**
* Gets a unique device ID that could be recognized by wake up server.
*
* This device ID is provisioned during car production and is registered
* with the wake up server.
*
* @return a unique device ID.
*/
String getDeviceId();
/**
* Gets the name for the remote wakeup server.
*
* This name will be provided to remote task server during registration
* and used by remote task server to find the remote wakeup server to
* use for waking up the device. This name must be pre-negotiated between
* the remote wakeup server/client and the remote task server/client and
* must be unique. We recommend the format to be a human readable string
* with reverse domain name notation (reverse-DNS), e.g.
* "com.google.vehicle.wakeup".
*/
String getWakeupServiceName();
/**
* Sets a callback to be called when a remote task is requested.
*
* @param callback A callback to be called when a remote task is requested.
*/
void setRemoteTaskCallback(IRemoteTaskCallback callback);
/**
* Clears a previously set remote task callback.
*
* If no callback was set, this operation is no-op.
*/
void clearRemoteTaskCallback();
/**
* Notifies whether AP is ready to receive remote tasks.
*
* <p>Wakeup client should store and use this state until a new call with a
* different state arrives.
*
* <p>If {@code isReadyForRemoteTask} is true, the wakeup client may send
* the task received from the server to AP immediately.
*
* <p>If {@code isReadyForRemoteTask} is false, it must store the received
* remote tasks and wait until AP is ready to receive tasks. If it takes too
* long for AP to become ready, the task must be reported to remote task
* server as failed. Implementation must make sure no duplicate tasks are
* delivered to AP.
*
* <p>If {@code isWakeupRequired} is true, it must try to wake up AP when a
* remote task arrives or when there are pending requests.
*
* <p>If {@code isWakeupRequired} is false, it must not try to wake up AP.
*/
void notifyApStateChange(in ApState state);
}

View file

@ -0,0 +1,31 @@
/*
* 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 android.hardware.automotive.remoteaccess;
/**
* The callback interface for car service to receive tasks from wakup client.
*/
@VintfStability
interface IRemoteTaskCallback {
/**
* A callback that is called when a remote task is requested.
*
* @param clientId An ID to uniquely identify a remote task client.
* @param data Opaque task data passed to the remote task client.
*/
oneway void onRemoteTaskRequested(String clientId, in byte[] data);
}

View file

@ -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",
],
},
}

View file

@ -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;
}

View file

@ -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

View file

@ -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",
],
}

View file

@ -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 {}

View file

@ -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

View file

@ -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>

View file

@ -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;
}

View file

@ -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

View file

@ -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"],
}

View file

@ -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

View file

@ -0,0 +1,282 @@
# Test GRPC Server.
A test GRPC server that implements wakeup_client.proto. This test server acts
as a reference implementation for a remote wakeup client running on TCU. The
test server does not communicate with any actual network server. It has the
following behavior:
* It starts a GRPC server on 'DGRPC_SERVICE_ADDRESS' compile flag which is
localhost:50051. The GRPC server provides the service according to
hardware/interfaces/automotive/remoteaccess/hal/default/proto/wakeup_client.proto.
In real implementation, DGRPC_SERVICE_ADDRESS can be specified to any IP
address where the TCU can be exposed to Application Processor. The default
remote access HAL implementation
(hardware/interfaces/automotive/remoteaccess/hal/default/Android.bp) also
uses DGRPC_SERVICE_ADDRESS to find this GRPC server, so it must have the
same IP address.
* It generates a fake task using FakeTaskGenerator every 'kTaskIntervalInMs' ms.
In real implementation, it should receive task from the remote server.
* Each fake task has an increasing unique client ID. The task data is always
what's defined for 'DATA' variable.
In real implementation, the client ID and task data should come from the
remote server.
* The generated tasks are put into a task queue which is a priority queue sorted
by task received time.
In real implementation, if the server provides a task timestamp, then this
queue can be sorted by that task timestamp instead.
* When the Application processor is started, the remote access HAL running on
Android will call 'GetRemoteTasks' to establish a long-live connection. This
connection is used to deliver all task data from remote wakeup client to
remote access HAL, which eventually to car service and applications.
When the 'GetRemoteTasks' is called, the wakeup client must send all the
pending tasks through the 'ServerWriter'. If no task is pending, then it must
block and wait for a new task to arrive.
If one task data fails to be sent through the channel, it likely means
the other side (Application processor) is shutting down or has closed the
channel. The wakeup client must put the task back to the pending queue and
wait for a new 'GetRemoteTasks' request to retry sending the task.
* When a new task arrives, if 'WakeupRequired' is true, then try to wakeup
the Application Processor by sending a specific CAN message. It is possible that
the waking up is already in progress. This is okay since Vehicle Processor
should ignore wakeup message if a wakeup is already in progress.
* When 'WakeupRequired' is updated from false to true, if there are unexpired
pending tasks in the task queue, try to wakeup Application Processor.
This is to handle the situation when a task arrives while the device is
shutting down. During the device shutdown, the channel to deliver the remote
tasks to Application Processor is shutdown so the new task will be added to the
task queue. 'WakeupRequired' will be set to false to prevent the wakeup
message preventing the shutdown. After the shutdown is complete,
'WakeupRequired' will be set to true and this wakeup client must try to wake
up the device again to execute the pending tasks.
* Every pending task has a timeout: 'KTaskTimeoutInMs'. If the pending task
is not delivered to remote access HAL before the timeout (through
GetRemoteTasks), the task timed out and a warning message is logged.
In real implementation, this kTaskTimeoutInMs has to be set long enough to
allow an Android bootup to happen. 20s is a reasonable value. When a task
timed out, the wakeup client should also report to remote task server about
the task timeout failure.
## How to build the test wakeup client
* Under android root: `make -j TestWakeupClientServer`
## How to push the test wakeup client to a TCU which runs Android.
* Make the target device writable:
`adb root`
`adb remount`
`adb reboot`
`adb root`
`adb remount`
* Under android root: `cd $ANDROID_PRODUCT_OUT`
* `adb push vendor/bin/TestWakeupClientServer /vendor/bin`
* `adb shell`
* `su`
* `/vendor/bin/TestWakeupClientServer`
## How to build and test the test wakeup client using one car emulator.
In this test setup we will use one google car emulator
(sdk_car_x86_64-userdebug). We assume both the TCU and the remote access HAL
runs on the same Android system, and they communicate through local loopback
interface.
* Under android root, `source build/envsetup.sh`
* `lunch sdk_car_x86_64-userdebug`
* `m -j`
* Run the emulator, the '-read-only' flag is required to run multiple instances:
`emulator -writable-system -read-only`
* The android lunch target: sdk_car_x86_64-userdebug and
cf_x86_64_auto-userdebug already contains the default remote access HAL. For
other lunch target, you can add the default remote access HAL by adding
'android.hardware.automotive.remoteaccess@V1-default-service' to
'PRODUCT_PACKAGES' variable in mk file, see `device/generic/car/common/car.mk`
as example.
To verify whether remote access HAL is running, you can use the following
command to check:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default`
* Make the target device writable:
`adb root`
`adb remount`
`adb reboot`
`adb root`
`adb remount`
* `make -j TestWakeupClientServer`
* `adb push $ANDROID_PRODUCT_OUT/vendor/bin/TestWakeupClientServer /vendor/bin`
* `adb shell`
* `su`
* `/vendor/bin/TestWakeupClientServer`
* Remote access HAL should start by default when the car emulator starts. Now
the test wake up client should also be running and generating fake tasks.
Start a new adb shell session by
`adb shell`
`su`
* Issue the command to start a simple debug callback that will capture all the
received tasks at the remote access HAL side:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --start-debug-callback`
* Issue the following debug command to remote access HAL to establish the
communication channel between it and the test wakeup client. This command
also notifies that wakeup is not required:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 1 0`
* Wait for a while, issue the following command to show the received fake tasks:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task`
You should expect to see some received tasks printed out.
* Simulate the Application Processor is shutting down by issuing the following
command:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 0 0`
* Wait for a while, issue the following command to show received tasks again:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task`
You should expect to see no new tasks received since remote access HAL already
closed the communication channel.
* Simulate the Application Processor is already shutdown and wake up is required
now:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 0 1`
Now you should expect to see the test wakeup client printing out messages
that it is trying to wake up application processor.
* Simulate the Application Processor is waken up:
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --set-ap-state 1 0`
* A new communication channel should have been established and all pending
non-expired tasks should be delivered to the remote access HAL.
`dumpsys android.hardware.automotive.remoteaccess.IRemoteAccess/default --show-task`
* Now you can issue `ctrl c` on the first adb shell to stop the test wakeup
client.
## How to build and test the test wakeup client using two car emulators.
In this test case, we are going to use two car emulators, one as the
Application Processor, one as the TCU.
* Change the IP address to allow IP communication between different emulator
instances. For detail about why we change it this way, see [interconnecting
emulator instance](https://developer.android.com/studio/run/emulator-networking#connecting).
Change 'DGRPC_SERVICE_ADDRESS' in `test_grpc_server/Android.bp` to
`10.0.2.15:50051`.
Change `DGRPC_SERVICE_ADDRESS` in 'hal/defaut/Android.bp' to
`10.0.2.2:50051`.
* Under android root: `source build/envsetup.sh`
* `lunch sdk_car_x86_64-userdebug`
* `m -j`
* Start one car emulator as TCU
`emulator -writable-system -read-only`
* Start a new shell session. Connect to the emulator's console,
see [Start and stop a console session](https://developer.android.com/studio/run/emulator-console#console-session)
for detail.
`telnet localhost 5554`
* `auth auth_token` where auth_token must match the contents of the
`.emulator_console_auth_token` file.
* `redir add tcp:50051:50051`
* Exit the telnet session
Make the target device writable:
`adb root`
`adb remount`
`adb reboot`
`adb root`
`adb remount`
* `make -j TestWakeupClientServer`
* `adb push $ANDROID_PRODUCT_OUT/vendor/bin/TestWakeupClientServer /vendor/bin`
* `adb shell`
* `su`
* `/vendor/bin/TestWakeupClientServer`
* Start a new shell, start another car emulator as the Application Processor:
`emulator -writable-system -read-only`
* Connect to adb shell for the application processor:
`adb -s emulator-5556 shell`
`su`
* Follow the test instructions for one car emulator using the 'dumpsys'
commands.

View file

@ -0,0 +1,42 @@
// 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"],
}
cc_binary {
name: "TestWakeupClientServer",
vendor: true,
srcs: ["src/*.cpp"],
local_include_dirs: ["include"],
shared_libs: [
"libbase",
"libutils",
"libgrpc++",
"libprotobuf-cpp-full",
],
whole_static_libs: [
"wakeup_client_protos",
],
cflags: [
"-Wno-unused-parameter",
"-DGRPC_SERVICE_ADDRESS=\"localhost:50051\"",
],
}

View file

@ -0,0 +1,139 @@
/*
* 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 <android-base/thread_annotations.h>
#include <utils/Looper.h>
#include <wakeup_client.grpc.pb.h>
#include <condition_variable>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
namespace android {
namespace hardware {
namespace automotive {
namespace remoteaccess {
// A class to generate fake task for testing. Not required for real implementation. In real
// implementation, the task should come from remote task server. This class is thread-safe.
class FakeTaskGenerator final {
public:
GetRemoteTasksResponse generateTask();
private:
// Simulates the client ID for each task.
std::atomic<int> mCurrentClientId = 0;
constexpr static uint8_t DATA[] = {0xde, 0xad, 0xbe, 0xef};
};
struct TaskInfo {
// This is unique per-task. Note that a task might be popped and put back into the task queue,
// it will have a new task ID but the same clientId in the task data.
int taskId;
int64_t timestampInMs;
GetRemoteTasksResponse taskData;
};
struct TaskInfoComparator {
// We want the smallest timestamp and smallest task ID on top.
bool operator()(const TaskInfo& l, const TaskInfo& r) {
return l.timestampInMs > r.timestampInMs ||
(l.timestampInMs == r.timestampInMs && l.taskId > r.taskId);
}
};
// forward-declaration.
class TaskQueue;
class TaskTimeoutMessageHandler final : public android::MessageHandler {
public:
TaskTimeoutMessageHandler(TaskQueue* taskQueue);
void handleMessage(const android::Message& message) override;
private:
TaskQueue* mTaskQueue;
};
// TaskQueue is thread-safe.
class TaskQueue final {
public:
TaskQueue();
~TaskQueue();
void add(const GetRemoteTasksResponse& response);
std::optional<GetRemoteTasksResponse> maybePopOne();
void waitForTask();
void stopWait();
void handleTaskTimeout();
bool isEmpty();
private:
std::thread mCheckTaskTimeoutThread;
std::mutex mLock;
std::priority_queue<TaskInfo, std::vector<TaskInfo>, TaskInfoComparator> mTasks
GUARDED_BY(mLock);
// A variable to notify mTasks is not empty.
std::condition_variable mTasksNotEmptyCv;
bool mStopped GUARDED_BY(mLock);
android::sp<Looper> mLooper;
android::sp<TaskTimeoutMessageHandler> mTaskTimeoutMessageHandler;
std::atomic<int> mTaskIdCounter = 0;
void checkForTestTimeoutLoop();
void waitForTaskWithLock(std::unique_lock<std::mutex>& lock);
};
class TestWakeupClientServiceImpl final : public WakeupClient::Service {
public:
TestWakeupClientServiceImpl();
~TestWakeupClientServiceImpl();
grpc::Status GetRemoteTasks(grpc::ServerContext* context, const GetRemoteTasksRequest* request,
grpc::ServerWriter<GetRemoteTasksResponse>* writer) override;
grpc::Status NotifyWakeupRequired(grpc::ServerContext* context,
const NotifyWakeupRequiredRequest* request,
NotifyWakeupRequiredResponse* response) override;
private:
// This is a thread for communicating with remote wakeup server (via network) and receive tasks
// from it.
std::thread mThread;
// A variable to notify server is stopping.
std::condition_variable mServerStoppedCv;
// Whether wakeup AP is required for executing tasks.
std::atomic<bool> mWakeupRequired = false;
std::mutex mLock;
bool mServerStopped GUARDED_BY(mLock);
// Thread-safe. For test impl only.
FakeTaskGenerator mFakeTaskGenerator;
// Thread-sfae.
TaskQueue mTaskQueue;
void fakeTaskGenerateLoop();
void wakeupApplicationProcessor();
};
} // namespace remoteaccess
} // namespace automotive
} // namespace hardware
} // namespace android

View file

@ -0,0 +1,255 @@
/*
* 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 "TestWakeupClientServiceImpl.h"
#include <android-base/stringprintf.h>
#include <inttypes.h>
#include <utils/Looper.h>
#include <utils/SystemClock.h>
#include <chrono>
#include <thread>
namespace android {
namespace hardware {
namespace automotive {
namespace remoteaccess {
namespace {
using ::android::uptimeMillis;
using ::android::base::ScopedLockAssertion;
using ::android::base::StringPrintf;
using ::grpc::ServerContext;
using ::grpc::ServerWriter;
using ::grpc::Status;
constexpr int kTaskIntervalInMs = 5'000;
constexpr int64_t KTaskTimeoutInMs = 20'000;
} // namespace
GetRemoteTasksResponse FakeTaskGenerator::generateTask() {
int clientId = mCurrentClientId++;
GetRemoteTasksResponse response;
response.set_data(std::string(reinterpret_cast<const char*>(DATA), sizeof(DATA)));
std::string clientIdStr = StringPrintf("%d", clientId);
response.set_clientid(clientIdStr);
return response;
}
TaskTimeoutMessageHandler::TaskTimeoutMessageHandler(TaskQueue* taskQueue)
: mTaskQueue(taskQueue) {}
void TaskTimeoutMessageHandler::handleMessage(const android::Message& message) {
mTaskQueue->handleTaskTimeout();
}
TaskQueue::TaskQueue() {
mTaskTimeoutMessageHandler = android::sp<TaskTimeoutMessageHandler>::make(this);
mLooper = Looper::prepare(/*opts=*/0);
mCheckTaskTimeoutThread = std::thread([this] { checkForTestTimeoutLoop(); });
}
TaskQueue::~TaskQueue() {
{
std::lock_guard<std::mutex> lockGuard(mLock);
mStopped = true;
}
while (true) {
// Remove all pending timeout handlers from queue.
if (!maybePopOne().has_value()) {
break;
}
}
if (mCheckTaskTimeoutThread.joinable()) {
mCheckTaskTimeoutThread.join();
}
}
std::optional<GetRemoteTasksResponse> TaskQueue::maybePopOne() {
std::lock_guard<std::mutex> lockGuard(mLock);
if (mTasks.size() == 0) {
return std::nullopt;
}
TaskInfo response = std::move(mTasks.top());
mTasks.pop();
mLooper->removeMessages(mTaskTimeoutMessageHandler, response.taskId);
return std::move(response.taskData);
}
void TaskQueue::add(const GetRemoteTasksResponse& task) {
std::lock_guard<std::mutex> lockGuard(mLock);
if (mStopped) {
return;
}
int taskId = mTaskIdCounter++;
mTasks.push(TaskInfo{
.taskId = taskId,
.timestampInMs = uptimeMillis(),
.taskData = task,
});
android::Message message(taskId);
mLooper->sendMessageDelayed(KTaskTimeoutInMs * 1000, mTaskTimeoutMessageHandler, message);
mTasksNotEmptyCv.notify_all();
}
void TaskQueue::waitForTask() {
std::unique_lock<std::mutex> lock(mLock);
waitForTaskWithLock(lock);
}
void TaskQueue::waitForTaskWithLock(std::unique_lock<std::mutex>& lock) {
mTasksNotEmptyCv.wait(lock, [this] {
ScopedLockAssertion lockAssertion(mLock);
return mTasks.size() > 0 || mStopped;
});
}
void TaskQueue::stopWait() {
std::lock_guard<std::mutex> lockGuard(mLock);
mStopped = true;
mTasksNotEmptyCv.notify_all();
}
bool TaskQueue::isEmpty() {
std::lock_guard<std::mutex> lockGuard(mLock);
return mTasks.size() == 0 || mStopped;
}
void TaskQueue::checkForTestTimeoutLoop() {
Looper::setForThread(mLooper);
while (true) {
{
std::unique_lock<std::mutex> lock(mLock);
if (mStopped) {
return;
}
}
mLooper->pollAll(/*timeoutMillis=*/-1);
}
}
void TaskQueue::handleTaskTimeout() {
// We know which task timed-out from the taskId in the message. However, there is no easy way
// to remove a specific task with the task ID from the priority_queue, so we just check from
// the top of the queue (which have the oldest tasks).
std::lock_guard<std::mutex> lockGuard(mLock);
int64_t now = uptimeMillis();
while (mTasks.size() > 0) {
const TaskInfo& taskInfo = mTasks.top();
if (taskInfo.timestampInMs + KTaskTimeoutInMs > now) {
break;
}
// In real implementation, this should report task failure to remote wakeup server.
printf("Task for client ID: %s timed-out, added at %" PRId64 " ms, now %" PRId64 " ms",
taskInfo.taskData.clientid().c_str(), taskInfo.timestampInMs, now);
mTasks.pop();
}
}
TestWakeupClientServiceImpl::TestWakeupClientServiceImpl() {
mThread = std::thread([this] { fakeTaskGenerateLoop(); });
}
TestWakeupClientServiceImpl::~TestWakeupClientServiceImpl() {
{
std::lock_guard<std::mutex> lockGuard(mLock);
mServerStopped = true;
mServerStoppedCv.notify_all();
}
mTaskQueue.stopWait();
if (mThread.joinable()) {
mThread.join();
}
}
void TestWakeupClientServiceImpl::fakeTaskGenerateLoop() {
// In actual implementation, this should communicate with the remote server and receives tasks
// from it. Here we simulate receiving one remote task every {kTaskIntervalInMs}ms.
while (true) {
mTaskQueue.add(mFakeTaskGenerator.generateTask());
printf("Received a new task\n");
if (mWakeupRequired) {
wakeupApplicationProcessor();
}
printf("Sleeping for %d seconds until next task\n", kTaskIntervalInMs);
std::unique_lock lk(mLock);
if (mServerStoppedCv.wait_for(lk, std::chrono::milliseconds(kTaskIntervalInMs), [this] {
ScopedLockAssertion lockAssertion(mLock);
return mServerStopped;
})) {
// If the stopped flag is set, we are quitting, exit the loop.
return;
}
}
}
Status TestWakeupClientServiceImpl::GetRemoteTasks(ServerContext* context,
const GetRemoteTasksRequest* request,
ServerWriter<GetRemoteTasksResponse>* writer) {
printf("GetRemoteTasks called\n");
while (true) {
mTaskQueue.waitForTask();
while (true) {
auto maybeTask = mTaskQueue.maybePopOne();
if (!maybeTask.has_value()) {
// No task left, loop again and wait for another task(s).
break;
}
// Loop through all the task in the queue but obtain lock for each element so we don't
// hold lock while writing the response.
const GetRemoteTasksResponse& response = maybeTask.value();
if (!writer->Write(response)) {
// Broken stream, maybe the client is shutting down.
printf("Failed to deliver remote task to remote access HAL\n");
// The task failed to be sent, add it back to the queue. The order might change, but
// it is okay.
mTaskQueue.add(response);
return Status::CANCELLED;
}
}
}
return Status::OK;
}
Status TestWakeupClientServiceImpl::NotifyWakeupRequired(ServerContext* context,
const NotifyWakeupRequiredRequest* request,
NotifyWakeupRequiredResponse* response) {
if (request->iswakeuprequired() && !mWakeupRequired && !mTaskQueue.isEmpty()) {
// If wakeup is now required and previously not required, this means we have finished
// shutting down the device. If there are still pending tasks, try waking up AP again
// to finish executing those tasks.
wakeupApplicationProcessor();
}
mWakeupRequired = request->iswakeuprequired();
return Status::OK;
}
void TestWakeupClientServiceImpl::wakeupApplicationProcessor() {
printf("Waking up application processor...\n");
// TODO(b/254547153): Send can bus message using socket CAN once we know what the message is.
}
} // namespace remoteaccess
} // namespace automotive
} // namespace hardware
} // namespace android

View file

@ -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.
*/
#include <string>
#include "TestWakeupClientServiceImpl.h"
#include <grpc/grpc.h>
#include <grpcpp/security/server_credentials.h>
#include <grpcpp/server.h>
#include <grpcpp/server_builder.h>
using ::android::hardware::automotive::remoteaccess::TestWakeupClientServiceImpl;
using ::grpc::Server;
using ::grpc::ServerBuilder;
using ::grpc::ServerWriter;
void RunServer() {
std::string serverAddress(GRPC_SERVICE_ADDRESS);
std::shared_ptr<TestWakeupClientServiceImpl> service =
std::make_unique<TestWakeupClientServiceImpl>();
ServerBuilder builder;
builder.AddListeningPort(serverAddress, grpc::InsecureServerCredentials());
builder.RegisterService(service.get());
std::unique_ptr<Server> server(builder.BuildAndStart());
printf("Test Remote Access GRPC Server listening on %s\n", serverAddress.c_str());
server->Wait();
}
int main(int argc, char** argv) {
RunServer();
return 0;
}

View file

@ -112,6 +112,13 @@
<regex-instance>.*</regex-instance>
</interface>
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.automotive.remoteaccess</name>
<interface>
<name>IRemoteAccess</name>
<regex-instance>.*</regex-instance>
</interface>
</hal>
<hal format="hidl" optional="true">
<name>android.hardware.automotive.vehicle</name>
<version>2.0</version>