Merge "Remove libaudioutils_nonvndk" into main
diff --git a/audio/aidl/common/include/Utils.h b/audio/aidl/common/include/Utils.h
index 3b08de7..59ca92a 100644
--- a/audio/aidl/common/include/Utils.h
+++ b/audio/aidl/common/include/Utils.h
@@ -174,4 +174,12 @@
return result;
}
+constexpr int32_t frameCountFromDurationUs(long durationUs, int32_t sampleRateHz) {
+ return (durationUs * sampleRateHz) / 1000000LL;
+}
+
+constexpr int32_t frameCountFromDurationMs(int32_t durationMs, int32_t sampleRateHz) {
+ return frameCountFromDurationUs(durationMs * 1000, sampleRateHz);
+}
+
} // namespace aidl::android::hardware::audio::common
diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp
index 8547026..11bd7d3 100644
--- a/audio/aidl/default/Android.bp
+++ b/audio/aidl/default/Android.bp
@@ -40,38 +40,6 @@
}
cc_library {
- name: "libaudioservicesounddoseimpl",
- vendor: true,
- defaults: [
- "latest_android_media_audio_common_types_ndk_shared",
- "latest_android_hardware_audio_core_sounddose_ndk_shared",
- "latest_android_hardware_audio_sounddose_ndk_shared",
- ],
- export_include_dirs: ["include"],
- srcs: [
- "SoundDose.cpp",
- ],
- shared_libs: [
- "libaudio_aidl_conversion_common_ndk",
- "libaudioutils",
- "libbase",
- "libbinder_ndk",
- "libcutils",
- "libutils",
- ],
- cflags: [
- "-Wall",
- "-Wextra",
- "-Werror",
- "-Wthread-safety",
- "-DBACKEND_NDK",
- ],
- visibility: [
- "//hardware/interfaces/audio/aidl/sounddose/default",
- ],
-}
-
-cc_library {
name: "libaudioserviceexampleimpl",
defaults: [
"aidlaudioservice_defaults",
diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp
index 1ec171a..66c2169 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -18,7 +18,6 @@
#include <set>
#define LOG_TAG "AHAL_Module"
-#include <Utils.h>
#include <aidl/android/media/audio/common/AudioInputFlags.h>
#include <aidl/android/media/audio/common/AudioOutputFlags.h>
#include <android-base/logging.h>
@@ -35,6 +34,7 @@
#include "core-impl/SoundDose.h"
#include "core-impl/utils.h"
+using aidl::android::hardware::audio::common::frameCountFromDurationMs;
using aidl::android::hardware::audio::common::getFrameSizeInBytes;
using aidl::android::hardware::audio::common::isBitPositionFlagSet;
using aidl::android::hardware::audio::common::isValidAudioMode;
@@ -202,15 +202,17 @@
LOG(ERROR) << __func__ << ": non-positive buffer size " << in_bufferSizeFrames;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
}
- if (in_bufferSizeFrames < kMinimumStreamBufferSizeFrames) {
- LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
- << ", must be at least " << kMinimumStreamBufferSizeFrames;
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
- }
auto& configs = getConfig().portConfigs;
auto portConfigIt = findById<AudioPortConfig>(configs, in_portConfigId);
// Since this is a private method, it is assumed that
// validity of the portConfigId has already been checked.
+ const int32_t minimumStreamBufferSizeFrames = calculateBufferSizeFrames(
+ getNominalLatencyMs(*portConfigIt), portConfigIt->sampleRate.value().value);
+ if (in_bufferSizeFrames < minimumStreamBufferSizeFrames) {
+ LOG(ERROR) << __func__ << ": insufficient buffer size " << in_bufferSizeFrames
+ << ", must be at least " << minimumStreamBufferSizeFrames;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+ }
const size_t frameSize =
getFrameSizeInBytes(portConfigIt->format.value(), portConfigIt->channelMask.value());
if (frameSize == 0) {
@@ -243,8 +245,8 @@
StreamContext temp(
std::make_unique<StreamContext::CommandMQ>(1, true /*configureEventFlagWord*/),
std::make_unique<StreamContext::ReplyMQ>(1, true /*configureEventFlagWord*/),
- portConfigIt->portId, portConfigIt->format.value(),
- portConfigIt->channelMask.value(), portConfigIt->sampleRate.value().value, flags,
+ portConfigIt->format.value(), portConfigIt->channelMask.value(),
+ portConfigIt->sampleRate.value().value, flags, getNominalLatencyMs(*portConfigIt),
portConfigIt->ext.get<AudioPortExt::mix>().handle,
std::make_unique<StreamContext::DataMQ>(frameSize * in_bufferSizeFrames),
asyncCallback, outEventCallback, mSoundDose.getInstance(), params);
@@ -363,6 +365,12 @@
return internal::getConfiguration(getType());
}
+int32_t Module::getNominalLatencyMs(const AudioPortConfig&) {
+ // Arbitrary value. Implementations must override this method to provide their actual latency.
+ static constexpr int32_t kLatencyMs = 5;
+ return kLatencyMs;
+}
+
std::vector<AudioRoute*> Module::getAudioRoutesForAudioPortImpl(int32_t portId) {
std::vector<AudioRoute*> result;
auto& routes = getConfig().routes;
@@ -613,32 +621,30 @@
std::vector<AudioRoute*> routesToMixPorts = getAudioRoutesForAudioPortImpl(templateId);
std::set<int32_t> routableMixPortIds = getRoutableAudioPortIds(templateId, &routesToMixPorts);
- if (hasDynamicProfilesOnly(connectedPort.profiles)) {
- if (!mDebug.simulateDeviceConnections) {
- RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort));
- } else {
- auto& connectedProfiles = getConfig().connectedProfiles;
- if (auto connectedProfilesIt = connectedProfiles.find(templateId);
- connectedProfilesIt != connectedProfiles.end()) {
- connectedPort.profiles = connectedProfilesIt->second;
- }
+ if (!mDebug.simulateDeviceConnections) {
+ // Even if the device port has static profiles, the HAL module might need to update
+ // them, or abort the connection process.
+ RETURN_STATUS_IF_ERROR(populateConnectedDevicePort(&connectedPort));
+ } else if (hasDynamicProfilesOnly(connectedPort.profiles)) {
+ auto& connectedProfiles = getConfig().connectedProfiles;
+ if (auto connectedProfilesIt = connectedProfiles.find(templateId);
+ connectedProfilesIt != connectedProfiles.end()) {
+ connectedPort.profiles = connectedProfilesIt->second;
}
- if (hasDynamicProfilesOnly(connectedPort.profiles)) {
- // Possible case 2. Check if all routable mix ports have static profiles.
- if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(),
- [&routableMixPortIds](const auto& p) {
- return routableMixPortIds.count(p.id) >
- 0 &&
- hasDynamicProfilesOnly(p.profiles);
- });
- dynamicMixPortIt != ports.end()) {
- LOG(ERROR) << __func__
- << ": connected port only has dynamic profiles after connecting "
- << "external device " << connectedPort.toString() << ", and there exist "
- << "a routable mix port with dynamic profiles: "
- << dynamicMixPortIt->toString();
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- }
+ }
+ if (hasDynamicProfilesOnly(connectedPort.profiles)) {
+ // Possible case 2. Check if all routable mix ports have static profiles.
+ if (auto dynamicMixPortIt = std::find_if(ports.begin(), ports.end(),
+ [&routableMixPortIds](const auto& p) {
+ return routableMixPortIds.count(p.id) > 0 &&
+ hasDynamicProfilesOnly(p.profiles);
+ });
+ dynamicMixPortIt != ports.end()) {
+ LOG(ERROR) << __func__ << ": connected port only has dynamic profiles after connecting "
+ << "external device " << connectedPort.toString() << ", and there exist "
+ << "a routable mix port with dynamic profiles: "
+ << dynamicMixPortIt->toString();
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
@@ -969,11 +975,21 @@
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
}
+ // Find the highest sample rate among mix port configs.
+ std::map<int32_t, AudioPortConfig*> sampleRates;
+ std::vector<AudioPortConfig*>& mixPortConfigs =
+ sources[0]->ext.getTag() == AudioPortExt::mix ? sources : sinks;
+ for (auto mix : mixPortConfigs) {
+ sampleRates.emplace(mix->sampleRate.value().value, mix);
+ }
*_aidl_return = in_requested;
- _aidl_return->minimumStreamBufferSizeFrames = kMinimumStreamBufferSizeFrames;
+ auto maxSampleRateIt = std::max_element(sampleRates.begin(), sampleRates.end());
+ const int32_t latencyMs = getNominalLatencyMs(*(maxSampleRateIt->second));
+ _aidl_return->minimumStreamBufferSizeFrames =
+ calculateBufferSizeFrames(latencyMs, maxSampleRateIt->first);
_aidl_return->latenciesMs.clear();
_aidl_return->latenciesMs.insert(_aidl_return->latenciesMs.end(),
- _aidl_return->sinkPortConfigIds.size(), kLatencyMs);
+ _aidl_return->sinkPortConfigIds.size(), latencyMs);
AudioPatch oldPatch{};
if (existing == patches.end()) {
_aidl_return->id = getConfig().nextPatchId++;
@@ -1215,7 +1231,7 @@
// Reset master mute if it failed.
onMasterMuteChanged(mMasterMute);
}
- return std::move(result);
+ return result;
}
ndk::ScopedAStatus Module::getMasterVolume(float* _aidl_return) {
@@ -1237,7 +1253,7 @@
<< "), error=" << result;
onMasterVolumeChanged(mMasterVolume);
}
- return std::move(result);
+ return result;
}
LOG(ERROR) << __func__ << ": invalid master volume value: " << in_volume;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
@@ -1558,11 +1574,6 @@
return result;
}
-Module::BtProfileHandles Module::getBtProfileManagerHandles() {
- return std::make_tuple(std::weak_ptr<IBluetooth>(), std::weak_ptr<IBluetoothA2dp>(),
- std::weak_ptr<IBluetoothLe>());
-}
-
ndk::ScopedAStatus Module::bluetoothParametersUpdated() {
return mStreams.bluetoothParametersUpdated();
}
diff --git a/audio/aidl/default/ModulePrimary.cpp b/audio/aidl/default/ModulePrimary.cpp
index 9919c7f..3da6d48 100644
--- a/audio/aidl/default/ModulePrimary.cpp
+++ b/audio/aidl/default/ModulePrimary.cpp
@@ -58,4 +58,12 @@
offloadInfo);
}
+int32_t ModulePrimary::getNominalLatencyMs(const AudioPortConfig&) {
+ // 85 ms is chosen considering 4096 frames @ 48 kHz. This is the value which allows
+ // the virtual Android device implementation to pass CTS. Hardware implementations
+ // should have significantly lower latency.
+ static constexpr int32_t kLatencyMs = 85;
+ return kLatencyMs;
+}
+
} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/Stream.cpp b/audio/aidl/default/Stream.cpp
index f00e358..e8c1693 100644
--- a/audio/aidl/default/Stream.cpp
+++ b/audio/aidl/default/Stream.cpp
@@ -138,7 +138,7 @@
reply->status = STATUS_OK;
if (isConnected) {
reply->observable.frames = mContext->getFrameCount();
- reply->observable.timeNs = ::android::elapsedRealtimeNano();
+ reply->observable.timeNs = ::android::uptimeNanos();
if (auto status = mDriver->refinePosition(&reply->observable); status == ::android::OK) {
return;
}
@@ -315,7 +315,7 @@
const size_t frameSize = mContext->getFrameSize();
size_t actualFrameCount = 0;
bool fatal = false;
- int32_t latency = Module::kLatencyMs;
+ int32_t latency = mContext->getNominalLatencyMs();
if (isConnected) {
if (::android::status_t status = mDriver->transfer(mDataBuffer.get(), byteCount / frameSize,
&actualFrameCount, &latency);
@@ -581,7 +581,7 @@
const size_t readByteCount = dataMQ->availableToRead();
const size_t frameSize = mContext->getFrameSize();
bool fatal = false;
- int32_t latency = Module::kLatencyMs;
+ int32_t latency = mContext->getNominalLatencyMs();
if (bool success = readByteCount > 0 ? dataMQ->read(&mDataBuffer[0], readByteCount) : true) {
const bool isConnected = mIsConnected;
LOG(VERBOSE) << __func__ << ": reading of " << readByteCount << " bytes from data MQ"
@@ -848,7 +848,7 @@
}
StreamInHwGainHelper::StreamInHwGainHelper(const StreamContext* context)
- : mChannelCount(getChannelCount(context->getChannelLayout())) {}
+ : mChannelCount(getChannelCount(context->getChannelLayout())), mHwGains(mChannelCount, 0.0f) {}
ndk::ScopedAStatus StreamInHwGainHelper::getHwGainImpl(std::vector<float>* _aidl_return) {
*_aidl_return = mHwGains;
@@ -979,7 +979,8 @@
}
StreamOutHwVolumeHelper::StreamOutHwVolumeHelper(const StreamContext* context)
- : mChannelCount(getChannelCount(context->getChannelLayout())) {}
+ : mChannelCount(getChannelCount(context->getChannelLayout())),
+ mHwVolumes(mChannelCount, 0.0f) {}
ndk::ScopedAStatus StreamOutHwVolumeHelper::getHwVolumeImpl(std::vector<float>* _aidl_return) {
*_aidl_return = mHwVolumes;
diff --git a/audio/aidl/default/alsa/Mixer.cpp b/audio/aidl/default/alsa/Mixer.cpp
index 126c033..e72502b 100644
--- a/audio/aidl/default/alsa/Mixer.cpp
+++ b/audio/aidl/default/alsa/Mixer.cpp
@@ -20,9 +20,24 @@
#define LOG_TAG "AHAL_AlsaMixer"
#include <android-base/logging.h>
#include <android/binder_status.h>
+#include <error/expected_utils.h>
#include "Mixer.h"
+namespace ndk {
+
+// This enables use of 'error/expected_utils' for ScopedAStatus.
+
+inline bool errorIsOk(const ScopedAStatus& s) {
+ return s.isOk();
+}
+
+inline std::string errorToString(const ScopedAStatus& s) {
+ return s.getDescription();
+}
+
+} // namespace ndk
+
namespace aidl::android::hardware::audio::core::alsa {
// static
@@ -93,6 +108,36 @@
}
}
+ndk::ScopedAStatus Mixer::getMasterMute(bool* muted) {
+ return getMixerControlMute(MASTER_SWITCH, muted);
+}
+
+ndk::ScopedAStatus Mixer::getMasterVolume(float* volume) {
+ return getMixerControlVolume(MASTER_VOLUME, volume);
+}
+
+ndk::ScopedAStatus Mixer::getMicGain(float* gain) {
+ return getMixerControlVolume(MIC_GAIN, gain);
+}
+
+ndk::ScopedAStatus Mixer::getMicMute(bool* muted) {
+ return getMixerControlMute(MIC_SWITCH, muted);
+}
+
+ndk::ScopedAStatus Mixer::getVolumes(std::vector<float>* volumes) {
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(Mixer::HW_VOLUME, &mctl));
+ std::vector<int> percents;
+ std::lock_guard l(mMixerAccess);
+ if (int err = getMixerControlPercent(mctl, &percents); err != 0) {
+ LOG(ERROR) << __func__ << ": failed to get volume, err=" << err;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ std::transform(percents.begin(), percents.end(), std::back_inserter(*volumes),
+ [](int percent) -> float { return std::clamp(percent / 100.0f, 0.0f, 1.0f); });
+ return ndk::ScopedAStatus::ok();
+}
+
ndk::ScopedAStatus Mixer::setMasterMute(bool muted) {
return setMixerControlMute(MASTER_SWITCH, muted);
}
@@ -110,35 +155,70 @@
}
ndk::ScopedAStatus Mixer::setVolumes(const std::vector<float>& volumes) {
- if (!isValid()) {
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- }
- auto it = mMixerControls.find(Mixer::HW_VOLUME);
- if (it == mMixerControls.end()) {
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
- }
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(Mixer::HW_VOLUME, &mctl));
std::vector<int> percents;
std::transform(
volumes.begin(), volumes.end(), std::back_inserter(percents),
[](float volume) -> int { return std::floor(std::clamp(volume, 0.0f, 1.0f) * 100); });
std::lock_guard l(mMixerAccess);
- if (int err = setMixerControlPercent(it->second, percents); err != 0) {
+ if (int err = setMixerControlPercent(mctl, percents); err != 0) {
LOG(ERROR) << __func__ << ": failed to set volume, err=" << err;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return ndk::ScopedAStatus::ok();
}
-ndk::ScopedAStatus Mixer::setMixerControlMute(Mixer::Control ctl, bool muted) {
+ndk::ScopedAStatus Mixer::findControl(Control ctl, struct mixer_ctl** result) {
if (!isValid()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
- auto it = mMixerControls.find(ctl);
- if (it == mMixerControls.end()) {
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+ if (auto it = mMixerControls.find(ctl); it != mMixerControls.end()) {
+ *result = it->second;
+ return ndk::ScopedAStatus::ok();
}
+ return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+}
+
+ndk::ScopedAStatus Mixer::getMixerControlMute(Control ctl, bool* muted) {
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
std::lock_guard l(mMixerAccess);
- if (int err = setMixerControlValue(it->second, muted ? 0 : 1); err != 0) {
+ std::vector<int> mutedValues;
+ if (int err = getMixerControlValues(mctl, &mutedValues); err != 0) {
+ LOG(ERROR) << __func__ << ": failed to get " << ctl << ", err=" << err;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ if (mutedValues.empty()) {
+ LOG(ERROR) << __func__ << ": got no values for " << ctl;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ *muted = mutedValues[0] != 0;
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Mixer::getMixerControlVolume(Control ctl, float* volume) {
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
+ std::lock_guard l(mMixerAccess);
+ std::vector<int> percents;
+ if (int err = getMixerControlPercent(mctl, &percents); err != 0) {
+ LOG(ERROR) << __func__ << ": failed to get " << ctl << ", err=" << err;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ if (percents.empty()) {
+ LOG(ERROR) << __func__ << ": got no values for " << ctl;
+ return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
+ }
+ *volume = std::clamp(percents[0] / 100.0f, 0.0f, 1.0f);
+ return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus Mixer::setMixerControlMute(Control ctl, bool muted) {
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
+ std::lock_guard l(mMixerAccess);
+ if (int err = setMixerControlValue(mctl, muted ? 0 : 1); err != 0) {
LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << muted << ", err=" << err;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
@@ -146,54 +226,72 @@
}
ndk::ScopedAStatus Mixer::setMixerControlVolume(Control ctl, float volume) {
- if (!isValid()) {
- return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
- }
- auto it = mMixerControls.find(ctl);
- if (it == mMixerControls.end()) {
- return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
- }
+ struct mixer_ctl* mctl;
+ RETURN_STATUS_IF_ERROR(findControl(ctl, &mctl));
volume = std::clamp(volume, 0.0f, 1.0f);
std::lock_guard l(mMixerAccess);
- if (int err = setMixerControlPercent(it->second, std::floor(volume * 100)); err != 0) {
+ if (int err = setMixerControlPercent(mctl, std::floor(volume * 100)); err != 0) {
LOG(ERROR) << __func__ << ": failed to set " << ctl << " to " << volume << ", err=" << err;
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
return ndk::ScopedAStatus::ok();
}
+int Mixer::getMixerControlPercent(struct mixer_ctl* ctl, std::vector<int>* percents) {
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ percents->resize(n);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int valueOrError = mixer_ctl_get_percent(ctl, id); valueOrError >= 0) {
+ (*percents)[id] = valueOrError;
+ } else {
+ return valueOrError;
+ }
+ }
+ return 0;
+}
+
+int Mixer::getMixerControlValues(struct mixer_ctl* ctl, std::vector<int>* values) {
+ const unsigned int n = mixer_ctl_get_num_values(ctl);
+ values->resize(n);
+ for (unsigned int id = 0; id < n; id++) {
+ if (int valueOrError = mixer_ctl_get_value(ctl, id); valueOrError >= 0) {
+ (*values)[id] = valueOrError;
+ } else {
+ return valueOrError;
+ }
+ }
+ return 0;
+}
+
int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, int percent) {
- int ret = 0;
const unsigned int n = mixer_ctl_get_num_values(ctl);
for (unsigned int id = 0; id < n; id++) {
if (int error = mixer_ctl_set_percent(ctl, id, percent); error != 0) {
- ret = error;
+ return error;
}
}
- return ret;
+ return 0;
}
int Mixer::setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents) {
- int ret = 0;
const unsigned int n = mixer_ctl_get_num_values(ctl);
for (unsigned int id = 0; id < n; id++) {
if (int error = mixer_ctl_set_percent(ctl, id, id < percents.size() ? percents[id] : 0);
error != 0) {
- ret = error;
+ return error;
}
}
- return ret;
+ return 0;
}
int Mixer::setMixerControlValue(struct mixer_ctl* ctl, int value) {
- int ret = 0;
const unsigned int n = mixer_ctl_get_num_values(ctl);
for (unsigned int id = 0; id < n; id++) {
if (int error = mixer_ctl_set_value(ctl, id, value); error != 0) {
- ret = error;
+ return error;
}
}
- return ret;
+ return 0;
}
} // namespace aidl::android::hardware::audio::core::alsa
diff --git a/audio/aidl/default/alsa/Mixer.h b/audio/aidl/default/alsa/Mixer.h
index 8fba1e0..41f19a8 100644
--- a/audio/aidl/default/alsa/Mixer.h
+++ b/audio/aidl/default/alsa/Mixer.h
@@ -39,6 +39,11 @@
bool isValid() const { return mMixer != nullptr; }
+ ndk::ScopedAStatus getMasterMute(bool* muted);
+ ndk::ScopedAStatus getMasterVolume(float* volume);
+ ndk::ScopedAStatus getMicGain(float* gain);
+ ndk::ScopedAStatus getMicMute(bool* muted);
+ ndk::ScopedAStatus getVolumes(std::vector<float>* volumes);
ndk::ScopedAStatus setMasterMute(bool muted);
ndk::ScopedAStatus setMasterVolume(float volume);
ndk::ScopedAStatus setMicGain(float gain);
@@ -60,9 +65,16 @@
static const std::map<Control, std::vector<ControlNamesAndExpectedCtlType>> kPossibleControls;
static Controls initializeMixerControls(struct mixer* mixer);
+ ndk::ScopedAStatus findControl(Control ctl, struct mixer_ctl** result);
+ ndk::ScopedAStatus getMixerControlMute(Control ctl, bool* muted);
+ ndk::ScopedAStatus getMixerControlVolume(Control ctl, float* volume);
ndk::ScopedAStatus setMixerControlMute(Control ctl, bool muted);
ndk::ScopedAStatus setMixerControlVolume(Control ctl, float volume);
+ int getMixerControlPercent(struct mixer_ctl* ctl, std::vector<int>* percents)
+ REQUIRES(mMixerAccess);
+ int getMixerControlValues(struct mixer_ctl* ctl, std::vector<int>* values)
+ REQUIRES(mMixerAccess);
int setMixerControlPercent(struct mixer_ctl* ctl, int percent) REQUIRES(mMixerAccess);
int setMixerControlPercent(struct mixer_ctl* ctl, const std::vector<int>& percents)
REQUIRES(mMixerAccess);
diff --git a/audio/aidl/default/alsa/StreamAlsa.cpp b/audio/aidl/default/alsa/StreamAlsa.cpp
index 403b94b..e57d538 100644
--- a/audio/aidl/default/alsa/StreamAlsa.cpp
+++ b/audio/aidl/default/alsa/StreamAlsa.cpp
@@ -119,7 +119,7 @@
::android::status_t StreamAlsa::refinePosition(StreamDescriptor::Position* position) {
if (mAlsaDeviceProxies.empty()) {
- LOG(FATAL) << __func__ << ": no opened devices";
+ LOG(WARNING) << __func__ << ": no opened devices";
return ::android::NO_INIT;
}
// Since the proxy can only count transferred frames since its creation,
diff --git a/audio/aidl/default/bluetooth/DevicePortProxy.cpp b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
index 12e204a..1be0875 100644
--- a/audio/aidl/default/bluetooth/DevicePortProxy.cpp
+++ b/audio/aidl/default/bluetooth/DevicePortProxy.cpp
@@ -19,11 +19,25 @@
#include <android-base/logging.h>
#include <android-base/stringprintf.h>
#include <audio_utils/primitives.h>
-#include <inttypes.h>
#include <log/log.h>
+#include "BluetoothAudioSessionControl.h"
#include "core-impl/DevicePortProxy.h"
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::bluetooth::audio::AudioConfiguration;
+using aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl;
+using aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus;
+using aidl::android::hardware::bluetooth::audio::ChannelMode;
+using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
+using aidl::android::hardware::bluetooth::audio::PortStatusCallbacks;
+using aidl::android::hardware::bluetooth::audio::PresentationPosition;
+using aidl::android::hardware::bluetooth::audio::SessionType;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioDeviceType;
+using android::base::StringPrintf;
+
namespace android::bluetooth::audio::aidl {
namespace {
@@ -33,20 +47,6 @@
} // namespace
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::bluetooth::audio::AudioConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSessionControl;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioStatus;
-using ::aidl::android::hardware::bluetooth::audio::ChannelMode;
-using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::PortStatusCallbacks;
-using ::aidl::android::hardware::bluetooth::audio::PresentationPosition;
-using ::aidl::android::hardware::bluetooth::audio::SessionType;
-using ::aidl::android::media::audio::common::AudioDeviceDescription;
-using ::aidl::android::media::audio::common::AudioDeviceType;
-using ::android::base::StringPrintf;
-
std::ostream& operator<<(std::ostream& os, const BluetoothStreamState& state) {
switch (state) {
case BluetoothStreamState::DISABLED:
diff --git a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
index 3c33207..502b153 100644
--- a/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
+++ b/audio/aidl/default/bluetooth/ModuleBluetooth.cpp
@@ -18,50 +18,56 @@
#include <android-base/logging.h>
-#include "BluetoothAudioSessionControl.h"
+#include "BluetoothAudioSession.h"
#include "core-impl/ModuleBluetooth.h"
#include "core-impl/StreamBluetooth.h"
-namespace aidl::android::hardware::audio::core {
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::media::audio::common::AudioDeviceDescription;
+using aidl::android::media::audio::common::AudioDeviceType;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::AudioPort;
+using aidl::android::media::audio::common::AudioPortExt;
+using aidl::android::media::audio::common::MicrophoneInfo;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession;
-using ::aidl::android::media::audio::common::AudioDeviceDescription;
-using ::aidl::android::media::audio::common::AudioDeviceType;
-using ::aidl::android::media::audio::common::AudioOffloadInfo;
-using ::aidl::android::media::audio::common::AudioPort;
-using ::aidl::android::media::audio::common::AudioPortExt;
-using ::aidl::android::media::audio::common::MicrophoneInfo;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
+namespace aidl::android::hardware::audio::core {
ndk::ScopedAStatus ModuleBluetooth::getBluetoothA2dp(
std::shared_ptr<IBluetoothA2dp>* _aidl_return) {
- if (!mBluetoothA2dp) {
- auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
- handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
- mBluetoothA2dp = handle;
- }
- *_aidl_return = mBluetoothA2dp.getInstance();
+ *_aidl_return = getBtA2dp().getInstance();
LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _aidl_return->get();
return ndk::ScopedAStatus::ok();
}
ndk::ScopedAStatus ModuleBluetooth::getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) {
+ *_aidl_return = getBtLe().getInstance();
+ LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
+ return ndk::ScopedAStatus::ok();
+}
+
+ChildInterface<BluetoothA2dp>& ModuleBluetooth::getBtA2dp() {
+ if (!mBluetoothA2dp) {
+ auto handle = ndk::SharedRefBase::make<BluetoothA2dp>();
+ handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
+ mBluetoothA2dp = handle;
+ }
+ return mBluetoothA2dp;
+}
+
+ChildInterface<BluetoothLe>& ModuleBluetooth::getBtLe() {
if (!mBluetoothLe) {
auto handle = ndk::SharedRefBase::make<BluetoothLe>();
handle->registerHandler(std::bind(&ModuleBluetooth::bluetoothParametersUpdated, this));
mBluetoothLe = handle;
}
- *_aidl_return = mBluetoothLe.getInstance();
- LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _aidl_return->get();
- return ndk::ScopedAStatus::ok();
+ return mBluetoothLe;
}
-Module::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
- return std::make_tuple(std::weak_ptr<IBluetooth>(), mBluetoothA2dp.getInstance(),
- mBluetoothLe.getInstance());
+ModuleBluetooth::BtProfileHandles ModuleBluetooth::getBtProfileManagerHandles() {
+ return std::make_tuple(std::weak_ptr<IBluetooth>(), getBtA2dp().getPtr(), getBtLe().getPtr());
}
ndk::ScopedAStatus ModuleBluetooth::getMicMute(bool* _aidl_return __unused) {
@@ -101,30 +107,35 @@
if (description.connection == AudioDeviceDescription::CONNECTION_BT_A2DP) {
bool isA2dpEnabled = false;
if (!!mBluetoothA2dp) {
- RETURN_STATUS_IF_ERROR(mBluetoothA2dp.getInstance()->isEnabled(&isA2dpEnabled));
+ RETURN_STATUS_IF_ERROR((*mBluetoothA2dp).isEnabled(&isA2dpEnabled));
}
+ LOG(DEBUG) << __func__ << ": isA2dpEnabled: " << isA2dpEnabled;
return isA2dpEnabled ? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
} else if (description.connection == AudioDeviceDescription::CONNECTION_BT_LE) {
bool isLeEnabled = false;
if (!!mBluetoothLe) {
- RETURN_STATUS_IF_ERROR(mBluetoothLe.getInstance()->isEnabled(&isLeEnabled));
+ RETURN_STATUS_IF_ERROR((*mBluetoothLe).isEnabled(&isLeEnabled));
}
+ LOG(DEBUG) << __func__ << ": isLeEnabled: " << isLeEnabled;
return isLeEnabled ? ndk::ScopedAStatus::ok()
: ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
} else if (description.connection == AudioDeviceDescription::CONNECTION_WIRELESS &&
description.type == AudioDeviceType::OUT_HEARING_AID) {
// Hearing aids can use a number of profiles, thus the only way to check
// connectivity is to try to talk to the BT HAL.
- if (!BluetoothAudioSession::IsAidlAvailable()) {
+ if (!::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::
+ IsAidlAvailable()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
std::shared_ptr<BluetoothAudioPortAidl> proxy = std::shared_ptr<BluetoothAudioPortAidl>(
std::make_shared<BluetoothAudioPortAidlOut>());
if (proxy->registerPort(description)) {
+ LOG(DEBUG) << __func__ << ": registered hearing aid port";
proxy->unregisterPort();
return ndk::ScopedAStatus::ok();
}
+ LOG(DEBUG) << __func__ << ": failed to register hearing aid port";
return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_STATE);
}
LOG(ERROR) << __func__ << ": unsupported device type: " << audioPort->toString();
diff --git a/audio/aidl/default/bluetooth/StreamBluetooth.cpp b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
index 91a33c2..0cee7f4 100644
--- a/audio/aidl/default/bluetooth/StreamBluetooth.cpp
+++ b/audio/aidl/default/bluetooth/StreamBluetooth.cpp
@@ -20,52 +20,49 @@
#include <android-base/logging.h>
#include <audio_utils/clock.h>
-#include "BluetoothAudioSessionControl.h"
+#include "BluetoothAudioSession.h"
#include "core-impl/StreamBluetooth.h"
-namespace aidl::android::hardware::audio::core {
+using aidl::android::hardware::audio::common::frameCountFromDurationUs;
+using aidl::android::hardware::audio::common::SinkMetadata;
+using aidl::android::hardware::audio::common::SourceMetadata;
+using aidl::android::hardware::audio::core::VendorParameter;
+using aidl::android::hardware::bluetooth::audio::ChannelMode;
+using aidl::android::hardware::bluetooth::audio::PcmConfiguration;
+using aidl::android::hardware::bluetooth::audio::PresentationPosition;
+using aidl::android::media::audio::common::AudioChannelLayout;
+using aidl::android::media::audio::common::AudioDevice;
+using aidl::android::media::audio::common::AudioDeviceAddress;
+using aidl::android::media::audio::common::AudioFormatDescription;
+using aidl::android::media::audio::common::AudioFormatType;
+using aidl::android::media::audio::common::AudioOffloadInfo;
+using aidl::android::media::audio::common::MicrophoneDynamicInfo;
+using aidl::android::media::audio::common::MicrophoneInfo;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
+using android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
+using android::bluetooth::audio::aidl::BluetoothStreamState;
-using ::aidl::android::hardware::audio::common::SinkMetadata;
-using ::aidl::android::hardware::audio::common::SourceMetadata;
-using ::aidl::android::hardware::audio::core::VendorParameter;
-using ::aidl::android::hardware::bluetooth::audio::ChannelMode;
-using ::aidl::android::hardware::bluetooth::audio::PcmConfiguration;
-using ::aidl::android::hardware::bluetooth::audio::PresentationPosition;
-using ::aidl::android::media::audio::common::AudioChannelLayout;
-using ::aidl::android::media::audio::common::AudioDevice;
-using ::aidl::android::media::audio::common::AudioDeviceAddress;
-using ::aidl::android::media::audio::common::AudioFormatDescription;
-using ::aidl::android::media::audio::common::AudioFormatType;
-using ::aidl::android::media::audio::common::AudioOffloadInfo;
-using ::aidl::android::media::audio::common::MicrophoneDynamicInfo;
-using ::aidl::android::media::audio::common::MicrophoneInfo;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidl;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlIn;
-using ::android::bluetooth::audio::aidl::BluetoothAudioPortAidlOut;
-using ::android::bluetooth::audio::aidl::BluetoothStreamState;
+namespace aidl::android::hardware::audio::core {
constexpr int kBluetoothDefaultInputBufferMs = 20;
constexpr int kBluetoothDefaultOutputBufferMs = 10;
// constexpr int kBluetoothSpatializerOutputBufferMs = 10;
-size_t getFrameCount(uint64_t durationUs, uint32_t sampleRate) {
- return (durationUs * sampleRate) / 1000000;
-}
-
// pcm configuration params are not really used by the module
StreamBluetooth::StreamBluetooth(StreamContext* context, const Metadata& metadata,
- Module::BtProfileHandles&& btHandles)
+ ModuleBluetooth::BtProfileHandles&& btHandles)
: StreamCommonImpl(context, metadata),
mSampleRate(getContext().getSampleRate()),
mChannelLayout(getContext().getChannelLayout()),
mFormat(getContext().getFormat()),
mFrameSizeBytes(getContext().getFrameSize()),
mIsInput(isInput(metadata)),
- mBluetoothA2dp(std::move(std::get<Module::BtInterface::BTA2DP>(btHandles))),
- mBluetoothLe(std::move(std::get<Module::BtInterface::BTLE>(btHandles))) {
+ mBluetoothA2dp(std::move(std::get<ModuleBluetooth::BtInterface::BTA2DP>(btHandles))),
+ mBluetoothLe(std::move(std::get<ModuleBluetooth::BtInterface::BTLE>(btHandles))) {
mPreferredDataIntervalUs =
- mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs;
- mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+ (mIsInput ? kBluetoothDefaultInputBufferMs : kBluetoothDefaultOutputBufferMs) * 1000;
+ mPreferredFrameCount = frameCountFromDurationUs(mPreferredDataIntervalUs, mSampleRate);
mIsInitialized = false;
mIsReadyToClose = false;
}
@@ -226,7 +223,7 @@
if (config.dataIntervalUs > 0) {
mPreferredDataIntervalUs =
std::min((int32_t)mPreferredDataIntervalUs, config.dataIntervalUs);
- mPreferredFrameCount = getFrameCount(mPreferredDataIntervalUs, mSampleRate);
+ mPreferredFrameCount = frameCountFromDurationUs(mPreferredDataIntervalUs, mSampleRate);
}
return true;
}
@@ -318,7 +315,7 @@
StreamInBluetooth::StreamInBluetooth(StreamContext&& context, const SinkMetadata& sinkMetadata,
const std::vector<MicrophoneInfo>& microphones,
- Module::BtProfileHandles&& btProfileHandles)
+ ModuleBluetooth::BtProfileHandles&& btProfileHandles)
: StreamIn(std::move(context), microphones),
StreamBluetooth(&mContextInstance, sinkMetadata, std::move(btProfileHandles)) {}
@@ -331,7 +328,7 @@
StreamOutBluetooth::StreamOutBluetooth(StreamContext&& context,
const SourceMetadata& sourceMetadata,
const std::optional<AudioOffloadInfo>& offloadInfo,
- Module::BtProfileHandles&& btProfileHandles)
+ ModuleBluetooth::BtProfileHandles&& btProfileHandles)
: StreamOut(std::move(context), offloadInfo),
StreamBluetooth(&mContextInstance, sourceMetadata, std::move(btProfileHandles)) {}
diff --git a/audio/aidl/default/include/core-impl/Bluetooth.h b/audio/aidl/default/include/core-impl/Bluetooth.h
index 44899bc..002cb19 100644
--- a/audio/aidl/default/include/core-impl/Bluetooth.h
+++ b/audio/aidl/default/include/core-impl/Bluetooth.h
@@ -46,9 +46,9 @@
class BluetoothA2dp : public BnBluetoothA2dp, public ParamChangeHandler {
public:
BluetoothA2dp() = default;
+ ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
private:
- ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
ndk::ScopedAStatus setEnabled(bool in_enabled) override;
ndk::ScopedAStatus supportsOffloadReconfiguration(bool* _aidl_return) override;
ndk::ScopedAStatus reconfigureOffload(
@@ -61,9 +61,9 @@
class BluetoothLe : public BnBluetoothLe, public ParamChangeHandler {
public:
BluetoothLe() = default;
+ ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
private:
- ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
ndk::ScopedAStatus setEnabled(bool in_enabled) override;
ndk::ScopedAStatus supportsOffloadReconfiguration(bool* _aidl_return) override;
ndk::ScopedAStatus reconfigureOffload(
diff --git a/audio/aidl/default/include/core-impl/ChildInterface.h b/audio/aidl/default/include/core-impl/ChildInterface.h
index 3b74c5e..f5f1855 100644
--- a/audio/aidl/default/include/core-impl/ChildInterface.h
+++ b/audio/aidl/default/include/core-impl/ChildInterface.h
@@ -40,6 +40,7 @@
explicit operator bool() const { return !!this->first; }
C& operator*() const { return *(this->first); }
C* operator->() const { return this->first; }
+ std::shared_ptr<C> getPtr() { return this->first; }
// Use 'getInstance' when returning the interface instance.
std::shared_ptr<C> getInstance() {
(void)getBinder();
diff --git a/audio/aidl/default/include/core-impl/DevicePortProxy.h b/audio/aidl/default/include/core-impl/DevicePortProxy.h
index 13b8c91..17a8cf3 100644
--- a/audio/aidl/default/include/core-impl/DevicePortProxy.h
+++ b/audio/aidl/default/include/core-impl/DevicePortProxy.h
@@ -25,11 +25,10 @@
#include <aidl/android/hardware/audio/common/SourceMetadata.h>
#include <aidl/android/hardware/bluetooth/audio/BluetoothAudioStatus.h>
#include <aidl/android/hardware/bluetooth/audio/PcmConfiguration.h>
+#include <aidl/android/hardware/bluetooth/audio/PresentationPosition.h>
#include <aidl/android/hardware/bluetooth/audio/SessionType.h>
#include <aidl/android/media/audio/common/AudioDeviceDescription.h>
-#include "BluetoothAudioSessionControl.h"
-
namespace android::bluetooth::audio::aidl {
enum class BluetoothStreamState : uint8_t {
@@ -239,4 +238,4 @@
size_t readData(void* buffer, size_t bytes) const override;
};
-} // namespace android::bluetooth::audio::aidl
\ No newline at end of file
+} // namespace android::bluetooth::audio::aidl
diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h
index d92d54b..caf43f1 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -22,6 +22,7 @@
#include <optional>
#include <set>
+#include <Utils.h>
#include <aidl/android/hardware/audio/core/BnModule.h>
#include "core-impl/ChildInterface.h"
@@ -45,13 +46,6 @@
int32_t nextPatchId = 1;
};
enum Type : int { DEFAULT, R_SUBMIX, STUB, USB, BLUETOOTH };
- enum BtInterface : int { BTCONF, BTA2DP, BTLE };
- typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
- std::weak_ptr<IBluetoothLe>>
- BtProfileHandles;
-
- // This value is used by default for all AudioPatches and reported by all streams.
- static constexpr int32_t kLatencyMs = 10;
static std::shared_ptr<Module> createInstance(Type type) {
return createInstance(type, std::make_unique<Configuration>());
@@ -145,8 +139,6 @@
ndk::ScopedAStatus getAAudioMixerBurstCount(int32_t* _aidl_return) override;
ndk::ScopedAStatus getAAudioHardwareBurstMinUsec(int32_t* _aidl_return) override;
- // This value is used for all AudioPatches.
- static constexpr int32_t kMinimumStreamBufferSizeFrames = 256;
// The maximum stream buffer size is 1 GiB = 2 ** 30 bytes;
static constexpr int32_t kMaximumStreamBufferSizeBytes = 1 << 30;
@@ -207,8 +199,19 @@
virtual ndk::ScopedAStatus onMasterVolumeChanged(float volume);
virtual std::vector<::aidl::android::media::audio::common::MicrophoneInfo> getMicrophoneInfos();
virtual std::unique_ptr<Configuration> initializeConfig();
+ virtual int32_t getNominalLatencyMs(
+ const ::aidl::android::media::audio::common::AudioPortConfig& portConfig);
// Utility and helper functions accessible to subclasses.
+ static int32_t calculateBufferSizeFrames(int32_t latencyMs, int32_t sampleRateHz) {
+ const int32_t rawSizeFrames =
+ aidl::android::hardware::audio::common::frameCountFromDurationMs(latencyMs,
+ sampleRateHz);
+ int32_t powerOf2 = 1;
+ while (powerOf2 < rawSizeFrames) powerOf2 <<= 1;
+ return powerOf2;
+ }
+
ndk::ScopedAStatus bluetoothParametersUpdated();
void cleanUpPatch(int32_t patchId);
ndk::ScopedAStatus createStreamContext(
@@ -222,7 +225,6 @@
ndk::ScopedAStatus findPortIdForNewStream(
int32_t in_portConfigId, ::aidl::android::media::audio::common::AudioPort** port);
std::vector<AudioRoute*> getAudioRoutesForAudioPortImpl(int32_t portId);
- virtual BtProfileHandles getBtProfileManagerHandles();
Configuration& getConfig();
const ConnectedDevicePorts& getConnectedDevicePorts() const { return mConnectedDevicePorts; }
bool getMasterMute() const { return mMasterMute; }
diff --git a/audio/aidl/default/include/core-impl/ModuleBluetooth.h b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
index 7ac2d34..a58798b 100644
--- a/audio/aidl/default/include/core-impl/ModuleBluetooth.h
+++ b/audio/aidl/default/include/core-impl/ModuleBluetooth.h
@@ -23,11 +23,18 @@
class ModuleBluetooth final : public Module {
public:
+ enum BtInterface : int { BTSCO, BTA2DP, BTLE };
+ typedef std::tuple<std::weak_ptr<IBluetooth>, std::weak_ptr<IBluetoothA2dp>,
+ std::weak_ptr<IBluetoothLe>>
+ BtProfileHandles;
+
ModuleBluetooth(std::unique_ptr<Configuration>&& config)
: Module(Type::BLUETOOTH, std::move(config)) {}
private:
- BtProfileHandles getBtProfileManagerHandles() override;
+ ChildInterface<BluetoothA2dp>& getBtA2dp();
+ ChildInterface<BluetoothLe>& getBtLe();
+ BtProfileHandles getBtProfileManagerHandles();
ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr<IBluetoothA2dp>* _aidl_return) override;
ndk::ScopedAStatus getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) override;
@@ -50,8 +57,8 @@
ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
- ChildInterface<IBluetoothA2dp> mBluetoothA2dp;
- ChildInterface<IBluetoothLe> mBluetoothLe;
+ ChildInterface<BluetoothA2dp> mBluetoothA2dp;
+ ChildInterface<BluetoothLe> mBluetoothLe;
};
} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/ModulePrimary.h b/audio/aidl/default/include/core-impl/ModulePrimary.h
index ee86d64..82c8a03 100644
--- a/audio/aidl/default/include/core-impl/ModulePrimary.h
+++ b/audio/aidl/default/include/core-impl/ModulePrimary.h
@@ -39,6 +39,8 @@
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo,
std::shared_ptr<StreamOut>* result) override;
+ int32_t getNominalLatencyMs(
+ const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
private:
ChildInterface<ITelephony> mTelephony;
diff --git a/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h b/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
index ebf4558..9f8acc9 100644
--- a/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
+++ b/audio/aidl/default/include/core-impl/ModuleRemoteSubmix.h
@@ -50,6 +50,9 @@
override;
ndk::ScopedAStatus onMasterMuteChanged(bool mute) override;
ndk::ScopedAStatus onMasterVolumeChanged(float volume) override;
+ int32_t getNominalLatencyMs(
+ const ::aidl::android::media::audio::common::AudioPortConfig& portConfig) override;
+ // TODO(b/307586684): Report proper minimum stream buffer size by overriding 'setAudioPatch'.
};
} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/include/core-impl/Stream.h b/audio/aidl/default/include/core-impl/Stream.h
index daa920d..aa9fb19 100644
--- a/audio/aidl/default/include/core-impl/Stream.h
+++ b/audio/aidl/default/include/core-impl/Stream.h
@@ -81,11 +81,10 @@
StreamContext() = default;
StreamContext(std::unique_ptr<CommandMQ> commandMQ, std::unique_ptr<ReplyMQ> replyMQ,
- int portId,
const ::aidl::android::media::audio::common::AudioFormatDescription& format,
const ::aidl::android::media::audio::common::AudioChannelLayout& channelLayout,
int sampleRate, const ::aidl::android::media::audio::common::AudioIoFlags& flags,
- int32_t mixPortHandle, std::unique_ptr<DataMQ> dataMQ,
+ int32_t nominalLatencyMs, int32_t mixPortHandle, std::unique_ptr<DataMQ> dataMQ,
std::shared_ptr<IStreamCallback> asyncCallback,
std::shared_ptr<IStreamOutEventCallback> outEventCallback,
std::weak_ptr<sounddose::StreamDataProcessorInterface> streamDataProcessor,
@@ -93,51 +92,17 @@
: mCommandMQ(std::move(commandMQ)),
mInternalCommandCookie(std::rand()),
mReplyMQ(std::move(replyMQ)),
- mPortId(portId),
mFormat(format),
mChannelLayout(channelLayout),
mSampleRate(sampleRate),
mFlags(flags),
+ mNominalLatencyMs(nominalLatencyMs),
mMixPortHandle(mixPortHandle),
mDataMQ(std::move(dataMQ)),
mAsyncCallback(asyncCallback),
mOutEventCallback(outEventCallback),
mStreamDataProcessor(streamDataProcessor),
mDebugParameters(debugParameters) {}
- StreamContext(StreamContext&& other)
- : mCommandMQ(std::move(other.mCommandMQ)),
- mInternalCommandCookie(other.mInternalCommandCookie),
- mReplyMQ(std::move(other.mReplyMQ)),
- mPortId(other.mPortId),
- mFormat(other.mFormat),
- mChannelLayout(other.mChannelLayout),
- mSampleRate(other.mSampleRate),
- mFlags(std::move(other.mFlags)),
- mMixPortHandle(other.mMixPortHandle),
- mDataMQ(std::move(other.mDataMQ)),
- mAsyncCallback(std::move(other.mAsyncCallback)),
- mOutEventCallback(std::move(other.mOutEventCallback)),
- mStreamDataProcessor(std::move(other.mStreamDataProcessor)),
- mDebugParameters(std::move(other.mDebugParameters)),
- mFrameCount(other.mFrameCount) {}
- StreamContext& operator=(StreamContext&& other) {
- mCommandMQ = std::move(other.mCommandMQ);
- mInternalCommandCookie = other.mInternalCommandCookie;
- mReplyMQ = std::move(other.mReplyMQ);
- mPortId = std::move(other.mPortId);
- mFormat = std::move(other.mFormat);
- mChannelLayout = std::move(other.mChannelLayout);
- mSampleRate = other.mSampleRate;
- mFlags = std::move(other.mFlags);
- mMixPortHandle = other.mMixPortHandle;
- mDataMQ = std::move(other.mDataMQ);
- mAsyncCallback = std::move(other.mAsyncCallback);
- mOutEventCallback = std::move(other.mOutEventCallback);
- mStreamDataProcessor = std::move(other.mStreamDataProcessor);
- mDebugParameters = std::move(other.mDebugParameters);
- mFrameCount = other.mFrameCount;
- return *this;
- }
void fillDescriptor(StreamDescriptor* desc);
std::shared_ptr<IStreamCallback> getAsyncCallback() const { return mAsyncCallback; }
@@ -156,6 +121,7 @@
size_t getFrameSize() const;
int getInternalCommandCookie() const { return mInternalCommandCookie; }
int32_t getMixPortHandle() const { return mMixPortHandle; }
+ int32_t getNominalLatencyMs() const { return mNominalLatencyMs; }
std::shared_ptr<IStreamOutEventCallback> getOutEventCallback() const {
return mOutEventCallback;
}
@@ -163,7 +129,6 @@
return mStreamDataProcessor;
}
void startStreamDataProcessor();
- int getPortId() const { return mPortId; }
ReplyMQ* getReplyMQ() const { return mReplyMQ.get(); }
int getTransientStateDelayMs() const { return mDebugParameters.transientStateDelayMs; }
int getSampleRate() const { return mSampleRate; }
@@ -176,14 +141,15 @@
long getFrameCount() const { return mFrameCount; }
private:
+ // Fields are non const to allow move assignment.
std::unique_ptr<CommandMQ> mCommandMQ;
int mInternalCommandCookie; // The value used to confirm that the command was posted internally
std::unique_ptr<ReplyMQ> mReplyMQ;
- int mPortId;
::aidl::android::media::audio::common::AudioFormatDescription mFormat;
::aidl::android::media::audio::common::AudioChannelLayout mChannelLayout;
int mSampleRate;
::aidl::android::media::audio::common::AudioIoFlags mFlags;
+ int32_t mNominalLatencyMs;
int32_t mMixPortHandle;
std::unique_ptr<DataMQ> mDataMQ;
std::shared_ptr<IStreamCallback> mAsyncCallback;
diff --git a/audio/aidl/default/include/core-impl/StreamBluetooth.h b/audio/aidl/default/include/core-impl/StreamBluetooth.h
index c2f8c1d..1258d38 100644
--- a/audio/aidl/default/include/core-impl/StreamBluetooth.h
+++ b/audio/aidl/default/include/core-impl/StreamBluetooth.h
@@ -24,7 +24,7 @@
#include <aidl/android/hardware/audio/core/IBluetoothLe.h>
#include "core-impl/DevicePortProxy.h"
-#include "core-impl/Module.h"
+#include "core-impl/ModuleBluetooth.h"
#include "core-impl/Stream.h"
namespace aidl::android::hardware::audio::core {
@@ -32,7 +32,7 @@
class StreamBluetooth : public StreamCommonImpl {
public:
StreamBluetooth(StreamContext* context, const Metadata& metadata,
- Module::BtProfileHandles&& btHandles);
+ ModuleBluetooth::BtProfileHandles&& btHandles);
// Methods of 'DriverInterface'.
::android::status_t init() override;
::android::status_t drain(StreamDescriptor::DrainMode) override;
@@ -80,7 +80,7 @@
StreamContext&& context,
const ::aidl::android::hardware::audio::common::SinkMetadata& sinkMetadata,
const std::vector<::aidl::android::media::audio::common::MicrophoneInfo>& microphones,
- Module::BtProfileHandles&& btHandles);
+ ModuleBluetooth::BtProfileHandles&& btHandles);
private:
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
@@ -97,7 +97,7 @@
const ::aidl::android::hardware::audio::common::SourceMetadata& sourceMetadata,
const std::optional<::aidl::android::media::audio::common::AudioOffloadInfo>&
offloadInfo,
- Module::BtProfileHandles&& btHandles);
+ ModuleBluetooth::BtProfileHandles&& btHandles);
private:
void onClose(StreamDescriptor::State) override { defaultOnClose(); }
diff --git a/audio/aidl/default/include/core-impl/StreamPrimary.h b/audio/aidl/default/include/core-impl/StreamPrimary.h
index b64b749..abc119c 100644
--- a/audio/aidl/default/include/core-impl/StreamPrimary.h
+++ b/audio/aidl/default/include/core-impl/StreamPrimary.h
@@ -27,10 +27,13 @@
public:
StreamPrimary(StreamContext* context, const Metadata& metadata);
+ ::android::status_t transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
+ int32_t* latencyMs) override;
+
protected:
std::vector<alsa::DeviceProfile> getDeviceProfiles() override;
- const bool mIsInput;
+ const bool mIsAsynchronous;
};
class StreamInPrimary final : public StreamIn, public StreamSwitcher, public StreamInHwGainHelper {
diff --git a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
index 94404a1..21592b3 100644
--- a/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
+++ b/audio/aidl/default/include/core-impl/StreamRemoteSubmix.h
@@ -46,7 +46,7 @@
ndk::ScopedAStatus prepareToClose() override;
private:
- size_t getPipeSizeInFrames();
+ long getDelayInUsForFrameCount(size_t frameCount);
size_t getStreamPipeSizeInFrames();
::android::status_t outWrite(void* buffer, size_t frameCount, size_t* actualFrameCount);
::android::status_t inRead(void* buffer, size_t frameCount, size_t* actualFrameCount);
diff --git a/audio/aidl/default/primary/StreamPrimary.cpp b/audio/aidl/default/primary/StreamPrimary.cpp
index 17de2ba..7e3bdd4 100644
--- a/audio/aidl/default/primary/StreamPrimary.cpp
+++ b/audio/aidl/default/primary/StreamPrimary.cpp
@@ -14,11 +14,12 @@
* limitations under the License.
*/
-#include <limits>
+#include <chrono>
#define LOG_TAG "AHAL_StreamPrimary"
#include <android-base/logging.h>
#include <android-base/properties.h>
+#include <audio_utils/clock.h>
#include <error/expected_utils.h>
#include "PrimaryMixer.h"
@@ -37,10 +38,34 @@
namespace aidl::android::hardware::audio::core {
StreamPrimary::StreamPrimary(StreamContext* context, const Metadata& metadata)
- : StreamAlsa(context, metadata, 3 /*readWriteRetries*/), mIsInput(isInput(metadata)) {
+ : StreamAlsa(context, metadata, 3 /*readWriteRetries*/),
+ mIsAsynchronous(!!getContext().getAsyncCallback()) {
context->startStreamDataProcessor();
}
+::android::status_t StreamPrimary::transfer(void* buffer, size_t frameCount,
+ size_t* actualFrameCount, int32_t* latencyMs) {
+ auto start = std::chrono::steady_clock::now();
+ if (auto status = StreamAlsa::transfer(buffer, frameCount, actualFrameCount, latencyMs);
+ status != ::android::OK) {
+ return status;
+ }
+ // This is a workaround for the emulator implementation which has a host-side buffer
+ // and this can result in reading faster than real time.
+ if (mIsInput && !mIsAsynchronous) {
+ auto recordDurationUs = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now() - start);
+ const long projectedVsObservedOffsetUs =
+ *actualFrameCount * MICROS_PER_SECOND / mContext.getSampleRate() -
+ recordDurationUs.count();
+ if (projectedVsObservedOffsetUs > 0) {
+ LOG(VERBOSE) << __func__ << ": sleeping for " << projectedVsObservedOffsetUs << " us";
+ usleep(projectedVsObservedOffsetUs);
+ }
+ }
+ return ::android::OK;
+}
+
std::vector<alsa::DeviceProfile> StreamPrimary::getDeviceProfiles() {
static const std::vector<alsa::DeviceProfile> kBuiltInSource{
alsa::DeviceProfile{.card = primary::PrimaryMixer::kAlsaCard,
@@ -66,7 +91,8 @@
GetBoolProperty("ro.boot.audio.tinyalsa.simulate_input", false);
return kSimulateInput || device.type.type == AudioDeviceType::IN_TELEPHONY_RX ||
device.type.type == AudioDeviceType::IN_FM_TUNER ||
- device.type.connection == AudioDeviceDescription::CONNECTION_BUS;
+ device.type.connection == AudioDeviceDescription::CONNECTION_BUS ||
+ (device.type.type == AudioDeviceType::IN_DEVICE && device.type.connection.empty());
}
StreamSwitcher::DeviceSwitchBehavior StreamInPrimary::switchCurrentStream(
@@ -101,6 +127,11 @@
if (isStubStream()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
+ float gain;
+ RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getMicGain(&gain));
+ _aidl_return->resize(0);
+ _aidl_return->resize(mChannelCount, gain);
+ RETURN_STATUS_IF_ERROR(setHwGainImpl(*_aidl_return));
return getHwGainImpl(_aidl_return);
}
@@ -132,7 +163,8 @@
static const bool kSimulateOutput =
GetBoolProperty("ro.boot.audio.tinyalsa.ignore_output", false);
return kSimulateOutput || device.type.type == AudioDeviceType::OUT_TELEPHONY_TX ||
- device.type.connection == AudioDeviceDescription::CONNECTION_BUS;
+ device.type.connection == AudioDeviceDescription::CONNECTION_BUS ||
+ (device.type.type == AudioDeviceType::OUT_DEVICE && device.type.connection.empty());
}
StreamSwitcher::DeviceSwitchBehavior StreamOutPrimary::switchCurrentStream(
@@ -167,6 +199,9 @@
if (isStubStream()) {
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
+ RETURN_STATUS_IF_ERROR(primary::PrimaryMixer::getInstance().getVolumes(_aidl_return));
+ _aidl_return->resize(mChannelCount);
+ RETURN_STATUS_IF_ERROR(setHwVolumeImpl(*_aidl_return));
return getHwVolumeImpl(_aidl_return);
}
diff --git a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
index f8c775f..3e8dd7c 100644
--- a/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/ModuleRemoteSubmix.cpp
@@ -21,6 +21,7 @@
#include <android-base/logging.h>
#include <error/expected_utils.h>
+#include "SubmixRoute.h"
#include "core-impl/ModuleRemoteSubmix.h"
#include "core-impl/StreamRemoteSubmix.h"
@@ -106,4 +107,12 @@
return ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
}
+int32_t ModuleRemoteSubmix::getNominalLatencyMs(const AudioPortConfig&) {
+ // See the note on kDefaultPipePeriodCount.
+ static constexpr int32_t kMaxLatencyMs =
+ (r_submix::kDefaultPipeSizeInFrames * 1000) / r_submix::kDefaultSampleRateHz;
+ static constexpr int32_t kMinLatencyMs = kMaxLatencyMs / r_submix::kDefaultPipePeriodCount;
+ return (kMaxLatencyMs + kMinLatencyMs) / 2;
+}
+
} // namespace aidl::android::hardware::audio::core
diff --git a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
index 9c9c08b..38281b9 100644
--- a/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
+++ b/audio/aidl/default/r_submix/StreamRemoteSubmix.cpp
@@ -17,8 +17,6 @@
#define LOG_TAG "AHAL_StreamRemoteSubmix"
#include <android-base/logging.h>
-#include <cmath>
-
#include "core-impl/StreamRemoteSubmix.h"
using aidl::android::hardware::audio::common::SinkMetadata;
@@ -158,27 +156,8 @@
::android::status_t StreamRemoteSubmix::transfer(void* buffer, size_t frameCount,
size_t* actualFrameCount, int32_t* latencyMs) {
- *latencyMs = (getStreamPipeSizeInFrames() * MILLIS_PER_SECOND) / mStreamConfig.sampleRate;
+ *latencyMs = getDelayInUsForFrameCount(getStreamPipeSizeInFrames()) / 1000;
LOG(VERBOSE) << __func__ << ": Latency " << *latencyMs << "ms";
-
- sp<MonoPipe> sink = mCurrentRoute->getSink();
- if (sink != nullptr) {
- if (sink->isShutdown()) {
- sink.clear();
- LOG(VERBOSE) << __func__ << ": pipe shutdown, ignoring the transfer.";
- // the pipe has already been shutdown, this buffer will be lost but we must simulate
- // timing so we don't drain the output faster than realtime
- const size_t delayUs = static_cast<size_t>(
- std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
- usleep(delayUs);
-
- *actualFrameCount = frameCount;
- return ::android::OK;
- }
- } else {
- LOG(ERROR) << __func__ << ": transfer without a pipe!";
- return ::android::UNEXPECTED_NULL;
- }
mCurrentRoute->exitStandby(mIsInput);
return (mIsInput ? inRead(buffer, frameCount, actualFrameCount)
: outWrite(buffer, frameCount, actualFrameCount));
@@ -202,6 +181,10 @@
return ::android::OK;
}
+long StreamRemoteSubmix::getDelayInUsForFrameCount(size_t frameCount) {
+ return frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate;
+}
+
// Calculate the maximum size of the pipe buffer in frames for the specified stream.
size_t StreamRemoteSubmix::getStreamPipeSizeInFrames() {
auto pipeConfig = mCurrentRoute->mPipeConfig;
@@ -215,11 +198,11 @@
if (sink != nullptr) {
if (sink->isShutdown()) {
sink.clear();
- LOG(VERBOSE) << __func__ << ": pipe shutdown, ignoring the write.";
+ const auto delayUs = getDelayInUsForFrameCount(frameCount);
+ LOG(DEBUG) << __func__ << ": pipe shutdown, ignoring the write, sleeping for "
+ << delayUs << " us";
// the pipe has already been shutdown, this buffer will be lost but we must
// simulate timing so we don't drain the output faster than realtime
- const size_t delayUs = static_cast<size_t>(
- std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
usleep(delayUs);
*actualFrameCount = frameCount;
return ::android::OK;
@@ -229,17 +212,18 @@
return ::android::UNKNOWN_ERROR;
}
- const size_t availableToWrite = sink->availableToWrite();
+ const bool shouldBlockWrite = mCurrentRoute->shouldBlockWrite();
+ size_t availableToWrite = sink->availableToWrite();
// NOTE: sink has been checked above and sink and source life cycles are synchronized
sp<MonoPipeReader> source = mCurrentRoute->getSource();
// If the write to the sink should be blocked, flush enough frames from the pipe to make space
// to write the most recent data.
- if (!mCurrentRoute->shouldBlockWrite() && availableToWrite < frameCount) {
+ if (!shouldBlockWrite && availableToWrite < frameCount) {
static uint8_t flushBuffer[64];
const size_t flushBufferSizeFrames = sizeof(flushBuffer) / mStreamConfig.frameSize;
size_t framesToFlushFromSource = frameCount - availableToWrite;
- LOG(VERBOSE) << __func__ << ": flushing " << framesToFlushFromSource
- << " frames from the pipe to avoid blocking";
+ LOG(DEBUG) << __func__ << ": flushing " << framesToFlushFromSource
+ << " frames from the pipe to avoid blocking";
while (framesToFlushFromSource) {
const size_t flushSize = std::min(framesToFlushFromSource, flushBufferSizeFrames);
framesToFlushFromSource -= flushSize;
@@ -247,7 +231,12 @@
source->read(flushBuffer, flushSize);
}
}
+ availableToWrite = sink->availableToWrite();
+ if (!shouldBlockWrite && frameCount > availableToWrite) {
+ // Truncate the request to avoid blocking.
+ frameCount = availableToWrite;
+ }
ssize_t writtenFrames = sink->write(buffer, frameCount);
if (writtenFrames < 0) {
if (writtenFrames == (ssize_t)::android::NEGOTIATE) {
@@ -261,7 +250,6 @@
writtenFrames = sink->write(buffer, frameCount);
}
}
- sink.clear();
if (writtenFrames < 0) {
LOG(ERROR) << __func__ << ": failed writing to pipe with " << writtenFrames;
@@ -286,8 +274,9 @@
} else {
LOG(ERROR) << __func__ << ": Read errors " << readErrorCount;
}
- const size_t delayUs = static_cast<size_t>(
- std::roundf(frameCount * MICROS_PER_SECOND / mStreamConfig.sampleRate));
+ const auto delayUs = getDelayInUsForFrameCount(frameCount);
+ LOG(DEBUG) << __func__ << ": no source, ignoring the read, sleeping for " << delayUs
+ << " us";
usleep(delayUs);
memset(buffer, 0, mStreamConfig.frameSize * frameCount);
*actualFrameCount = frameCount;
@@ -296,7 +285,7 @@
// read the data from the pipe
int attempts = 0;
- const size_t delayUs = static_cast<size_t>(std::roundf(kReadAttemptSleepUs));
+ const long delayUs = kReadAttemptSleepUs;
char* buff = (char*)buffer;
size_t remainingFrames = frameCount;
int availableToRead = source->availableToRead();
@@ -313,11 +302,12 @@
buff += framesRead * mStreamConfig.frameSize;
availableToRead -= framesRead;
LOG(VERBOSE) << __func__ << ": (attempts = " << attempts << ") got " << framesRead
- << " frames, remaining=" << remainingFrames;
+ << " frames, remaining =" << remainingFrames;
} else {
attempts++;
LOG(WARNING) << __func__ << ": read returned " << framesRead
- << " , read failure attempts = " << attempts;
+ << " , read failure attempts = " << attempts << ", sleeping for "
+ << delayUs << " us";
usleep(delayUs);
}
}
@@ -337,18 +327,18 @@
// compute how much we need to sleep after reading the data by comparing the wall clock with
// the projected time at which we should return.
// wall clock after reading from the pipe
- auto recordDurationUs = std::chrono::steady_clock::now() - mCurrentRoute->getRecordStartTime();
+ auto recordDurationUs = std::chrono::duration_cast<std::chrono::microseconds>(
+ std::chrono::steady_clock::now() - mCurrentRoute->getRecordStartTime());
// readCounterFrames contains the number of frames that have been read since the beginning of
// recording (including this call): it's converted to usec and compared to how long we've been
// recording for, which gives us how long we must wait to sync the projected recording time, and
// the observed recording time.
- const int projectedVsObservedOffsetUs =
- std::roundf((readCounterFrames * MICROS_PER_SECOND / mStreamConfig.sampleRate) -
- recordDurationUs.count());
+ const long projectedVsObservedOffsetUs =
+ getDelayInUsForFrameCount(readCounterFrames) - recordDurationUs.count();
LOG(VERBOSE) << __func__ << ": record duration " << recordDurationUs.count()
- << " microseconds, will wait: " << projectedVsObservedOffsetUs << " microseconds";
+ << " us, will wait: " << projectedVsObservedOffsetUs << " us";
if (projectedVsObservedOffsetUs > 0) {
usleep(projectedVsObservedOffsetUs);
}
diff --git a/audio/aidl/default/r_submix/SubmixRoute.h b/audio/aidl/default/r_submix/SubmixRoute.h
index 1a98df2..92b95e9 100644
--- a/audio/aidl/default/r_submix/SubmixRoute.h
+++ b/audio/aidl/default/r_submix/SubmixRoute.h
@@ -14,16 +14,19 @@
* limitations under the License.
*/
+#pragma once
+
+#include <chrono>
#include <mutex>
+#include <android-base/thread_annotations.h>
#include <audio_utils/clock.h>
#include <media/nbaio/MonoPipe.h>
#include <media/nbaio/MonoPipeReader.h>
#include <aidl/android/media/audio/common/AudioChannelLayout.h>
-
-#include "core-impl/Stream.h"
+#include <aidl/android/media/audio/common/AudioFormatDescription.h>
using aidl::android::media::audio::common::AudioChannelLayout;
using aidl::android::media::audio::common::AudioFormatDescription;
@@ -36,9 +39,13 @@
namespace aidl::android::hardware::audio::core::r_submix {
static constexpr int kDefaultSampleRateHz = 48000;
-// Size at default sample rate
-// NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe().
-static constexpr int kDefaultPipeSizeInFrames = (1024 * 4);
+// Value used to divide the MonoPipe buffer into segments that are written to the source and
+// read from the sink. The maximum latency of the device is the size of the MonoPipe's buffer
+// the minimum latency is the MonoPipe buffer size divided by this value.
+static constexpr int kDefaultPipePeriodCount = 4;
+// Size at the default sample rate
+// NOTE: This value will be rounded up to the nearest power of 2 by MonoPipe.
+static constexpr int kDefaultPipeSizeInFrames = 1024 * kDefaultPipePeriodCount;
// Configuration of the audio stream.
struct AudioConfig {
diff --git a/audio/aidl/default/stub/StreamStub.cpp b/audio/aidl/default/stub/StreamStub.cpp
index 660a51e..2422fe4 100644
--- a/audio/aidl/default/stub/StreamStub.cpp
+++ b/audio/aidl/default/stub/StreamStub.cpp
@@ -94,7 +94,7 @@
}
::android::status_t StreamStub::transfer(void* buffer, size_t frameCount, size_t* actualFrameCount,
- int32_t* latencyMs) {
+ int32_t*) {
if (!mIsInitialized) {
LOG(FATAL) << __func__ << ": must not happen for an uninitialized driver";
}
@@ -117,7 +117,6 @@
}
}
*actualFrameCount = frameCount;
- *latencyMs = Module::kLatencyMs;
return ::android::OK;
}
diff --git a/audio/aidl/vts/Android.bp b/audio/aidl/vts/Android.bp
index 1d96a0e..ad816c7 100644
--- a/audio/aidl/vts/Android.bp
+++ b/audio/aidl/vts/Android.bp
@@ -40,7 +40,10 @@
"general-tests",
"vts",
],
- srcs: [":effectCommonFile"],
+ srcs: [
+ ":effectCommonFile",
+ "TestUtils.cpp",
+ ],
}
cc_test {
diff --git a/audio/aidl/vts/AudioHalBinderServiceUtil.h b/audio/aidl/vts/AudioHalBinderServiceUtil.h
index b4b4632..4ebc1b1 100644
--- a/audio/aidl/vts/AudioHalBinderServiceUtil.h
+++ b/audio/aidl/vts/AudioHalBinderServiceUtil.h
@@ -42,20 +42,9 @@
ndk::SpAIBinder restartService(
std::chrono::milliseconds timeoutMs = std::chrono::milliseconds(3000)) {
- mDeathHandler.reset(new AidlDeathRecipient(mBinder));
- if (STATUS_OK != mDeathHandler->linkToDeath()) {
- LOG(ERROR) << "linkToDeath failed";
- return nullptr;
+ if (!stopService(timeoutMs)) {
+ return {};
}
- if (!android::base::SetProperty("sys.audio.restart.hal", "1")) {
- LOG(ERROR) << "SetProperty failed";
- return nullptr;
- }
- if (!mDeathHandler->waitForFired(timeoutMs)) {
- LOG(ERROR) << "Timeout wait for death";
- return nullptr;
- }
- mDeathHandler.reset();
return connectToService(mServiceName);
}
@@ -71,8 +60,7 @@
bool waitForFired(std::chrono::milliseconds timeoutMs) {
std::unique_lock<std::mutex> lock(mutex);
- condition.wait_for(lock, timeoutMs, [this]() { return fired; });
- return fired;
+ return condition.wait_for(lock, timeoutMs, [this]() { return fired; });
}
private:
@@ -94,7 +82,23 @@
}
};
+ bool stopService(std::chrono::milliseconds timeoutMs) {
+ AidlDeathRecipient deathHandler(mBinder);
+ if (STATUS_OK != deathHandler.linkToDeath()) {
+ LOG(ERROR) << "linkToDeath failed";
+ return false;
+ }
+ if (!android::base::SetProperty("sys.audio.restart.hal", "1")) {
+ LOG(ERROR) << "SetProperty failed";
+ return false;
+ }
+ if (!deathHandler.waitForFired(timeoutMs)) {
+ LOG(ERROR) << "Timeout wait for death of " << mServiceName;
+ return false;
+ }
+ return true;
+ }
+
std::string mServiceName;
ndk::SpAIBinder mBinder;
- std::unique_ptr<AidlDeathRecipient> mDeathHandler;
};
diff --git a/audio/aidl/vts/ModuleConfig.cpp b/audio/aidl/vts/ModuleConfig.cpp
index 8f19547..a633d83 100644
--- a/audio/aidl/vts/ModuleConfig.cpp
+++ b/audio/aidl/vts/ModuleConfig.cpp
@@ -67,20 +67,7 @@
return {};
}
-std::vector<aidl::android::media::audio::common::AudioPort>
-ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
- const std::string& connection) {
- return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
-}
-
// static
-std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
- const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
- return getAudioPortsForDeviceTypes(
- ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
- AudioDeviceType::IN_MICROPHONE_BACK});
-}
-
std::vector<aidl::android::media::audio::common::AudioPort>
ModuleConfig::getAudioPortsForDeviceTypes(
const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
@@ -100,6 +87,14 @@
return result;
}
+// static
+std::vector<aidl::android::media::audio::common::AudioPort> ModuleConfig::getBuiltInMicPorts(
+ const std::vector<aidl::android::media::audio::common::AudioPort>& ports) {
+ return getAudioPortsForDeviceTypes(
+ ports, std::vector<AudioDeviceType>{AudioDeviceType::IN_MICROPHONE,
+ AudioDeviceType::IN_MICROPHONE_BACK});
+}
+
template <typename T>
auto findById(const std::vector<T>& v, int32_t id) {
return std::find_if(v.begin(), v.end(), [&](const auto& p) { return p.id == id; });
@@ -119,10 +114,7 @@
} else {
mAttachedSinkDevicePorts.insert(port.id);
}
- } else if (devicePort.device.type.connection != AudioDeviceDescription::CONNECTION_VIRTUAL
- // The "virtual" connection is used for remote submix which is a dynamic
- // device but it can be connected and used w/o external hardware.
- && port.profiles.empty()) {
+ } else {
mExternalDevicePorts.insert(port.id);
}
}
@@ -141,6 +133,12 @@
return result;
}
+std::vector<aidl::android::media::audio::common::AudioPort>
+ModuleConfig::getAudioPortsForDeviceTypes(const std::vector<AudioDeviceType>& deviceTypes,
+ const std::string& connection) const {
+ return getAudioPortsForDeviceTypes(mPorts, deviceTypes, connection);
+}
+
std::vector<AudioPort> ModuleConfig::getConnectedExternalDevicePorts() const {
std::vector<AudioPort> result;
std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
@@ -229,6 +227,16 @@
});
}
+std::vector<AudioPort> ModuleConfig::getRemoteSubmixPorts(bool isInput, bool singlePort) const {
+ AudioDeviceType deviceType = isInput ? AudioDeviceType::IN_SUBMIX : AudioDeviceType::OUT_SUBMIX;
+ auto ports = getAudioPortsForDeviceTypes(std::vector<AudioDeviceType>{deviceType},
+ AudioDeviceDescription::CONNECTION_VIRTUAL);
+ if (singlePort) {
+ if (!ports.empty()) ports.resize(1);
+ }
+ return ports;
+}
+
std::vector<AudioPort> ModuleConfig::getConnectedDevicesPortsForMixPort(
bool isInput, const AudioPortConfig& mixPortConfig) const {
const auto mixPortIt = findById<AudioPort>(mPorts, mixPortConfig.portId);
@@ -281,19 +289,29 @@
return {};
}
-std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port) const {
- std::set<int32_t> portIds;
- for (const auto& route : mRoutes) {
- if (port.id == route.sinkPortId) {
- portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
- } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
- port.id);
- it != route.sourcePortIds.end()) {
- portIds.insert(route.sinkPortId);
- }
- }
+std::vector<AudioPort> ModuleConfig::getRoutableDevicePortsForMixPort(const AudioPort& port,
+ bool connectedOnly) const {
+ std::set<int32_t> portIds = findRoutablePortIds(port.id);
const bool isInput = port.flags.getTag() == AudioIoFlags::input;
- return findMixPorts(isInput, false /*connectedOnly*/, false /*singlePort*/,
+ std::set<int32_t> devicePortIds;
+ if (connectedOnly) {
+ devicePortIds = isInput ? getConnectedSourceDevicePorts() : getConnectedSinkDevicePorts();
+ } else {
+ devicePortIds = portIds;
+ }
+ std::vector<AudioPort> result;
+ std::copy_if(mPorts.begin(), mPorts.end(), std::back_inserter(result), [&](const auto& port) {
+ return port.ext.getTag() == AudioPortExt::Tag::device && portIds.count(port.id) > 0 &&
+ devicePortIds.count(port.id) > 0;
+ });
+ return result;
+}
+
+std::vector<AudioPort> ModuleConfig::getRoutableMixPortsForDevicePort(const AudioPort& port,
+ bool connectedOnly) const {
+ std::set<int32_t> portIds = findRoutablePortIds(port.id);
+ const bool isInput = port.flags.getTag() == AudioIoFlags::input;
+ return findMixPorts(isInput, connectedOnly, false /*singlePort*/,
[&portIds](const AudioPort& p) { return portIds.count(p.id) > 0; });
}
@@ -470,6 +488,20 @@
return result;
}
+std::set<int32_t> ModuleConfig::findRoutablePortIds(int32_t portId) const {
+ std::set<int32_t> portIds;
+ for (const auto& route : mRoutes) {
+ if (portId == route.sinkPortId) {
+ portIds.insert(route.sourcePortIds.begin(), route.sourcePortIds.end());
+ } else if (auto it = std::find(route.sourcePortIds.begin(), route.sourcePortIds.end(),
+ portId);
+ it != route.sourcePortIds.end()) {
+ portIds.insert(route.sinkPortId);
+ }
+ }
+ return portIds;
+}
+
std::vector<AudioPortConfig> ModuleConfig::generateAudioMixPortConfigs(
const std::vector<AudioPort>& ports, bool isInput, bool singleProfile) const {
std::vector<AudioPortConfig> result;
diff --git a/audio/aidl/vts/ModuleConfig.h b/audio/aidl/vts/ModuleConfig.h
index b89adc0..4a87f8c 100644
--- a/audio/aidl/vts/ModuleConfig.h
+++ b/audio/aidl/vts/ModuleConfig.h
@@ -38,9 +38,6 @@
generateOffloadInfoIfNeeded(
const aidl::android::media::audio::common::AudioPortConfig& portConfig);
- std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
- const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
- const std::string& connection = "");
static std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
const std::vector<aidl::android::media::audio::common::AudioPort>& ports,
const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
@@ -53,6 +50,9 @@
std::string getError() const { return mStatus.getMessage(); }
std::vector<aidl::android::media::audio::common::AudioPort> getAttachedDevicePorts() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getAudioPortsForDeviceTypes(
+ const std::vector<aidl::android::media::audio::common::AudioDeviceType>& deviceTypes,
+ const std::string& connection = "") const;
std::vector<aidl::android::media::audio::common::AudioPort> getConnectedExternalDevicePorts()
const;
std::set<int32_t> getConnectedSinkDevicePorts() const;
@@ -85,6 +85,8 @@
std::vector<aidl::android::media::audio::common::AudioPort> getMmapInMixPorts(
bool connectedOnly /*Permanently attached and connected external devices*/,
bool singlePort) const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getRemoteSubmixPorts(
+ bool isInput, bool singlePort) const;
std::vector<aidl::android::media::audio::common::AudioPort> getConnectedDevicesPortsForMixPort(
bool isInput, const aidl::android::media::audio::common::AudioPort& mixPort) const {
@@ -103,8 +105,12 @@
std::optional<aidl::android::media::audio::common::AudioPort>
getSourceMixPortForConnectedDevice() const;
+ std::vector<aidl::android::media::audio::common::AudioPort> getRoutableDevicePortsForMixPort(
+ const aidl::android::media::audio::common::AudioPort& port,
+ bool connectedOnly /*Permanently attached and connected external devices*/) const;
std::vector<aidl::android::media::audio::common::AudioPort> getRoutableMixPortsForDevicePort(
- const aidl::android::media::audio::common::AudioPort& port) const;
+ const aidl::android::media::audio::common::AudioPort& port,
+ bool connectedOnly /*Permanently attached and connected external devices*/) const;
std::optional<SrcSinkPair> getNonRoutableSrcSinkPair(bool isInput) const;
std::optional<SrcSinkPair> getRoutableSrcSinkPair(bool isInput) const;
@@ -176,6 +182,7 @@
bool isInput, bool connectedOnly, bool singlePort,
const std::function<bool(const aidl::android::media::audio::common::AudioPort&)>& pred)
const;
+ std::set<int32_t> findRoutablePortIds(int32_t portId) const;
std::vector<aidl::android::media::audio::common::AudioPortConfig> generateAudioMixPortConfigs(
const std::vector<aidl::android::media::audio::common::AudioPort>& ports, bool isInput,
bool singleProfile) const;
diff --git a/audio/aidl/vts/TestUtils.cpp b/audio/aidl/vts/TestUtils.cpp
new file mode 100644
index 0000000..f018468
--- /dev/null
+++ b/audio/aidl/vts/TestUtils.cpp
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "TestUtils.h"
+
+#define LOG_TAG "VtsHalAudio_TestUtils"
+
+#include <android-base/logging.h>
+
+namespace android::hardware::audio::common::testing {
+
+namespace detail {
+void TestExecutionTracer::OnTestStart(const ::testing::TestInfo& test_info) {
+ TraceTestState("Started", test_info);
+}
+
+void TestExecutionTracer::OnTestEnd(const ::testing::TestInfo& test_info) {
+ TraceTestState("Completed", test_info);
+}
+
+void TestExecutionTracer::OnTestPartResult(const ::testing::TestPartResult& result) {
+ LOG(INFO) << result;
+}
+
+void TestExecutionTracer::TraceTestState(const std::string& state,
+ const ::testing::TestInfo& test_info) {
+ LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
+}
+}
+}
\ No newline at end of file
diff --git a/audio/aidl/vts/TestUtils.h b/audio/aidl/vts/TestUtils.h
index b559669..e9f3251 100644
--- a/audio/aidl/vts/TestUtils.h
+++ b/audio/aidl/vts/TestUtils.h
@@ -18,15 +18,24 @@
#include <algorithm>
#include <initializer_list>
-#include <iostream>
#include <android/binder_auto_utils.h>
#include <gtest/gtest.h>
+#include <system/audio_aidl_utils.h>
namespace android::hardware::audio::common::testing {
namespace detail {
+class TestExecutionTracer : public ::testing::EmptyTestEventListener {
+ public:
+ void OnTestStart(const ::testing::TestInfo& test_info) override;
+ void OnTestEnd(const ::testing::TestInfo& test_info) override;
+ void OnTestPartResult(const ::testing::TestPartResult& result) override;
+ private:
+ static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info);
+};
+
inline ::testing::AssertionResult assertIsOk(const char* expr, const ::ndk::ScopedAStatus& status) {
if (status.isOk()) {
return ::testing::AssertionSuccess();
@@ -83,4 +92,4 @@
if ((flags).hwAcceleratorMode == Flags::HardwareAccelerator::TUNNEL || (flags).bypass) { \
GTEST_SKIP() << "Skip data path for offload"; \
} \
- })
\ No newline at end of file
+ })
diff --git a/audio/aidl/vts/VtsHalAECTargetTest.cpp b/audio/aidl/vts/VtsHalAECTargetTest.cpp
index 0354e3c..39168b1 100644
--- a/audio/aidl/vts/VtsHalAECTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAECTargetTest.cpp
@@ -34,6 +34,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Range;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
enum ParamName { PARAM_INSTANCE_NAME, PARAM_ECHO_DELAY, PARAM_MOBILE_MODE };
using AECParamTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
@@ -178,6 +179,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAGC1TargetTest.cpp b/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
index 65c6a8f..6066025 100644
--- a/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAGC1TargetTest.cpp
@@ -28,6 +28,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
enum ParamName {
PARAM_INSTANCE_NAME,
@@ -189,6 +190,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAGC2TargetTest.cpp b/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
index 46a569e..8793e4c 100644
--- a/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAGC2TargetTest.cpp
@@ -29,6 +29,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
enum ParamName {
PARAM_INSTANCE_NAME,
@@ -194,6 +195,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index 9c52d19..697aae9 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -108,14 +108,28 @@
using aidl::android::media::audio::common::Void;
using android::hardware::audio::common::StreamLogic;
using android::hardware::audio::common::StreamWorker;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
using ndk::enum_range;
using ndk::ScopedAStatus;
template <typename T>
-auto findById(std::vector<T>& v, int32_t id) {
+std::set<int32_t> extractIds(const std::vector<T>& v) {
+ std::set<int32_t> ids;
+ std::transform(v.begin(), v.end(), std::inserter(ids, ids.begin()),
+ [](const auto& entity) { return entity.id; });
+ return ids;
+}
+
+template <typename T>
+auto findById(const std::vector<T>& v, int32_t id) {
return std::find_if(v.begin(), v.end(), [&](const auto& e) { return e.id == id; });
}
+template <typename T>
+auto findAny(const std::vector<T>& v, const std::set<int32_t>& ids) {
+ return std::find_if(v.begin(), v.end(), [&](const auto& e) { return ids.count(e.id) > 0; });
+}
+
template <typename C>
std::vector<int32_t> GetNonExistentIds(const C& allIds) {
if (allIds.empty()) {
@@ -154,8 +168,10 @@
static int nextId = 0;
using Tag = AudioDeviceAddress::Tag;
const auto& deviceDescription = port.ext.get<AudioPortExt::Tag::device>().device.type;
- AudioDeviceAddress address;
- if (kPointToPointConnections.count(deviceDescription.connection) == 0) {
+ AudioDeviceAddress address = port.ext.get<AudioPortExt::Tag::device>().device.address;
+ // If the address is already set, do not re-generate.
+ if (address == AudioDeviceAddress() &&
+ kPointToPointConnections.count(deviceDescription.connection) == 0) {
switch (suggestDeviceAddressTag(deviceDescription)) {
case Tag::id:
address = AudioDeviceAddress::make<Tag::id>(std::to_string(++nextId));
@@ -416,18 +432,21 @@
// Can be used as a base for any test here, does not depend on the fixture GTest parameters.
class AudioCoreModuleBase {
public:
- // Default buffer sizes are used mostly for negative tests.
- static constexpr int kDefaultBufferSizeFrames = 256;
+ // Fixed buffer size are used for negative tests only. For any tests involving stream
+ // opening that must success, the minimum buffer size must be obtained from a patch.
+ // This is implemented by the 'StreamFixture' utility class.
+ static constexpr int kNegativeTestBufferSizeFrames = 256;
static constexpr int kDefaultLargeBufferSizeFrames = 48000;
- void SetUpImpl(const std::string& moduleName) {
- ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName));
+ void SetUpImpl(const std::string& moduleName, bool setUpDebug = true) {
+ ASSERT_NO_FATAL_FAILURE(ConnectToService(moduleName, setUpDebug));
ASSERT_IS_OK(module->getAudioPorts(&initialPorts));
ASSERT_IS_OK(module->getAudioRoutes(&initialRoutes));
}
void TearDownImpl() {
debug.reset();
+ ASSERT_NE(module, nullptr);
std::vector<AudioPort> finalPorts;
ASSERT_IS_OK(module->getAudioPorts(&finalPorts));
EXPECT_NO_FATAL_FAILURE(VerifyVectorsAreEqual<AudioPort>(initialPorts, finalPorts))
@@ -438,21 +457,26 @@
<< "The list of audio routes was not restored to the initial state";
}
- void ConnectToService(const std::string& moduleName) {
+ void ConnectToService(const std::string& moduleName, bool setUpDebug) {
ASSERT_EQ(module, nullptr);
ASSERT_EQ(debug, nullptr);
module = IModule::fromBinder(binderUtil.connectToService(moduleName));
ASSERT_NE(module, nullptr);
- ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+ if (setUpDebug) {
+ ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+ }
}
void RestartService() {
ASSERT_NE(module, nullptr);
moduleConfig.reset();
+ const bool setUpDebug = !!debug;
debug.reset();
module = IModule::fromBinder(binderUtil.restartService());
ASSERT_NE(module, nullptr);
- ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+ if (setUpDebug) {
+ ASSERT_NO_FATAL_FAILURE(SetUpDebug());
+ }
}
void SetUpDebug() {
@@ -492,9 +516,7 @@
const std::string& errorMessage) {
std::vector<Entity> entities;
{ ASSERT_IS_OK((module.get()->*getter)(&entities)); }
- std::transform(entities.begin(), entities.end(),
- std::inserter(*entityIds, entityIds->begin()),
- [](const auto& entity) { return entity.id; });
+ *entityIds = extractIds<Entity>(entities);
EXPECT_EQ(entities.size(), entityIds->size()) << errorMessage;
}
@@ -1111,6 +1133,7 @@
template <typename T>
struct IOTraits {
static constexpr bool is_input = std::is_same_v<T, IStreamIn>;
+ static constexpr const char* directionStr = is_input ? "input" : "output";
using Worker = std::conditional_t<is_input, StreamReader, StreamWriter>;
};
@@ -1142,8 +1165,7 @@
}
ScopedAStatus SetUpNoChecks(IModule* module, const AudioPortConfig& portConfig,
long bufferSizeFrames);
- void SetUp(IModule* module, long bufferSizeFrames) {
- ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+ void SetUpStream(IModule* module, long bufferSizeFrames) {
ASSERT_IS_OK(SetUpNoChecks(module, bufferSizeFrames)) << "port config id " << getPortId();
ASSERT_NE(nullptr, mStream) << "port config id " << getPortId();
EXPECT_GE(mDescriptor.bufferSizeFrames, bufferSizeFrames)
@@ -1151,6 +1173,10 @@
mContext.emplace(mDescriptor);
ASSERT_NO_FATAL_FAILURE(mContext.value().checkIsValid());
}
+ void SetUp(IModule* module, long bufferSizeFrames) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module));
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module, bufferSizeFrames));
+ }
Stream* get() const { return mStream.get(); }
const StreamContext* getContext() const { return mContext ? &(mContext.value()) : nullptr; }
StreamEventReceiver* getEventReceiver() { return mStreamCallback->getEventReceiver(); }
@@ -1290,6 +1316,9 @@
}
int32_t getId() const { return mPatch.id; }
const AudioPatch& get() const { return mPatch; }
+ int32_t getMinimumStreamBufferSizeFrames() const {
+ return mPatch.minimumStreamBufferSizeFrames;
+ }
const AudioPortConfig& getSinkPortConfig() const { return mSinkPortConfig.get(); }
const AudioPortConfig& getSrcPortConfig() const { return mSrcPortConfig.get(); }
const AudioPortConfig& getPortConfig(bool getSink) const {
@@ -1502,8 +1531,8 @@
EXPECT_EQ(portConnected.get(), connectedPort);
const auto& portProfiles = connectedPort.profiles;
if (portProfiles.empty()) {
- const auto routableMixPorts =
- moduleConfig->getRoutableMixPortsForDevicePort(connectedPort);
+ const auto routableMixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+ connectedPort, true /*connectedOnly*/);
bool hasMixPortWithStaticProfile = false;
for (const auto& mixPort : routableMixPorts) {
const auto& mixPortProfiles = mixPort.profiles;
@@ -1544,7 +1573,7 @@
{
aidl::android::hardware::audio::core::IModule::OpenInputStreamArguments args;
args.portConfigId = portConfigId;
- args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
aidl::android::hardware::audio::core::IModule::OpenInputStreamReturn ret;
EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openInputStream(args, &ret))
<< "port config ID " << portConfigId;
@@ -1553,7 +1582,7 @@
{
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
args.portConfigId = portConfigId;
- args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ args.bufferSizeFrames = kNegativeTestBufferSizeFrames;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
<< "port config ID " << portConfigId;
@@ -1716,6 +1745,10 @@
doNotSimulateConnections.flags().simulateDeviceConnections = false;
ASSERT_NO_FATAL_FAILURE(doNotSimulateConnections.SetUp(module.get()));
for (const auto& port : ports) {
+ // Virtual devices may not require external hardware and thus can always be connected.
+ if (port.ext.get<AudioPortExt::device>().device.type.connection ==
+ AudioDeviceDescription::CONNECTION_VIRTUAL)
+ continue;
AudioPort portWithData = GenerateUniqueDeviceAddress(port), connectedPort;
ScopedAStatus status = module->connectExternalDevice(portWithData, &connectedPort);
EXPECT_STATUS(EX_ILLEGAL_STATE, status) << "static port " << portWithData.toString();
@@ -2562,6 +2595,260 @@
std::vector<std::string> mStatuses;
};
+// A helper which sets up necessary HAL structures for a proper stream initialization.
+//
+// The full sequence of actions to set up a stream is as follows:
+//
+// device port -> connect if necessary -> set up port config | -> set up patch
+// mix port -> set up port config, unless it has been provided |
+//
+// then, from the patch, figure out the minimum HAL buffer size -> set up stream
+//
+// This sequence is reflected in the order of fields declaration.
+// Various tests need to be able to start and stop at various point in this sequence,
+// this is why there are methods that do just part of the work.
+//
+// Note: To maximize test coverage, this class relies on simulation of external device
+// connections by the HAL module.
+template <typename Stream>
+class StreamFixture {
+ public:
+ // Tests might need to override the direction.
+ StreamFixture(bool isInput = IOTraits<Stream>::is_input) : mIsInput(isInput) {}
+
+ void SetUpPortConfigAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+ bool connectedOnly) {
+ const auto mixPorts = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+ mSkipTestReason = "No mix ports";
+ for (const auto& mixPort : mixPorts) {
+ mSkipTestReason = "";
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort,
+ connectedOnly));
+ if (mSkipTestReason.empty()) break;
+ }
+ }
+
+ void SetUpPortConfigForMixPortOrConfig(
+ IModule* module, ModuleConfig* moduleConfig, const AudioPort& initialMixPort,
+ bool connectedOnly, const std::optional<AudioPortConfig>& mixPortConfig = {}) {
+ if (mixPortConfig.has_value() && !connectedOnly) {
+ // Connecting an external device may cause change in mix port profiles and the provided
+ // config may become invalid.
+ LOG(FATAL) << __func__ << ": when specifying a mix port config, it is not allowed "
+ << "to change connected devices, thus `connectedOnly` must be `true`";
+ }
+ std::optional<AudioPort> connectedDevicePort;
+ ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, initialMixPort,
+ connectedOnly, &connectedDevicePort));
+ if (!mSkipTestReason.empty()) return;
+ if (mixPortConfig.has_value()) {
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPortConfig(module, moduleConfig, *mixPortConfig, *connectedDevicePort));
+ } else {
+ // If an external device was connected, the profiles of the mix port might have changed.
+ AudioPort mixPort;
+ ASSERT_NO_FATAL_FAILURE(module->getAudioPort(initialMixPort.id, &mixPort));
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPortConfig(module, moduleConfig, mixPort, *connectedDevicePort));
+ }
+ }
+
+ void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig, const AudioPort& mixPort,
+ const AudioPort& devicePort) {
+ auto mixPortConfig = moduleConfig->getSingleConfigForMixPort(mIsInput, mixPort);
+ ASSERT_TRUE(mixPortConfig.has_value())
+ << "Unable to generate port config for mix port " << mixPort.toString();
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, *mixPortConfig, devicePort));
+ }
+ void SetUpPortConfig(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPatch(module, moduleConfig, mixPortConfig, devicePort));
+ mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+ ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+ }
+
+ ScopedAStatus SetUpStreamNoChecks(IModule* module) {
+ return mStream->SetUpNoChecks(module, getMinimumStreamBufferSizeFrames());
+ }
+ void SetUpStream(IModule* module) {
+ ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, getMinimumStreamBufferSizeFrames()));
+ }
+
+ void SetUpStreamForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPort& devicePort, bool connectedOnly = false) {
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPortConfigForDevicePort(module, moduleConfig, devicePort, connectedOnly));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+ }
+ void SetUpStreamForAnyMixPort(IModule* module, ModuleConfig* moduleConfig,
+ bool connectedOnly = false) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfigAnyMixPort(module, moduleConfig, connectedOnly));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+ }
+ void SetUpStreamForMixPort(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPort& mixPort, bool connectedOnly = false) {
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPortConfigForMixPortOrConfig(module, moduleConfig, mixPort, connectedOnly));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+ }
+ void SetUpStreamForPortsPair(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPort& mixPort, const AudioPort& devicePort) {
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfig(module, moduleConfig, mixPort, devicePort));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+ }
+ void SetUpStreamForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPortConfig& mixPortConfig) {
+ // Since mix port configs may change after connecting an external device,
+ // only connected device ports are considered.
+ constexpr bool connectedOnly = true;
+ const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+ const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+ ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+ ASSERT_NO_FATAL_FAILURE(SetUpPortConfigForMixPortOrConfig(module, moduleConfig, *mixPortIt,
+ connectedOnly, mixPortConfig));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(SetUpStream(module));
+ }
+ void SetUpPatchForMixPortConfig(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPortConfig& mixPortConfig) {
+ constexpr bool connectedOnly = true;
+ const auto& ports = moduleConfig->getMixPorts(mIsInput, connectedOnly);
+ const auto mixPortIt = findById<AudioPort>(ports, mixPortConfig.portId);
+ ASSERT_NE(mixPortIt, ports.end()) << "Port id " << mixPortConfig.portId << " not found";
+ std::optional<AudioPort> connectedDevicePort;
+ ASSERT_NO_FATAL_FAILURE(SetUpDevicePortForMixPort(module, moduleConfig, *mixPortIt,
+ connectedOnly, &connectedDevicePort));
+ if (!mSkipTestReason.empty()) return;
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPatch(module, moduleConfig, mixPortConfig, *connectedDevicePort));
+ }
+
+ void ReconnectPatch(IModule* module) {
+ mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+ mDevicePortConfig->get());
+ ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+ }
+ void TeardownPatch() { mPatch.reset(); }
+ // Assuming that the patch is set up, while the stream isn't yet,
+ // tear the patch down and set up stream.
+ void TeardownPatchSetUpStream(IModule* module) {
+ const int32_t bufferSize = getMinimumStreamBufferSizeFrames();
+ ASSERT_NO_FATAL_FAILURE(TeardownPatch());
+ mStream = std::make_unique<WithStream<Stream>>(mMixPortConfig->get());
+ ASSERT_NO_FATAL_FAILURE(mStream->SetUpPortConfig(module));
+ ASSERT_NO_FATAL_FAILURE(mStream->SetUpStream(module, bufferSize));
+ }
+
+ const AudioDevice& getDevice() const { return mDevice; }
+ int32_t getMinimumStreamBufferSizeFrames() const {
+ return mPatch->getMinimumStreamBufferSizeFrames();
+ }
+ const AudioPatch& getPatch() const { return mPatch->get(); }
+ const AudioPortConfig& getPortConfig() const { return mMixPortConfig->get(); }
+ int32_t getPortId() const { return mMixPortConfig->getId(); }
+ Stream* getStream() const { return mStream->get(); }
+ const StreamContext* getStreamContext() const { return mStream->getContext(); }
+ StreamEventReceiver* getStreamEventReceiver() { return mStream->getEventReceiver(); }
+ std::shared_ptr<Stream> getStreamSharedPointer() const { return mStream->getSharedPointer(); }
+ const std::string& skipTestReason() const { return mSkipTestReason; }
+
+ private:
+ void SetUpDevicePort(IModule* module, ModuleConfig* moduleConfig,
+ const std::set<int32_t>& devicePortIds, bool connectedOnly,
+ std::optional<AudioPort>* connectedDevicePort) {
+ const auto attachedDevicePorts = moduleConfig->getAttachedDevicePorts();
+ if (auto it = findAny<AudioPort>(attachedDevicePorts, devicePortIds);
+ it != attachedDevicePorts.end()) {
+ *connectedDevicePort = *it;
+ LOG(DEBUG) << __func__ << ": found attached port " << it->toString();
+ }
+ const auto connectedDevicePorts = moduleConfig->getConnectedExternalDevicePorts();
+ if (auto it = findAny<AudioPort>(connectedDevicePorts, devicePortIds);
+ it != connectedDevicePorts.end()) {
+ *connectedDevicePort = *it;
+ LOG(DEBUG) << __func__ << ": found connected port " << it->toString();
+ }
+ if (!connectedOnly && !connectedDevicePort->has_value()) {
+ const auto externalDevicePorts = moduleConfig->getExternalDevicePorts();
+ if (auto it = findAny<AudioPort>(externalDevicePorts, devicePortIds);
+ it != externalDevicePorts.end()) {
+ AudioPort portWithData = GenerateUniqueDeviceAddress(*it);
+ mPortConnected = std::make_unique<WithDevicePortConnectedState>(portWithData);
+ ASSERT_NO_FATAL_FAILURE(mPortConnected->SetUp(module, moduleConfig));
+ *connectedDevicePort = mPortConnected->get();
+ LOG(DEBUG) << __func__ << ": connected port " << mPortConnected->get().toString();
+ }
+ }
+ }
+ void SetUpDevicePortForMixPort(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPort& mixPort, bool connectedOnly,
+ std::optional<AudioPort>* connectedDevicePort) {
+ const auto devicePorts =
+ moduleConfig->getRoutableDevicePortsForMixPort(mixPort, connectedOnly);
+ if (devicePorts.empty()) {
+ mSkipTestReason = std::string("No routable device ports found for mix port id ")
+ .append(std::to_string(mixPort.id));
+ LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+ return;
+ };
+ ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig,
+ extractIds<AudioPort>(devicePorts), connectedOnly,
+ connectedDevicePort));
+ if (!connectedDevicePort->has_value()) {
+ mSkipTestReason = std::string("Unable to find a device port pair for mix port id ")
+ .append(std::to_string(mixPort.id));
+ LOG(DEBUG) << __func__ << ": " << mSkipTestReason;
+ return;
+ }
+ }
+ void SetUpPortConfigForDevicePort(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPort& devicePort, bool connectedOnly) {
+ std::optional<AudioPort> connectedDevicePort;
+ ASSERT_NO_FATAL_FAILURE(SetUpDevicePort(module, moduleConfig, {devicePort.id},
+ connectedOnly, &connectedDevicePort));
+ if (!connectedDevicePort.has_value()) {
+ mSkipTestReason = std::string("Device port id ")
+ .append(std::to_string(devicePort.id))
+ .append(" is not attached and can not be connected");
+ return;
+ }
+ const auto mixPorts = moduleConfig->getRoutableMixPortsForDevicePort(
+ *connectedDevicePort, true /*connectedOnly*/);
+ if (mixPorts.empty()) {
+ mSkipTestReason = std::string("No routable mix ports found for device port id ")
+ .append(std::to_string(devicePort.id));
+ return;
+ }
+ ASSERT_NO_FATAL_FAILURE(
+ SetUpPortConfig(module, moduleConfig, *mixPorts.begin(), *connectedDevicePort));
+ }
+ void SetUpPatch(IModule* module, ModuleConfig* moduleConfig,
+ const AudioPortConfig& mixPortConfig, const AudioPort& devicePort) {
+ mMixPortConfig = std::make_unique<WithAudioPortConfig>(mixPortConfig);
+ ASSERT_NO_FATAL_FAILURE(mMixPortConfig->SetUp(module));
+ mDevicePortConfig = std::make_unique<WithAudioPortConfig>(
+ moduleConfig->getSingleConfigForDevicePort(devicePort));
+ ASSERT_NO_FATAL_FAILURE(mDevicePortConfig->SetUp(module));
+ mDevice = devicePort.ext.get<AudioPortExt::device>().device;
+ mPatch = std::make_unique<WithAudioPatch>(mIsInput, mMixPortConfig->get(),
+ mDevicePortConfig->get());
+ ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(module));
+ }
+
+ const bool mIsInput;
+ std::string mSkipTestReason;
+ std::unique_ptr<WithDevicePortConnectedState> mPortConnected;
+ AudioDevice mDevice;
+ std::unique_ptr<WithAudioPortConfig> mMixPortConfig;
+ std::unique_ptr<WithAudioPortConfig> mDevicePortConfig;
+ std::unique_ptr<WithAudioPatch> mPatch;
+ std::unique_ptr<WithStream<Stream>> mStream;
+};
+
template <typename Stream>
class AudioStream : public AudioCoreModule {
public:
@@ -2571,16 +2858,15 @@
}
void GetStreamCommon() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
std::shared_ptr<IStreamCommon> streamCommon1;
- EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon1));
+ EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon1));
std::shared_ptr<IStreamCommon> streamCommon2;
- EXPECT_IS_OK(stream.get()->getStreamCommon(&streamCommon2));
+ EXPECT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon2));
ASSERT_NE(nullptr, streamCommon1);
ASSERT_NE(nullptr, streamCommon2);
EXPECT_EQ(streamCommon1->asBinder(), streamCommon2->asBinder())
@@ -2588,31 +2874,31 @@
}
void CloseTwice() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
std::shared_ptr<Stream> heldStream;
{
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
- heldStream = stream.getSharedPointer();
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(
+ stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
+ }
+ heldStream = stream.getStreamSharedPointer();
}
EXPECT_STATUS(EX_ILLEGAL_STATE, WithStream<Stream>::callClose(heldStream))
<< "when closing the stream twice";
}
void PrepareToCloseTwice() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
std::shared_ptr<IStreamCommon> heldStreamCommon;
{
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(
+ stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
+ }
std::shared_ptr<IStreamCommon> streamCommon;
- ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+ ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
heldStreamCommon = streamCommon;
EXPECT_IS_OK(streamCommon->prepareToClose());
EXPECT_IS_OK(streamCommon->prepareToClose())
@@ -2625,9 +2911,13 @@
void OpenAllConfigs() {
const auto allPortConfigs =
moduleConfig->getPortConfigsForMixPorts(IOTraits<Stream>::is_input);
+ if (allPortConfigs.empty()) {
+ GTEST_SKIP() << "No mix ports for attached devices";
+ }
for (const auto& portConfig : allPortConfigs) {
- WithStream<Stream> stream(portConfig);
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+ module.get(), moduleConfig.get(), portConfig));
}
}
@@ -2647,22 +2937,21 @@
void OpenInvalidDirection() {
// Important! The direction of the port config must be reversed.
- const auto portConfig =
- moduleConfig->getSingleConfigForMixPort(!IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream(!IOTraits<Stream>::is_input);
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigAnyMixPort(module.get(), moduleConfig.get(),
+ false /*connectedOnly*/));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
- EXPECT_STATUS(EX_ILLEGAL_ARGUMENT,
- stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.SetUpStreamNoChecks(module.get()))
<< "port config ID " << stream.getPortId();
- EXPECT_EQ(nullptr, stream.get());
+ EXPECT_EQ(nullptr, stream.getStream());
}
void OpenOverMaxCount() {
+ constexpr bool connectedOnly = true;
constexpr bool isInput = IOTraits<Stream>::is_input;
- auto ports = moduleConfig->getMixPorts(isInput, true /*connectedOnly*/);
+ auto ports = moduleConfig->getMixPorts(isInput, connectedOnly);
bool hasSingleRun = false;
for (const auto& port : ports) {
const size_t maxStreamCount = port.ext.get<AudioPortExt::Tag::mix>().maxOpenStreamCount;
@@ -2675,16 +2964,16 @@
continue;
}
hasSingleRun = true;
- std::optional<WithStream<Stream>> streamWraps[maxStreamCount + 1];
+ StreamFixture<Stream> streams[maxStreamCount + 1];
for (size_t i = 0; i <= maxStreamCount; ++i) {
- streamWraps[i].emplace(portConfigs[i]);
- WithStream<Stream>& stream = streamWraps[i].value();
+ ASSERT_NO_FATAL_FAILURE(streams[i].SetUpPortConfigForMixPortOrConfig(
+ module.get(), moduleConfig.get(), port, connectedOnly, portConfigs[i]));
+ ASSERT_EQ("", streams[i].skipTestReason());
+ auto& stream = streams[i];
if (i < maxStreamCount) {
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStream(module.get()));
} else {
- ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfig(module.get()));
- EXPECT_STATUS(EX_ILLEGAL_STATE,
- stream.SetUpNoChecks(module.get(), kDefaultBufferSizeFrames))
+ EXPECT_STATUS(EX_ILLEGAL_STATE, stream.SetUpStreamNoChecks(module.get()))
<< "port config ID " << stream.getPortId() << ", maxOpenStreamCount is "
<< maxStreamCount;
}
@@ -2704,12 +2993,11 @@
}
void ResetPortConfigWithOpenStream() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
EXPECT_STATUS(EX_ILLEGAL_STATE, module->resetAudioPortConfig(stream.getPortId()))
<< "port config ID " << stream.getPortId();
}
@@ -2723,14 +3011,13 @@
}
void UpdateHwAvSyncId() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
std::shared_ptr<IStreamCommon> streamCommon;
- ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+ ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
ASSERT_NE(nullptr, streamCommon);
const auto kStatuses = {EX_NONE, EX_ILLEGAL_ARGUMENT, EX_ILLEGAL_STATE};
for (const auto id : {-100, -1, 0, 1, 100}) {
@@ -2743,14 +3030,13 @@
}
void GetVendorParameters() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
std::shared_ptr<IStreamCommon> streamCommon;
- ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+ ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
ASSERT_NE(nullptr, streamCommon);
bool isGetterSupported = false;
@@ -2764,14 +3050,13 @@
}
void SetVendorParameters() {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- GTEST_SKIP() << "No mix port for attached devices";
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForAnyMixPort(module.get(), moduleConfig.get()));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
}
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
std::shared_ptr<IStreamCommon> streamCommon;
- ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+ ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
ASSERT_NE(nullptr, streamCommon);
bool isSupported = false;
@@ -2782,32 +3067,37 @@
}
void HwGainHwVolume() {
- const auto ports =
- moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+ // Since device connection emulation does not cover complete functionality,
+ // only use this test with connected devices.
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
if (ports.empty()) {
GTEST_SKIP() << "No mix ports";
}
bool atLeastOneSupports = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
- if (!portConfig.has_value()) continue;
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+ port, connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ const auto portConfig = stream.getPortConfig();
+ SCOPED_TRACE(portConfig.toString());
std::vector<std::vector<float>> validValues, invalidValues;
bool isSupported = false;
if constexpr (IOTraits<Stream>::is_input) {
- GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+ GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
IStreamIn::HW_GAIN_MIN, IStreamIn::HW_GAIN_MAX,
&validValues, &invalidValues);
EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
- stream.get(), &IStreamIn::getHwGain, &IStreamIn::setHwGain, validValues,
- invalidValues, &isSupported));
+ stream.getStream(), &IStreamIn::getHwGain, &IStreamIn::setHwGain,
+ validValues, invalidValues, &isSupported));
} else {
- GenerateTestArrays<float>(getChannelCount(portConfig.value().channelMask.value()),
+ GenerateTestArrays<float>(getChannelCount(portConfig.channelMask.value()),
IStreamOut::HW_VOLUME_MIN, IStreamOut::HW_VOLUME_MAX,
&validValues, &invalidValues);
EXPECT_NO_FATAL_FAILURE(TestAccessors<std::vector<float>>(
- stream.get(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
+ stream.getStream(), &IStreamOut::getHwVolume, &IStreamOut::setHwVolume,
validValues, invalidValues, &isSupported));
}
if (isSupported) atLeastOneSupports = true;
@@ -2821,19 +3111,22 @@
// currently we can only pass a nullptr, and the HAL module must either reject
// it as an invalid argument, or say that offloaded effects are not supported.
void AddRemoveEffectInvalidArguments() {
- const auto ports =
- moduleConfig->getMixPorts(IOTraits<Stream>::is_input, true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getMixPorts(IOTraits<Stream>::is_input, connectedOnly);
if (ports.empty()) {
GTEST_SKIP() << "No mix ports";
}
bool atLeastOneSupports = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
- if (!portConfig.has_value()) continue;
- WithStream<Stream> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(),
+ port, connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ const auto portConfig = stream.getPortConfig();
+ SCOPED_TRACE(portConfig.toString());
std::shared_ptr<IStreamCommon> streamCommon;
- ASSERT_IS_OK(stream.get()->getStreamCommon(&streamCommon));
+ ASSERT_IS_OK(stream.getStream()->getStreamCommon(&streamCommon));
ASSERT_NE(nullptr, streamCommon);
ndk::ScopedAStatus addEffectStatus = streamCommon->addEffect(nullptr);
ndk::ScopedAStatus removeEffectStatus = streamCommon->removeEffect(nullptr);
@@ -2853,11 +3146,14 @@
}
void OpenTwiceSamePortConfigImpl(const AudioPortConfig& portConfig) {
- WithStream<Stream> stream1(portConfig);
- ASSERT_NO_FATAL_FAILURE(stream1.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream1;
+ ASSERT_NO_FATAL_FAILURE(
+ stream1.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+ ASSERT_EQ("", stream1.skipTestReason());
WithStream<Stream> stream2;
- EXPECT_STATUS(EX_ILLEGAL_STATE, stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
- kDefaultBufferSizeFrames))
+ EXPECT_STATUS(EX_ILLEGAL_STATE,
+ stream2.SetUpNoChecks(module.get(), stream1.getPortConfig(),
+ stream1.getMinimumStreamBufferSizeFrames()))
<< "when opening a stream twice for the same port config ID "
<< stream1.getPortId();
}
@@ -2892,11 +3188,13 @@
for (const auto& seq : sequences) {
SCOPED_TRACE(std::string("Sequence ").append(seq.first));
LOG(DEBUG) << __func__ << ": Sequence " << seq.first;
- WithStream<Stream> stream(portConfig);
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPortConfig(
+ module.get(), moduleConfig.get(), portConfig));
+ ASSERT_EQ("", stream.skipTestReason());
StreamLogicDriverInvalidCommand driver(seq.second);
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
- stream.getEventReceiver());
+ typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamEventReceiver());
LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
LOG(DEBUG) << __func__ << ": joining worker...";
@@ -2949,63 +3247,59 @@
if (ports.empty()) {
GTEST_SKIP() << "No input mix ports for attached devices";
}
+ bool atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
- WithStream<IStreamIn> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
- {
- // The port of the stream is not connected, thus the list of active mics must be empty.
- std::vector<MicrophoneDynamicInfo> activeMics;
- EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
- EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
- "non-empty list of active microphones";
+ auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
+ moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
+ if (micDevicePorts.empty()) continue;
+ atLeastOnePort = true;
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamIn> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForPortsPair(module.get(), moduleConfig.get(),
+ port, micDevicePorts[0]));
+ if (!stream.skipTestReason().empty()) continue;
+ std::vector<MicrophoneDynamicInfo> activeMics;
+ EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&activeMics));
+ EXPECT_FALSE(activeMics.empty());
+ for (const auto& mic : activeMics) {
+ EXPECT_NE(micInfos.end(),
+ std::find_if(micInfos.begin(), micInfos.end(),
+ [&](const auto& micInfo) { return micInfo.id == mic.id; }))
+ << "active microphone \"" << mic.id << "\" is not listed in "
+ << "microphone infos returned by the module: "
+ << ::android::internal::ToString(micInfos);
+ EXPECT_NE(0UL, mic.channelMapping.size())
+ << "No channels specified for the microphone \"" << mic.id << "\"";
}
- if (auto micDevicePorts = ModuleConfig::getBuiltInMicPorts(
- moduleConfig->getConnectedSourceDevicesPortsForMixPort(port));
- !micDevicePorts.empty()) {
- auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(micDevicePorts[0]);
- WithAudioPatch patch(true /*isInput*/, stream.getPortConfig(), devicePortConfig);
- ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
- std::vector<MicrophoneDynamicInfo> activeMics;
- EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
- EXPECT_FALSE(activeMics.empty());
- for (const auto& mic : activeMics) {
- EXPECT_NE(micInfos.end(),
- std::find_if(micInfos.begin(), micInfos.end(),
- [&](const auto& micInfo) { return micInfo.id == mic.id; }))
- << "active microphone \"" << mic.id << "\" is not listed in "
- << "microphone infos returned by the module: "
- << ::android::internal::ToString(micInfos);
- EXPECT_NE(0UL, mic.channelMapping.size())
- << "No channels specified for the microphone \"" << mic.id << "\"";
- }
- }
- {
- // Now the port of the stream is not connected again, re-check that there are no
- // active microphones.
- std::vector<MicrophoneDynamicInfo> activeMics;
- EXPECT_IS_OK(stream.get()->getActiveMicrophones(&activeMics));
- EXPECT_TRUE(activeMics.empty()) << "a stream on an unconnected port returns a "
- "non-empty list of active microphones";
- }
+ stream.TeardownPatch();
+ // Now the port of the stream is not connected, check that there are no active microphones.
+ std::vector<MicrophoneDynamicInfo> emptyMics;
+ EXPECT_IS_OK(stream.getStream()->getActiveMicrophones(&emptyMics));
+ EXPECT_TRUE(emptyMics.empty()) << "a stream on an unconnected port returns a "
+ "non-empty list of active microphones";
+ }
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
}
}
TEST_P(AudioStreamIn, MicrophoneDirection) {
using MD = IStreamIn::MicrophoneDirection;
- const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
if (ports.empty()) {
GTEST_SKIP() << "No input mix ports for attached devices";
}
- bool isSupported = false;
+ bool isSupported = false, atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
- WithStream<IStreamIn> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamIn> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+ connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ atLeastOnePort = true;
EXPECT_NO_FATAL_FAILURE(
- TestAccessors<MD>(stream.get(), &IStreamIn::getMicrophoneDirection,
+ TestAccessors<MD>(stream.getStream(), &IStreamIn::getMicrophoneDirection,
&IStreamIn::setMicrophoneDirection,
std::vector<MD>(enum_range<MD>().begin(), enum_range<MD>().end()),
{}, &isSupported));
@@ -3014,21 +3308,27 @@
if (!isSupported) {
GTEST_SKIP() << "Microphone direction is not supported";
}
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+ }
}
TEST_P(AudioStreamIn, MicrophoneFieldDimension) {
- const auto ports = moduleConfig->getInputMixPorts(true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getInputMixPorts(connectedOnly);
if (ports.empty()) {
GTEST_SKIP() << "No input mix ports for attached devices";
}
- bool isSupported = false;
+ bool isSupported = false, atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(true, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for input mix port";
- WithStream<IStreamIn> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamIn> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+ connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ atLeastOnePort = true;
EXPECT_NO_FATAL_FAILURE(TestAccessors<float>(
- stream.get(), &IStreamIn::getMicrophoneFieldDimension,
+ stream.getStream(), &IStreamIn::getMicrophoneFieldDimension,
&IStreamIn::setMicrophoneFieldDimension,
{IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE,
IStreamIn::MIC_FIELD_DIMENSION_WIDE_ANGLE / 2.0f,
@@ -3045,6 +3345,9 @@
if (!isSupported) {
GTEST_SKIP() << "Microphone direction is not supported";
}
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No input mix ports could be routed to built-in microphone devices";
+ }
}
TEST_P(AudioStreamOut, OpenTwicePrimary) {
@@ -3059,65 +3362,79 @@
}
TEST_P(AudioStreamOut, RequireOffloadInfo) {
+ constexpr bool connectedOnly = true;
const auto offloadMixPorts =
- moduleConfig->getOffloadMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+ moduleConfig->getOffloadMixPorts(connectedOnly, true /*singlePort*/);
if (offloadMixPorts.empty()) {
GTEST_SKIP()
<< "No mix port for compressed offload that could be routed to attached devices";
}
- const auto config = moduleConfig->getSingleConfigForMixPort(false, *offloadMixPorts.begin());
- ASSERT_TRUE(config.has_value()) << "No profiles specified for the compressed offload mix port";
- WithAudioPortConfig portConfig(config.value());
- ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+ StreamFixture<IStreamOut> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+ module.get(), moduleConfig.get(), *offloadMixPorts.begin(), connectedOnly));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
+ }
+ const auto portConfig = stream.getPortConfig();
StreamDescriptor descriptor;
- std::shared_ptr<IStreamOut> ignored;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
- args.portConfigId = portConfig.getId();
- args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
+ args.portConfigId = portConfig.id;
+ args.sourceMetadata = GenerateSourceMetadata(portConfig);
args.bufferSizeFrames = kDefaultLargeBufferSizeFrames;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
<< "when no offload info is provided for a compressed offload mix port";
+ if (ret.stream != nullptr) {
+ (void)WithStream<IStreamOut>::callClose(ret.stream);
+ }
}
TEST_P(AudioStreamOut, RequireAsyncCallback) {
+ constexpr bool connectedOnly = true;
const auto nonBlockingMixPorts =
- moduleConfig->getNonBlockingMixPorts(true /*connectedOnly*/, true /*singlePort*/);
+ moduleConfig->getNonBlockingMixPorts(connectedOnly, true /*singlePort*/);
if (nonBlockingMixPorts.empty()) {
GTEST_SKIP()
<< "No mix port for non-blocking output that could be routed to attached devices";
}
- const auto config =
- moduleConfig->getSingleConfigForMixPort(false, *nonBlockingMixPorts.begin());
- ASSERT_TRUE(config.has_value()) << "No profiles specified for the non-blocking mix port";
- WithAudioPortConfig portConfig(config.value());
- ASSERT_NO_FATAL_FAILURE(portConfig.SetUp(module.get()));
+ StreamFixture<IStreamOut> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpPortConfigForMixPortOrConfig(
+ module.get(), moduleConfig.get(), *nonBlockingMixPorts.begin(), connectedOnly));
+ if (auto reason = stream.skipTestReason(); !reason.empty()) {
+ GTEST_SKIP() << reason;
+ }
+ const auto portConfig = stream.getPortConfig();
StreamDescriptor descriptor;
- std::shared_ptr<IStreamOut> ignored;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamArguments args;
- args.portConfigId = portConfig.getId();
- args.sourceMetadata = GenerateSourceMetadata(portConfig.get());
- args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig.get());
- args.bufferSizeFrames = kDefaultBufferSizeFrames;
+ args.portConfigId = portConfig.id;
+ args.sourceMetadata = GenerateSourceMetadata(portConfig);
+ args.offloadInfo = ModuleConfig::generateOffloadInfoIfNeeded(portConfig);
+ args.bufferSizeFrames = stream.getPatch().minimumStreamBufferSizeFrames;
aidl::android::hardware::audio::core::IModule::OpenOutputStreamReturn ret;
EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, module->openOutputStream(args, &ret))
<< "when no async callback is provided for a non-blocking mix port";
+ if (ret.stream != nullptr) {
+ (void)WithStream<IStreamOut>::callClose(ret.stream);
+ }
}
TEST_P(AudioStreamOut, AudioDescriptionMixLevel) {
- const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
if (ports.empty()) {
- GTEST_SKIP() << "No output mix ports";
+ GTEST_SKIP() << "No output mix ports for attached devices";
}
- bool atLeastOneSupports = false;
+ bool atLeastOneSupports = false, atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
- WithStream<IStreamOut> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamOut> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+ connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ atLeastOnePort = true;
bool isSupported = false;
EXPECT_NO_FATAL_FAILURE(
- TestAccessors<float>(stream.get(), &IStreamOut::getAudioDescriptionMixLevel,
+ TestAccessors<float>(stream.getStream(), &IStreamOut::getAudioDescriptionMixLevel,
&IStreamOut::setAudioDescriptionMixLevel,
{IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX,
IStreamOut::AUDIO_DESCRIPTION_MIX_LEVEL_MAX - 1, 0,
@@ -3127,48 +3444,60 @@
&isSupported));
if (isSupported) atLeastOneSupports = true;
}
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No output mix ports could be routed to devices";
+ }
if (!atLeastOneSupports) {
GTEST_SKIP() << "Audio description mix level is not supported";
}
}
TEST_P(AudioStreamOut, DualMonoMode) {
- const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
if (ports.empty()) {
- GTEST_SKIP() << "No output mix ports";
+ GTEST_SKIP() << "No output mix ports for attached devices";
}
- bool atLeastOneSupports = false;
+ bool atLeastOneSupports = false, atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
- WithStream<IStreamOut> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamOut> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+ connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ atLeastOnePort = true;
bool isSupported = false;
EXPECT_NO_FATAL_FAILURE(TestAccessors<AudioDualMonoMode>(
- stream.get(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
+ stream.getStream(), &IStreamOut::getDualMonoMode, &IStreamOut::setDualMonoMode,
std::vector<AudioDualMonoMode>(enum_range<AudioDualMonoMode>().begin(),
enum_range<AudioDualMonoMode>().end()),
{}, &isSupported));
if (isSupported) atLeastOneSupports = true;
}
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No output mix ports could be routed to devices";
+ }
if (!atLeastOneSupports) {
GTEST_SKIP() << "Audio dual mono mode is not supported";
}
}
TEST_P(AudioStreamOut, LatencyMode) {
- const auto ports = moduleConfig->getOutputMixPorts(true /*connectedOnly*/);
+ constexpr bool connectedOnly = true;
+ const auto ports = moduleConfig->getOutputMixPorts(connectedOnly);
if (ports.empty()) {
- GTEST_SKIP() << "No output mix ports";
+ GTEST_SKIP() << "No output mix ports for attached devices";
}
- bool atLeastOneSupports = false;
+ bool atLeastOneSupports = false, atLeastOnePort = false;
for (const auto& port : ports) {
- const auto portConfig = moduleConfig->getSingleConfigForMixPort(false, port);
- ASSERT_TRUE(portConfig.has_value()) << "No profiles specified for output mix port";
- WithStream<IStreamOut> stream(portConfig.value());
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ SCOPED_TRACE(port.toString());
+ StreamFixture<IStreamOut> stream;
+ ASSERT_NO_FATAL_FAILURE(stream.SetUpStreamForMixPort(module.get(), moduleConfig.get(), port,
+ connectedOnly));
+ if (!stream.skipTestReason().empty()) continue;
+ atLeastOnePort = true;
std::vector<AudioLatencyMode> supportedModes;
- ndk::ScopedAStatus status = stream.get()->getRecommendedLatencyModes(&supportedModes);
+ ndk::ScopedAStatus status = stream.getStream()->getRecommendedLatencyModes(&supportedModes);
if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) continue;
atLeastOneSupports = true;
if (!status.isOk()) {
@@ -3180,7 +3509,7 @@
enum_range<AudioLatencyMode>().end());
for (const auto mode : supportedModes) {
unsupportedModes.erase(mode);
- ndk::ScopedAStatus status = stream.get()->setLatencyMode(mode);
+ ndk::ScopedAStatus status = stream.getStream()->setLatencyMode(mode);
if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) {
ADD_FAILURE() << "When latency modes are supported, both getRecommendedLatencyModes"
<< " and setLatencyMode must be supported";
@@ -3188,12 +3517,15 @@
EXPECT_IS_OK(status) << "Setting of supported latency mode must succeed";
}
for (const auto mode : unsupportedModes) {
- EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.get()->setLatencyMode(mode));
+ EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, stream.getStream()->setLatencyMode(mode));
}
}
if (!atLeastOneSupports) {
GTEST_SKIP() << "Audio latency modes are not supported";
}
+ if (!atLeastOnePort) {
+ GTEST_SKIP() << "No output mix ports could be routed to devices";
+ }
}
TEST_P(AudioStreamOut, PlaybackRate) {
@@ -3495,29 +3827,22 @@
}
}
- bool ValidateObservablePosition(const AudioPortConfig& devicePortConfig) {
- return !isTelephonyDeviceType(
- devicePortConfig.ext.get<AudioPortExt::Tag::device>().device.type.type);
+ bool ValidateObservablePosition(const AudioDevice& device) {
+ return !isTelephonyDeviceType(device.type.type);
}
// Set up a patch first, then open a stream.
void RunStreamIoCommandsImplSeq1(const AudioPortConfig& portConfig,
std::shared_ptr<StateSequence> commandsAndStates,
bool validatePositionIncrease) {
- auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
- IOTraits<Stream>::is_input, portConfig);
- ASSERT_FALSE(devicePorts.empty());
- auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
- SCOPED_TRACE(devicePortConfig.toString());
- WithAudioPatch patch(IOTraits<Stream>::is_input, portConfig, devicePortConfig);
- ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
-
- WithStream<Stream> stream(patch.getPortConfig(IOTraits<Stream>::is_input));
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(
+ stream.SetUpStreamForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+ ASSERT_EQ("", stream.skipTestReason());
StreamLogicDefaultDriver driver(commandsAndStates,
- stream.getContext()->getFrameSizeBytes());
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
- stream.getEventReceiver());
+ stream.getStreamContext()->getFrameSizeBytes());
+ typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamEventReceiver());
LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
@@ -3525,7 +3850,7 @@
worker.join();
EXPECT_FALSE(worker.hasError()) << worker.getError();
EXPECT_EQ("", driver.getUnexpectedStateTransition());
- if (ValidateObservablePosition(devicePortConfig)) {
+ if (ValidateObservablePosition(stream.getDevice())) {
if (validatePositionIncrease) {
EXPECT_TRUE(driver.hasObservablePositionIncrease());
}
@@ -3533,24 +3858,21 @@
}
}
- // Open a stream, then set up a patch for it.
+ // Open a stream, then set up a patch for it. Since first it is needed to get
+ // the minimum buffer size, a preliminary patch is set up, then removed.
void RunStreamIoCommandsImplSeq2(const AudioPortConfig& portConfig,
std::shared_ptr<StateSequence> commandsAndStates,
bool validatePositionIncrease) {
- WithStream<Stream> stream(portConfig);
- ASSERT_NO_FATAL_FAILURE(stream.SetUp(module.get(), kDefaultBufferSizeFrames));
+ StreamFixture<Stream> stream;
+ ASSERT_NO_FATAL_FAILURE(
+ stream.SetUpPatchForMixPortConfig(module.get(), moduleConfig.get(), portConfig));
+ ASSERT_EQ("", stream.skipTestReason());
+ ASSERT_NO_FATAL_FAILURE(stream.TeardownPatchSetUpStream(module.get()));
StreamLogicDefaultDriver driver(commandsAndStates,
- stream.getContext()->getFrameSizeBytes());
- typename IOTraits<Stream>::Worker worker(*stream.getContext(), &driver,
- stream.getEventReceiver());
-
- auto devicePorts = moduleConfig->getConnectedDevicesPortsForMixPort(
- IOTraits<Stream>::is_input, portConfig);
- ASSERT_FALSE(devicePorts.empty());
- auto devicePortConfig = moduleConfig->getSingleConfigForDevicePort(devicePorts[0]);
- SCOPED_TRACE(devicePortConfig.toString());
- WithAudioPatch patch(IOTraits<Stream>::is_input, stream.getPortConfig(), devicePortConfig);
- ASSERT_NO_FATAL_FAILURE(patch.SetUp(module.get()));
+ stream.getStreamContext()->getFrameSizeBytes());
+ typename IOTraits<Stream>::Worker worker(*stream.getStreamContext(), &driver,
+ stream.getStreamEventReceiver());
+ ASSERT_NO_FATAL_FAILURE(stream.ReconnectPatch(module.get()));
LOG(DEBUG) << __func__ << ": starting worker...";
ASSERT_TRUE(worker.start());
@@ -3558,7 +3880,7 @@
worker.join();
EXPECT_FALSE(worker.hasError()) << worker.getError();
EXPECT_EQ("", driver.getUnexpectedStateTransition());
- if (ValidateObservablePosition(devicePortConfig)) {
+ if (ValidateObservablePosition(stream.getDevice())) {
if (validatePositionIncrease) {
EXPECT_TRUE(driver.hasObservablePositionIncrease());
}
@@ -4252,191 +4574,154 @@
explicit WithRemoteSubmix(AudioDeviceAddress address) : mAddress(address) {}
WithRemoteSubmix(const WithRemoteSubmix&) = delete;
WithRemoteSubmix& operator=(const WithRemoteSubmix&) = delete;
+
static std::optional<AudioPort> getRemoteSubmixAudioPort(
ModuleConfig* moduleConfig,
const std::optional<AudioDeviceAddress>& address = std::nullopt) {
- AudioDeviceType deviceType = IOTraits<Stream>::is_input ? AudioDeviceType::IN_SUBMIX
- : AudioDeviceType::OUT_SUBMIX;
- auto ports = moduleConfig->getAudioPortsForDeviceTypes(
- std::vector<AudioDeviceType>{deviceType},
- AudioDeviceDescription::CONNECTION_VIRTUAL);
+ auto ports =
+ moduleConfig->getRemoteSubmixPorts(IOTraits<Stream>::is_input, true /*singlePort*/);
if (ports.empty()) return {};
AudioPort port = ports.front();
if (address) {
port.ext.template get<AudioPortExt::Tag::device>().device.address = address.value();
- } else {
- port = GenerateUniqueDeviceAddress(port);
}
return port;
}
- std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
- void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& connectedPort) {
- mModule = module;
- mModuleConfig = moduleConfig;
- ASSERT_NO_FATAL_FAILURE(SetupPatch(connectedPort));
- if (!mSkipTest) {
- // open stream
- mStream = std::make_unique<WithStream<Stream>>(
- mPatch->getPortConfig(IOTraits<Stream>::is_input));
- ASSERT_NO_FATAL_FAILURE(
- mStream->SetUp(mModule, AudioCoreModuleBase::kDefaultBufferSizeFrames));
- }
- mAddress = connectedPort.ext.template get<AudioPortExt::Tag::device>().device.address;
- }
void SetUp(IModule* module, ModuleConfig* moduleConfig) {
- ASSERT_NO_FATAL_FAILURE(SetUpPortConnection(module, moduleConfig));
- SetUp(module, moduleConfig, mConnectedPort->get());
+ auto devicePort = getRemoteSubmixAudioPort(moduleConfig, mAddress);
+ ASSERT_TRUE(devicePort.has_value()) << "Device port for remote submix device not found";
+ ASSERT_NO_FATAL_FAILURE(SetUp(module, moduleConfig, *devicePort));
}
- void sendBurstCommands() {
- const StreamContext* context = mStream->getContext();
- StreamLogicDefaultDriver driver(makeBurstCommands(true), context->getFrameSizeBytes());
- typename IOTraits<Stream>::Worker worker(*context, &driver, mStream->getEventReceiver());
- LOG(DEBUG) << __func__ << ": starting worker...";
- ASSERT_TRUE(worker.start());
- LOG(DEBUG) << __func__ << ": joining worker...";
- worker.join();
- EXPECT_FALSE(worker.hasError()) << worker.getError();
- EXPECT_EQ("", driver.getUnexpectedStateTransition());
- if (IOTraits<Stream>::is_input) {
- EXPECT_TRUE(driver.hasObservablePositionIncrease());
- }
- EXPECT_FALSE(driver.hasRetrogradeObservablePosition());
+ void SendBurstCommandsStartWorker() {
+ const StreamContext* context = mStream->getStreamContext();
+ mWorkerDriver = std::make_unique<StreamLogicDefaultDriver>(makeBurstCommands(true),
+ context->getFrameSizeBytes());
+ mWorker = std::make_unique<typename IOTraits<Stream>::Worker>(
+ *context, mWorkerDriver.get(), mStream->getStreamEventReceiver());
+ LOG(DEBUG) << __func__ << ": starting " << IOTraits<Stream>::directionStr << " worker...";
+ ASSERT_TRUE(mWorker->start());
}
- bool skipTest() const { return mSkipTest; }
+
+ void SendBurstCommandsJoinWorker() {
+ // Must call 'prepareToClose' before attempting to join because the stream may be
+ // stuck due to absence of activity from the other side of the remote submix pipe.
+ std::shared_ptr<IStreamCommon> common;
+ ASSERT_IS_OK(mStream->getStream()->getStreamCommon(&common));
+ ASSERT_IS_OK(common->prepareToClose());
+ LOG(DEBUG) << __func__ << ": joining " << IOTraits<Stream>::directionStr << " worker...";
+ mWorker->join();
+ EXPECT_FALSE(mWorker->hasError()) << mWorker->getError();
+ EXPECT_EQ("", mWorkerDriver->getUnexpectedStateTransition());
+ if (IOTraits<Stream>::is_input) {
+ EXPECT_TRUE(mWorkerDriver->hasObservablePositionIncrease());
+ }
+ EXPECT_FALSE(mWorkerDriver->hasRetrogradeObservablePosition());
+ mWorker.reset();
+ mWorkerDriver.reset();
+ }
+
+ void SendBurstCommands() {
+ ASSERT_NO_FATAL_FAILURE(SendBurstCommandsStartWorker());
+ ASSERT_NO_FATAL_FAILURE(SendBurstCommandsJoinWorker());
+ }
+
+ std::optional<AudioDeviceAddress> getAudioDeviceAddress() const { return mAddress; }
+ std::string skipTestReason() const { return mStream->skipTestReason(); }
private:
- /* Connect remote submix external device */
- void SetUpPortConnection(IModule* module, ModuleConfig* moduleConfig) {
- auto port = getRemoteSubmixAudioPort(moduleConfig, mAddress);
- ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
- mConnectedPort = std::make_unique<WithDevicePortConnectedState>(port.value());
- ASSERT_NO_FATAL_FAILURE(mConnectedPort->SetUp(module, moduleConfig));
- }
- /* Get mix port config for stream and setup patch for it. */
- void SetupPatch(const AudioPort& connectedPort) {
- const auto portConfig =
- mModuleConfig->getSingleConfigForMixPort(IOTraits<Stream>::is_input);
- if (!portConfig.has_value()) {
- LOG(DEBUG) << __func__ << ": portConfig not found";
- mSkipTest = true;
- return;
- }
- auto devicePortConfig = mModuleConfig->getSingleConfigForDevicePort(connectedPort);
- mPatch = std::make_unique<WithAudioPatch>(IOTraits<Stream>::is_input, portConfig.value(),
- devicePortConfig);
- ASSERT_NO_FATAL_FAILURE(mPatch->SetUp(mModule));
+ void SetUp(IModule* module, ModuleConfig* moduleConfig, const AudioPort& devicePort) {
+ mStream = std::make_unique<StreamFixture<Stream>>();
+ ASSERT_NO_FATAL_FAILURE(
+ mStream->SetUpStreamForDevicePort(module, moduleConfig, devicePort));
+ mAddress = mStream->getDevice().address;
}
- bool mSkipTest = false;
- IModule* mModule = nullptr;
- ModuleConfig* mModuleConfig = nullptr;
std::optional<AudioDeviceAddress> mAddress;
- std::unique_ptr<WithDevicePortConnectedState> mConnectedPort;
- std::unique_ptr<WithAudioPatch> mPatch;
- std::unique_ptr<WithStream<Stream>> mStream;
+ std::unique_ptr<StreamFixture<Stream>> mStream;
+ std::unique_ptr<StreamLogicDefaultDriver> mWorkerDriver;
+ std::unique_ptr<typename IOTraits<Stream>::Worker> mWorker;
};
class AudioModuleRemoteSubmix : public AudioCoreModule {
public:
void SetUp() override {
- ASSERT_NO_FATAL_FAILURE(AudioCoreModule::SetUp());
+ // Turn off "debug" which enables connections simulation. Since devices of the remote
+ // submix module are virtual, there is no need for simulation.
+ ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam(), false /*setUpDebug*/));
ASSERT_NO_FATAL_FAILURE(SetUpModuleConfig());
}
-
- void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); }
};
TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenNoInput) {
- // open output stream
WithRemoteSubmix<IStreamOut> streamOut;
ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
- if (streamOut.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
- // write something to stream
- ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+ // Note: here and in other tests any issue with connection attempts is considered as a problem.
+ ASSERT_EQ("", streamOut.skipTestReason());
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
}
TEST_P(AudioModuleRemoteSubmix, OutputDoesNotBlockWhenInputStuck) {
- // open output stream
WithRemoteSubmix<IStreamOut> streamOut;
ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
- if (streamOut.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ ASSERT_EQ("", streamOut.skipTestReason());
auto address = streamOut.getAudioDeviceAddress();
ASSERT_TRUE(address.has_value());
- // open input stream
WithRemoteSubmix<IStreamIn> streamIn(address.value());
ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
- if (streamIn.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ ASSERT_EQ("", streamIn.skipTestReason());
- // write something to stream
- ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommands());
}
TEST_P(AudioModuleRemoteSubmix, OutputAndInput) {
- // open output stream
WithRemoteSubmix<IStreamOut> streamOut;
ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
- if (streamOut.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ ASSERT_EQ("", streamOut.skipTestReason());
auto address = streamOut.getAudioDeviceAddress();
ASSERT_TRUE(address.has_value());
- // open input stream
WithRemoteSubmix<IStreamIn> streamIn(address.value());
ASSERT_NO_FATAL_FAILURE(streamIn.SetUp(module.get(), moduleConfig.get()));
- if (streamIn.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ ASSERT_EQ("", streamIn.skipTestReason());
- // write something to stream
- ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
- // read from input stream
- ASSERT_NO_FATAL_FAILURE(streamIn.sendBurstCommands());
+ // Start writing into the output stream.
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
+ // Simultaneously, read from the input stream.
+ ASSERT_NO_FATAL_FAILURE(streamIn.SendBurstCommands());
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
}
TEST_P(AudioModuleRemoteSubmix, OpenInputMultipleTimes) {
- // open output stream
WithRemoteSubmix<IStreamOut> streamOut;
ASSERT_NO_FATAL_FAILURE(streamOut.SetUp(module.get(), moduleConfig.get()));
- if (streamOut.skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ ASSERT_EQ("", streamOut.skipTestReason());
auto address = streamOut.getAudioDeviceAddress();
ASSERT_TRUE(address.has_value());
- // connect remote submix input device port
- auto port = WithRemoteSubmix<IStreamIn>::getRemoteSubmixAudioPort(moduleConfig.get(),
- address.value());
- ASSERT_TRUE(port.has_value()) << "Device AudioPort for remote submix not found";
- WithDevicePortConnectedState connectedInputPort(port.value());
- ASSERT_NO_FATAL_FAILURE(connectedInputPort.SetUp(module.get(), moduleConfig.get()));
-
- // open input streams
- const int streamInCount = 3;
+ const size_t streamInCount = 3;
std::vector<std::unique_ptr<WithRemoteSubmix<IStreamIn>>> streamIns(streamInCount);
- for (int i = 0; i < streamInCount; i++) {
- streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>();
- ASSERT_NO_FATAL_FAILURE(
- streamIns[i]->SetUp(module.get(), moduleConfig.get(), connectedInputPort.get()));
- if (streamIns[i]->skipTest()) {
- GTEST_SKIP() << "No mix port for attached devices";
- }
+ for (size_t i = 0; i < streamInCount; i++) {
+ streamIns[i] = std::make_unique<WithRemoteSubmix<IStreamIn>>(address.value());
+ ASSERT_NO_FATAL_FAILURE(streamIns[i]->SetUp(module.get(), moduleConfig.get()));
+ ASSERT_EQ("", streamIns[i]->skipTestReason());
}
- // write something to output stream
- ASSERT_NO_FATAL_FAILURE(streamOut.sendBurstCommands());
-
- // read from input streams
- for (int i = 0; i < streamInCount; i++) {
- ASSERT_NO_FATAL_FAILURE(streamIns[i]->sendBurstCommands());
+ // Start writing into the output stream.
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsStartWorker());
+ // Simultaneously, read from input streams.
+ for (size_t i = 0; i < streamInCount; i++) {
+ ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsStartWorker());
+ }
+ for (size_t i = 0; i < streamInCount; i++) {
+ ASSERT_NO_FATAL_FAILURE(streamIns[i]->SendBurstCommandsJoinWorker());
+ }
+ ASSERT_NO_FATAL_FAILURE(streamOut.SendBurstCommandsJoinWorker());
+ // Clean up input streams in the reverse order because the device connection is owned
+ // by the first one.
+ for (size_t i = streamInCount; i != 0; --i) {
+ streamIns[i - 1].reset();
}
}
@@ -4444,21 +4729,6 @@
::testing::ValuesIn(getRemoteSubmixModuleInstance()));
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioModuleRemoteSubmix);
-class TestExecutionTracer : public ::testing::EmptyTestEventListener {
- public:
- void OnTestStart(const ::testing::TestInfo& test_info) override {
- TraceTestState("Started", test_info);
- }
- void OnTestEnd(const ::testing::TestInfo& test_info) override {
- TraceTestState("Completed", test_info);
- }
-
- private:
- static void TraceTestState(const std::string& state, const ::testing::TestInfo& test_info) {
- LOG(INFO) << state << " " << test_info.test_suite_name() << "::" << test_info.name();
- }
-};
-
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
diff --git a/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
index 225640e..523f20d 100644
--- a/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectFactoryTargetTest.cpp
@@ -52,6 +52,7 @@
using aidl::android::media::audio::common::AudioSource;
using aidl::android::media::audio::common::AudioStreamType;
using aidl::android::media::audio::common::AudioUuid;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/// Effect factory testing.
class EffectFactoryTest : public testing::TestWithParam<std::string> {
@@ -303,6 +304,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
index 1876756..ca1cea9 100644
--- a/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioEffectTargetTest.cpp
@@ -51,6 +51,7 @@
using aidl::android::media::audio::common::AudioDeviceType;
using aidl::android::media::audio::common::AudioMode;
using aidl::android::media::audio::common::AudioSource;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
enum ParamName { PARAM_INSTANCE_NAME };
using EffectTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>>;
@@ -907,6 +908,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
index afddb84..2d9a233 100644
--- a/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalBassBoostTargetTest.cpp
@@ -32,7 +32,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Range;
-
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
* VtsAudioEffectTargetTest.
@@ -155,6 +155,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalDownmixTargetTest.cpp b/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
index 7a2f31b..c01a9a2 100644
--- a/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalDownmixTargetTest.cpp
@@ -28,7 +28,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
-
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
* VtsAudioEffectTargetTest.
@@ -136,6 +136,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
index 5509c76..2650f49 100644
--- a/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
+++ b/audio/aidl/vts/VtsHalDynamicsProcessingTest.cpp
@@ -36,6 +36,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -996,6 +997,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp b/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
index f2ef185..474b361 100644
--- a/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalEnvironmentalReverbTargetTest.cpp
@@ -28,6 +28,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -536,6 +537,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp b/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
index 37e7c0a..09396d1 100644
--- a/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalEqualizerTargetTest.cpp
@@ -46,6 +46,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific effect (equalizer) parameter checking, general IEffect interfaces
@@ -220,6 +221,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
index b33234b..5a32398 100644
--- a/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalHapticGeneratorTargetTest.cpp
@@ -33,6 +33,7 @@
using aidl::android::hardware::audio::effect::IEffect;
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -431,6 +432,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp b/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
index cbb80a9..925f9ec 100644
--- a/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalLoudnessEnhancerTargetTest.cpp
@@ -30,28 +30,21 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::LoudnessEnhancer;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
-/**
- * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
- * VtsAudioEffectTargetTest.
- */
-enum ParamName { PARAM_INSTANCE_NAME, PARAM_GAIN_MB };
-using LoudnessEnhancerParamTestParam =
- std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int>;
+static constexpr float kMaxAudioSample = 1;
+static constexpr int kZeroGain = 0;
+static constexpr int kMaxGain = std::numeric_limits<int>::max();
+static constexpr int kMinGain = std::numeric_limits<int>::min();
+static constexpr float kAbsError = 0.0001;
// Every int 32 bit value is a valid gain, so testing the corner cases and one regular value.
// TODO : Update the test values once range/capability is updated by implementation.
-const std::vector<int> kGainMbValues = {std::numeric_limits<int>::min(), 100,
- std::numeric_limits<int>::max()};
+static const std::vector<int> kGainMbValues = {kMinGain, -100, -50, kZeroGain, 50, 100, kMaxGain};
-class LoudnessEnhancerParamTest : public ::testing::TestWithParam<LoudnessEnhancerParamTestParam>,
- public EffectHelper {
+class LoudnessEnhancerEffectHelper : public EffectHelper {
public:
- LoudnessEnhancerParamTest() : mParamGainMb(std::get<PARAM_GAIN_MB>(GetParam())) {
- std::tie(mFactory, mDescriptor) = std::get<PARAM_INSTANCE_NAME>(GetParam());
- }
-
- void SetUp() override {
+ void SetUpLoudnessEnhancer() {
ASSERT_NE(nullptr, mFactory);
ASSERT_NO_FATAL_FAILURE(create(mFactory, mEffect, mDescriptor));
@@ -59,13 +52,14 @@
Parameter::Common common = EffectHelper::createParamCommon(
0 /* session */, 1 /* ioHandle */, 44100 /* iSampleRate */, 44100 /* oSampleRate */,
kInputFrameCount /* iFrameCount */, kOutputFrameCount /* oFrameCount */);
- IEffect::OpenEffectReturn ret;
- ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &ret, EX_NONE));
+ ASSERT_NO_FATAL_FAILURE(open(mEffect, common, specific, &mOpenEffectReturn, EX_NONE));
ASSERT_NE(nullptr, mEffect);
}
- void TearDown() override {
+
+ void TearDownLoudnessEnhancer() {
ASSERT_NO_FATAL_FAILURE(close(mEffect));
ASSERT_NO_FATAL_FAILURE(destroy(mFactory, mEffect));
+ mOpenEffectReturn = IEffect::OpenEffectReturn{};
}
Parameter::Specific getDefaultParamSpecific() {
@@ -75,52 +69,230 @@
return specific;
}
- static const long kInputFrameCount = 0x100, kOutputFrameCount = 0x100;
- std::shared_ptr<IFactory> mFactory;
- std::shared_ptr<IEffect> mEffect;
- Descriptor mDescriptor;
- int mParamGainMb = 0;
+ Parameter createLoudnessParam(int gainMb) {
+ LoudnessEnhancer le;
+ le.set<LoudnessEnhancer::gainMb>(gainMb);
+ Parameter param;
+ Parameter::Specific specific;
+ specific.set<Parameter::Specific::loudnessEnhancer>(le);
+ param.set<Parameter::specific>(specific);
+ return param;
+ }
- void SetAndGetParameters() {
- for (auto& it : mTags) {
- auto& tag = it.first;
- auto& le = it.second;
-
- // set parameter
- Parameter expectParam;
- Parameter::Specific specific;
- specific.set<Parameter::Specific::loudnessEnhancer>(le);
- expectParam.set<Parameter::specific>(specific);
- // All values are valid, set parameter should succeed
- EXPECT_STATUS(EX_NONE, mEffect->setParameter(expectParam)) << expectParam.toString();
-
- // get parameter
- Parameter getParam;
- Parameter::Id id;
- LoudnessEnhancer::Id leId;
- leId.set<LoudnessEnhancer::Id::commonTag>(tag);
- id.set<Parameter::Id::loudnessEnhancerTag>(leId);
- EXPECT_STATUS(EX_NONE, mEffect->getParameter(id, &getParam));
-
- EXPECT_EQ(expectParam, getParam) << "\nexpect:" << expectParam.toString()
- << "\ngetParam:" << getParam.toString();
+ binder_exception_t isGainValid(int gainMb) {
+ LoudnessEnhancer le;
+ le.set<LoudnessEnhancer::gainMb>(gainMb);
+ if (isParameterValid<LoudnessEnhancer, Range::loudnessEnhancer>(le, mDescriptor)) {
+ return EX_NONE;
+ } else {
+ return EX_ILLEGAL_ARGUMENT;
}
}
- void addGainMbParam(int gainMb) {
- LoudnessEnhancer le;
- le.set<LoudnessEnhancer::gainMb>(gainMb);
- mTags.push_back({LoudnessEnhancer::gainMb, le});
+ void setParameters(int gain, binder_exception_t expected) {
+ // set parameter
+ auto param = createLoudnessParam(gain);
+ EXPECT_STATUS(expected, mEffect->setParameter(param)) << param.toString();
}
- private:
- std::vector<std::pair<LoudnessEnhancer::Tag, LoudnessEnhancer>> mTags;
- void CleanUp() { mTags.clear(); }
+ void validateParameters(int gain) {
+ // get parameter
+ LoudnessEnhancer::Id leId;
+ Parameter getParam;
+ Parameter::Id id;
+
+ LoudnessEnhancer::Tag tag(LoudnessEnhancer::gainMb);
+ leId.set<LoudnessEnhancer::Id::commonTag>(tag);
+ id.set<Parameter::Id::loudnessEnhancerTag>(leId);
+ EXPECT_STATUS(EX_NONE, mEffect->getParameter(id, &getParam));
+ auto expectedParam = createLoudnessParam(gain);
+ EXPECT_EQ(expectedParam, getParam) << "\nexpectedParam:" << expectedParam.toString()
+ << "\ngetParam:" << getParam.toString();
+ }
+
+ static const long kInputFrameCount = 0x100, kOutputFrameCount = 0x100;
+ IEffect::OpenEffectReturn mOpenEffectReturn;
+ std::shared_ptr<IFactory> mFactory;
+ std::shared_ptr<IEffect> mEffect;
+ Descriptor mDescriptor;
+};
+
+/**
+ * Here we focus on specific parameter checking, general IEffect interfaces testing performed in
+ * VtsAudioEffectTargetTest.
+ */
+enum ParamName { PARAM_INSTANCE_NAME, PARAM_GAIN_MB };
+using LoudnessEnhancerParamTestParam =
+ std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>, int>;
+
+class LoudnessEnhancerParamTest : public ::testing::TestWithParam<LoudnessEnhancerParamTestParam>,
+ public LoudnessEnhancerEffectHelper {
+ public:
+ LoudnessEnhancerParamTest() : mParamGainMb(std::get<PARAM_GAIN_MB>(GetParam())) {
+ std::tie(mFactory, mDescriptor) = std::get<PARAM_INSTANCE_NAME>(GetParam());
+ }
+
+ void SetUp() override { SetUpLoudnessEnhancer(); }
+ void TearDown() override { TearDownLoudnessEnhancer(); }
+ int mParamGainMb = 0;
};
TEST_P(LoudnessEnhancerParamTest, SetAndGetGainMb) {
- EXPECT_NO_FATAL_FAILURE(addGainMbParam(mParamGainMb));
- SetAndGetParameters();
+ binder_exception_t expected = isGainValid(mParamGainMb);
+ setParameters(mParamGainMb, expected);
+ if (expected == EX_NONE) {
+ validateParameters(mParamGainMb);
+ }
+}
+
+using LoudnessEnhancerDataTestParam = std::pair<std::shared_ptr<IFactory>, Descriptor>;
+
+class LoudnessEnhancerDataTest : public ::testing::TestWithParam<LoudnessEnhancerDataTestParam>,
+ public LoudnessEnhancerEffectHelper {
+ public:
+ LoudnessEnhancerDataTest() {
+ std::tie(mFactory, mDescriptor) = GetParam();
+ generateInputBuffer();
+ mOutputBuffer.resize(kBufferSize);
+ }
+
+ void SetUp() override {
+ SetUpLoudnessEnhancer();
+
+ // Creating AidlMessageQueues
+ mStatusMQ = std::make_unique<EffectHelper::StatusMQ>(mOpenEffectReturn.statusMQ);
+ mInputMQ = std::make_unique<EffectHelper::DataMQ>(mOpenEffectReturn.inputDataMQ);
+ mOutputMQ = std::make_unique<EffectHelper::DataMQ>(mOpenEffectReturn.outputDataMQ);
+ }
+
+ void TearDown() override { TearDownLoudnessEnhancer(); }
+
+ // Fill inputBuffer with random values between -kMaxAudioSample to kMaxAudioSample
+ void generateInputBuffer() {
+ for (size_t i = 0; i < kBufferSize; i++) {
+ mInputBuffer.push_back(((static_cast<float>(std::rand()) / RAND_MAX) * 2 - 1) *
+ kMaxAudioSample);
+ }
+ }
+
+ // Add gains to the mInputBuffer and store processed output to mOutputBuffer
+ void processAndWriteToOutput() {
+ // Check AidlMessageQueues are not null
+ ASSERT_TRUE(mStatusMQ->isValid());
+ ASSERT_TRUE(mInputMQ->isValid());
+ ASSERT_TRUE(mOutputMQ->isValid());
+
+ // Enabling the process
+ ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::START));
+ ASSERT_NO_FATAL_FAILURE(expectState(mEffect, State::PROCESSING));
+
+ // Write from buffer to message queues and calling process
+ EXPECT_NO_FATAL_FAILURE(EffectHelper::writeToFmq(mStatusMQ, mInputMQ, mInputBuffer));
+
+ // Read the updated message queues into buffer
+ EXPECT_NO_FATAL_FAILURE(EffectHelper::readFromFmq(mStatusMQ, 1, mOutputMQ,
+ mOutputBuffer.size(), mOutputBuffer));
+
+ // Disable the process
+ ASSERT_NO_FATAL_FAILURE(command(mEffect, CommandId::STOP));
+ }
+
+ void assertGreaterGain(const std::vector<float>& first, const std::vector<float>& second) {
+ for (size_t i = 0; i < first.size(); i++) {
+ if (first[i] != 0) {
+ ASSERT_GT(abs(first[i]), abs(second[i]));
+
+ } else {
+ ASSERT_EQ(first[i], second[i]);
+ }
+ }
+ }
+
+ void assertSequentialGains(const std::vector<int>& gainValues, bool isIncreasing) {
+ std::vector<float> baseOutput(kBufferSize);
+
+ // Process a reference output buffer with 0 gain which gives compressed input values
+ binder_exception_t expected;
+ expected = isGainValid(kZeroGain);
+ ASSERT_EQ(expected, EX_NONE);
+ setParameters(kZeroGain, expected);
+ ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+ baseOutput = mOutputBuffer;
+
+ // Compare the outputs for increasing gain
+ for (int gain : gainValues) {
+ // Setting the parameters
+ binder_exception_t expected = isGainValid(gain);
+ if (expected != EX_NONE) {
+ GTEST_SKIP() << "Gains not supported.";
+ }
+ setParameters(gain, expected);
+ ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+ // Compare the mOutputBuffer values with baseOutput and update it
+ if (isIncreasing) {
+ ASSERT_NO_FATAL_FAILURE(assertGreaterGain(mOutputBuffer, baseOutput));
+ } else {
+ ASSERT_NO_FATAL_FAILURE(assertGreaterGain(baseOutput, mOutputBuffer));
+ }
+
+ baseOutput = mOutputBuffer;
+ }
+ }
+
+ std::unique_ptr<StatusMQ> mStatusMQ;
+ std::unique_ptr<DataMQ> mInputMQ;
+ std::unique_ptr<DataMQ> mOutputMQ;
+
+ std::vector<float> mInputBuffer;
+ std::vector<float> mOutputBuffer;
+ static constexpr float kBufferSize = 128;
+};
+
+TEST_P(LoudnessEnhancerDataTest, IncreasingGains) {
+ static const std::vector<int> kIncreasingGains = {50, 100};
+
+ assertSequentialGains(kIncreasingGains, true /*isIncreasing*/);
+}
+
+TEST_P(LoudnessEnhancerDataTest, DecreasingGains) {
+ static const std::vector<int> kDecreasingGains = {-50, -100};
+
+ assertSequentialGains(kDecreasingGains, false /*isIncreasing*/);
+}
+
+TEST_P(LoudnessEnhancerDataTest, MinimumGain) {
+ // Setting the parameters
+ binder_exception_t expected = isGainValid(kMinGain);
+ if (expected != EX_NONE) {
+ GTEST_SKIP() << "Minimum integer value not supported";
+ }
+ setParameters(kMinGain, expected);
+ ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+ // Validate that mOutputBuffer has 0 values for INT_MIN gain
+ for (size_t i = 0; i < mOutputBuffer.size(); i++) {
+ ASSERT_FLOAT_EQ(mOutputBuffer[i], 0);
+ }
+}
+
+TEST_P(LoudnessEnhancerDataTest, MaximumGain) {
+ // Setting the parameters
+ binder_exception_t expected = isGainValid(kMaxGain);
+ if (expected != EX_NONE) {
+ GTEST_SKIP() << "Maximum integer value not supported";
+ }
+ setParameters(kMaxGain, expected);
+ ASSERT_NO_FATAL_FAILURE(processAndWriteToOutput());
+
+ // Validate that mOutputBuffer reaches to kMaxAudioSample for INT_MAX gain
+ for (size_t i = 0; i < mOutputBuffer.size(); i++) {
+ if (mInputBuffer[i] != 0) {
+ EXPECT_NEAR(kMaxAudioSample, abs(mOutputBuffer[i]), kAbsError);
+ } else {
+ ASSERT_EQ(mOutputBuffer[i], mInputBuffer[i]);
+ }
+ }
}
INSTANTIATE_TEST_SUITE_P(
@@ -139,8 +311,23 @@
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LoudnessEnhancerParamTest);
+INSTANTIATE_TEST_SUITE_P(
+ LoudnessEnhancerTest, LoudnessEnhancerDataTest,
+ testing::ValuesIn(EffectFactoryHelper::getAllEffectDescriptors(
+ IFactory::descriptor, getEffectTypeUuidLoudnessEnhancer())),
+ [](const testing::TestParamInfo<LoudnessEnhancerDataTest::ParamType>& info) {
+ auto descriptor = info.param;
+ std::string name = getPrefix(descriptor.second);
+ std::replace_if(
+ name.begin(), name.end(), [](const char c) { return !std::isalnum(c); }, '_');
+ return name;
+ });
+
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(LoudnessEnhancerDataTest);
+
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalNSTargetTest.cpp b/audio/aidl/vts/VtsHalNSTargetTest.cpp
index 624d5d2..12d56b0 100644
--- a/audio/aidl/vts/VtsHalNSTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalNSTargetTest.cpp
@@ -32,6 +32,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::NoiseSuppression;
using aidl::android::hardware::audio::effect::Parameter;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
enum ParamName { PARAM_INSTANCE_NAME, PARAM_LEVEL, PARAM_TYPE };
using NSParamTestParam = std::tuple<std::pair<std::shared_ptr<IFactory>, Descriptor>,
@@ -171,6 +172,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp b/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
index 3056c6c..57eda09 100644
--- a/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalPresetReverbTargetTest.cpp
@@ -29,6 +29,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::PresetReverb;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -147,6 +148,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp b/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
index 07a9fa4..3e39d3a 100644
--- a/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVirtualizerTargetTest.cpp
@@ -28,6 +28,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Virtualizer;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -151,6 +152,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp b/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
index 903ba69..1b8352b 100644
--- a/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVisualizerTargetTest.cpp
@@ -31,6 +31,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Visualizer;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -207,6 +208,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
index 0b5b9fc..257100b 100644
--- a/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalVolumeTargetTest.cpp
@@ -28,6 +28,7 @@
using aidl::android::hardware::audio::effect::IFactory;
using aidl::android::hardware::audio::effect::Parameter;
using aidl::android::hardware::audio::effect::Volume;
+using android::hardware::audio::common::testing::detail::TestExecutionTracer;
/**
* Here we focus on specific parameter checking, general IEffect interfaces testing performed in
@@ -159,6 +160,7 @@
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
+ ::testing::UnitTest::GetInstance()->listeners().Append(new TestExecutionTracer());
ABinderProcess_setThreadPoolMaxThreadCount(1);
ABinderProcess_startThreadPool();
return RUN_ALL_TESTS();
diff --git a/audio/common/all-versions/test/utility/src/ValidateXml.cpp b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
index 4d6f003..b7e685b 100644
--- a/audio/common/all-versions/test/utility/src/ValidateXml.cpp
+++ b/audio/common/all-versions/test/utility/src/ValidateXml.cpp
@@ -19,6 +19,7 @@
#include <numeric>
+#include <libxml/parser.h>
#define LIBXML_SCHEMAS_ENABLED
#include <libxml/xmlschemastypes.h>
#define LIBXML_XINCLUDE_ENABLED
diff --git a/audio/common/all-versions/test/utility/tests/utility_tests.cpp b/audio/common/all-versions/test/utility/tests/utility_tests.cpp
index c523066..c2bcfbc 100644
--- a/audio/common/all-versions/test/utility/tests/utility_tests.cpp
+++ b/audio/common/all-versions/test/utility/tests/utility_tests.cpp
@@ -70,6 +70,7 @@
std::string substitute(const char* fmt, const char* param) {
std::string buffer(static_cast<size_t>(strlen(fmt) + strlen(param)), '\0');
snprintf(buffer.data(), buffer.size(), fmt, param);
+ buffer.resize(strlen(buffer.c_str()));
return buffer;
}
diff --git a/automotive/evs/aidl/impl/default/Android.bp b/automotive/evs/aidl/impl/default/Android.bp
index 6b638a3..3d5b7c4 100644
--- a/automotive/evs/aidl/impl/default/Android.bp
+++ b/automotive/evs/aidl/impl/default/Android.bp
@@ -24,6 +24,9 @@
cc_defaults {
name: "android.hardware.automotive.evs-aidl-default-service-default",
defaults: ["EvsHalDefaults"],
+ header_libs: [
+ "libstagefright_headers",
+ ],
shared_libs: [
"android.hardware.graphics.bufferqueue@1.0",
"android.hardware.graphics.bufferqueue@2.0",
@@ -35,6 +38,7 @@
"libcamera_metadata",
"libhardware_legacy",
"libhidlbase",
+ "libmediandk",
"libnativewindow",
"libtinyxml2",
"libui",
diff --git a/automotive/evs/aidl/impl/default/include/EvsVideoEmulatedCamera.h b/automotive/evs/aidl/impl/default/include/EvsVideoEmulatedCamera.h
index 356a42a..a850d65 100644
--- a/automotive/evs/aidl/impl/default/include/EvsVideoEmulatedCamera.h
+++ b/automotive/evs/aidl/impl/default/include/EvsVideoEmulatedCamera.h
@@ -26,20 +26,27 @@
#include <aidl/android/hardware/automotive/evs/IEvsDisplay.h>
#include <aidl/android/hardware/automotive/evs/ParameterRange.h>
#include <aidl/android/hardware/automotive/evs/Stream.h>
+#include <media/NdkMediaExtractor.h>
+
+#include <ui/GraphicBuffer.h>
#include <cstdint>
#include <memory>
+#include <thread>
#include <unordered_map>
#include <vector>
namespace aidl::android::hardware::automotive::evs::implementation {
class EvsVideoEmulatedCamera : public EvsCamera {
+ private:
+ using Base = EvsCamera;
+
public:
EvsVideoEmulatedCamera(Sigil sigil, const char* deviceName,
std::unique_ptr<ConfigManager::CameraInfo>& camInfo);
- ~EvsVideoEmulatedCamera() override;
+ ~EvsVideoEmulatedCamera() override = default;
// Methods from ::android::hardware::automotive::evs::IEvsCamera follow.
ndk::ScopedAStatus forcePrimaryClient(
@@ -60,6 +67,9 @@
ndk::ScopedAStatus setPrimaryClient() override;
ndk::ScopedAStatus unsetPrimaryClient() override;
+ // Methods from EvsCameraBase follow.
+ void shutdown() override;
+
const evs::CameraDesc& getDesc() { return mDescription; }
static std::shared_ptr<EvsVideoEmulatedCamera> Create(const char* deviceName);
@@ -81,8 +91,18 @@
int32_t value;
};
+ bool initialize();
+
+ void generateFrames();
+
+ void renderOneFrame();
+
void initializeParameters();
+ void onCodecInputAvailable(const int32_t index);
+
+ void onCodecOutputAvailable(const int32_t index, const AMediaCodecBufferInfo& info);
+
::android::status_t allocateOneFrame(buffer_handle_t* handle) override;
bool startVideoStreamImpl_locked(const std::shared_ptr<evs::IEvsCameraStream>& receiver,
@@ -92,9 +112,42 @@
bool stopVideoStreamImpl_locked(ndk::ScopedAStatus& status,
std::unique_lock<std::mutex>& lck) override;
+ bool postVideoStreamStop_locked(ndk::ScopedAStatus& status,
+ std::unique_lock<std::mutex>& lck) override;
+
// The properties of this camera.
CameraDesc mDescription = {};
+ std::thread mCaptureThread;
+
+ // The callback used to deliver each frame
+ std::shared_ptr<evs::IEvsCameraStream> mStream;
+
+ std::string mVideoFileName;
+ // Media decoder resources - Owned by mDecoderThead when thread is running.
+ int mVideoFd = 0;
+
+ struct AMediaExtractorDeleter {
+ void operator()(AMediaExtractor* extractor) const { AMediaExtractor_delete(extractor); }
+ };
+ struct AMediaCodecDeleter {
+ void operator()(AMediaCodec* codec) const { AMediaCodec_delete(codec); }
+ };
+
+ std::unique_ptr<AMediaExtractor, AMediaExtractorDeleter> mVideoExtractor;
+ std::unique_ptr<AMediaCodec, AMediaCodecDeleter> mVideoCodec;
+
+ // Horizontal pixel count in the buffers
+ int32_t mWidth = 0;
+ // Vertical pixel count in the buffers
+ int32_t mHeight = 0;
+ // Values from android_pixel_format_t
+ uint32_t mFormat = 0;
+ // Values from from Gralloc.h
+ uint64_t mUsage = 0;
+ // Bytes per line in the buffers
+ uint32_t mStride = 0;
+
// Camera parameters.
std::unordered_map<CameraParam, std::shared_ptr<CameraParameterDesc>> mParams;
diff --git a/automotive/evs/aidl/impl/default/src/EvsVideoEmulatedCamera.cpp b/automotive/evs/aidl/impl/default/src/EvsVideoEmulatedCamera.cpp
index f198a64..8181e47 100644
--- a/automotive/evs/aidl/impl/default/src/EvsVideoEmulatedCamera.cpp
+++ b/automotive/evs/aidl/impl/default/src/EvsVideoEmulatedCamera.cpp
@@ -18,17 +18,35 @@
#include <aidl/android/hardware/automotive/evs/EvsResult.h>
+#include <aidlcommonsupport/NativeHandle.h>
#include <android-base/logging.h>
+#include <android-base/strings.h>
+#include <media/stagefright/MediaCodecConstants.h>
+#include <ui/GraphicBufferAllocator.h>
+#include <utils/SystemClock.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <chrono>
#include <cstddef>
#include <cstdint>
+#include <tuple>
+#include <utility>
namespace aidl::android::hardware::automotive::evs::implementation {
+namespace {
+struct FormatDeleter {
+ void operator()(AMediaFormat* format) const { AMediaFormat_delete(format); }
+};
+} // namespace
+
EvsVideoEmulatedCamera::EvsVideoEmulatedCamera(Sigil, const char* deviceName,
std::unique_ptr<ConfigManager::CameraInfo>& camInfo)
- : mCameraInfo(camInfo) {
- mDescription.id = deviceName;
+ : mVideoFileName(deviceName), mCameraInfo(camInfo) {
+ mDescription.id = mVideoFileName;
/* set camera metadata */
if (camInfo) {
@@ -40,6 +58,256 @@
initializeParameters();
}
+bool EvsVideoEmulatedCamera::initialize() {
+ // Open file.
+ mVideoFd = open(mVideoFileName.c_str(), 0, O_RDONLY);
+ if (mVideoFd < 0) {
+ PLOG(ERROR) << __func__ << ": Failed to open video file \"" << mVideoFileName << "\".";
+ return false;
+ }
+
+ // Initialize Media Extractor.
+ {
+ mVideoExtractor.reset(AMediaExtractor_new());
+ off64_t filesize = lseek64(mVideoFd, 0, SEEK_END);
+ lseek(mVideoFd, 0, SEEK_SET);
+ const media_status_t status =
+ AMediaExtractor_setDataSourceFd(mVideoExtractor.get(), mVideoFd, 0, filesize);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__
+ << ": Received error when initializing media extractor. Error code: "
+ << status << ".";
+ return false;
+ }
+ }
+
+ // Initialize Media Codec and file format.
+ std::unique_ptr<AMediaFormat, FormatDeleter> format;
+ const char* mime;
+ bool selected = false;
+ int numTracks = AMediaExtractor_getTrackCount(mVideoExtractor.get());
+ for (int i = 0; i < numTracks; i++) {
+ format.reset(AMediaExtractor_getTrackFormat(mVideoExtractor.get(), i));
+ if (!AMediaFormat_getString(format.get(), AMEDIAFORMAT_KEY_MIME, &mime)) {
+ LOG(ERROR) << __func__ << ": Error in fetching format string";
+ continue;
+ }
+ if (!::android::base::StartsWith(mime, "video/")) {
+ continue;
+ }
+ const media_status_t status = AMediaExtractor_selectTrack(mVideoExtractor.get(), i);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__
+ << ": Media extractor returned error to select track. Error Code: " << status
+ << ".";
+ return false;
+ }
+ selected = true;
+ break;
+ }
+ if (!selected) {
+ LOG(ERROR) << __func__ << ": No video track in video file \"" << mVideoFileName << "\".";
+ return false;
+ }
+
+ mVideoCodec.reset(AMediaCodec_createDecoderByType(mime));
+ if (!mVideoCodec) {
+ LOG(ERROR) << __func__ << ": Unable to create decoder.";
+ return false;
+ }
+
+ mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary test value
+ mUsage = GRALLOC_USAGE_HW_TEXTURE | GRALLOC_USAGE_HW_CAMERA_WRITE |
+ GRALLOC_USAGE_SW_READ_RARELY | GRALLOC_USAGE_SW_WRITE_RARELY;
+ mFormat = HAL_PIXEL_FORMAT_YCBCR_420_888;
+ AMediaFormat_setInt32(format.get(), AMEDIAFORMAT_KEY_COLOR_FORMAT, COLOR_FormatYUV420Flexible);
+ {
+ const media_status_t status =
+ AMediaCodec_configure(mVideoCodec.get(), format.get(), nullptr, nullptr, 0);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__
+ << ": Received error in configuring mCodec. Error code: " << status << ".";
+ return false;
+ }
+ }
+ format.reset(AMediaCodec_getOutputFormat(mVideoCodec.get()));
+ AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_WIDTH, &mWidth);
+ AMediaFormat_getInt32(format.get(), AMEDIAFORMAT_KEY_HEIGHT, &mHeight);
+ return true;
+}
+
+void EvsVideoEmulatedCamera::generateFrames() {
+ while (true) {
+ {
+ std::lock_guard lock(mMutex);
+ if (mStreamState != StreamState::RUNNING) {
+ return;
+ }
+ }
+ renderOneFrame();
+ }
+}
+
+void EvsVideoEmulatedCamera::onCodecInputAvailable(const int32_t index) {
+ const size_t sampleSize = AMediaExtractor_getSampleSize(mVideoExtractor.get());
+ const int64_t presentationTime = AMediaExtractor_getSampleTime(mVideoExtractor.get());
+ size_t bufferSize = 0;
+ uint8_t* const codecInputBuffer =
+ AMediaCodec_getInputBuffer(mVideoCodec.get(), index, &bufferSize);
+ if (sampleSize > bufferSize) {
+ LOG(ERROR) << __func__ << ": Buffer is not large enough.";
+ }
+ if (presentationTime < 0) {
+ AMediaCodec_queueInputBuffer(mVideoCodec.get(), index, /* offset = */ 0,
+ /* size = */ 0, presentationTime,
+ AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
+ LOG(INFO) << __func__ << ": Reaching the end of stream.";
+ return;
+ }
+ const size_t readSize =
+ AMediaExtractor_readSampleData(mVideoExtractor.get(), codecInputBuffer, sampleSize);
+ const media_status_t status = AMediaCodec_queueInputBuffer(
+ mVideoCodec.get(), index, /*offset = */ 0, readSize, presentationTime, /* flags = */ 0);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__
+ << ": Received error in queueing input buffer. Error code: " << status;
+ }
+}
+
+void EvsVideoEmulatedCamera::onCodecOutputAvailable(const int32_t index,
+ const AMediaCodecBufferInfo& info) {
+ using std::chrono::duration_cast;
+ using std::chrono::microseconds;
+ using std::chrono::nanoseconds;
+ using AidlPixelFormat = ::aidl::android::hardware::graphics::common::PixelFormat;
+ using ::aidl::android::hardware::graphics::common::BufferUsage;
+
+ size_t decodedOutSize = 0;
+ uint8_t* const codecOutputBuffer =
+ AMediaCodec_getOutputBuffer(mVideoCodec.get(), index, &decodedOutSize) + info.offset;
+
+ std::size_t renderBufferId = static_cast<std::size_t>(-1);
+ buffer_handle_t renderBufferHandle = nullptr;
+ {
+ std::lock_guard lock(mMutex);
+ if (mStreamState != StreamState::RUNNING) {
+ return;
+ }
+ std::tie(renderBufferId, renderBufferHandle) = useBuffer_unsafe();
+ }
+ if (!renderBufferHandle) {
+ LOG(ERROR) << __func__ << ": Camera failed to get an available render buffer.";
+ return;
+ }
+ std::vector<BufferDesc> renderBufferDescs;
+ renderBufferDescs.push_back({
+ .buffer =
+ {
+ .description =
+ {
+ .width = static_cast<int32_t>(mWidth),
+ .height = static_cast<int32_t>(mHeight),
+ .layers = 1,
+ .format = static_cast<AidlPixelFormat>(mFormat),
+ .usage = static_cast<BufferUsage>(mUsage),
+ .stride = static_cast<int32_t>(mStride),
+ },
+ .handle = ::android::dupToAidl(renderBufferHandle),
+ },
+ .bufferId = static_cast<int32_t>(renderBufferId),
+ .deviceId = mDescription.id,
+ .timestamp = duration_cast<microseconds>(nanoseconds(::android::elapsedRealtimeNano()))
+ .count(),
+ });
+
+ // Lock our output buffer for writing
+ uint8_t* pixels = nullptr;
+ int32_t bytesPerStride = 0;
+ auto& mapper = ::android::GraphicBufferMapper::get();
+ mapper.lock(renderBufferHandle, GRALLOC_USAGE_SW_WRITE_OFTEN | GRALLOC_USAGE_SW_READ_NEVER,
+ ::android::Rect(mWidth, mHeight), (void**)&pixels, nullptr, &bytesPerStride);
+
+ // If we failed to lock the pixel buffer, we're about to crash, but log it first
+ if (!pixels) {
+ LOG(ERROR) << __func__ << ": Camera failed to gain access to image buffer for writing";
+ return;
+ }
+
+ std::size_t ySize = mHeight * mStride;
+ std::size_t uvSize = ySize / 4;
+
+ std::memcpy(pixels, codecOutputBuffer, ySize);
+ pixels += ySize;
+
+ uint8_t* u_head = codecOutputBuffer + ySize;
+ uint8_t* v_head = u_head + uvSize;
+
+ for (size_t i = 0; i < uvSize; ++i) {
+ *(pixels++) = *(u_head++);
+ *(pixels++) = *(v_head++);
+ }
+
+ const auto status =
+ AMediaCodec_releaseOutputBuffer(mVideoCodec.get(), index, /* render = */ false);
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__
+ << ": Received error in releasing output buffer. Error code: " << status;
+ }
+
+ // Release our output buffer
+ mapper.unlock(renderBufferHandle);
+
+ // Issue the (asynchronous) callback to the client -- can't be holding the lock
+ if (mStream && mStream->deliverFrame(renderBufferDescs).isOk()) {
+ LOG(DEBUG) << __func__ << ": Delivered " << renderBufferHandle
+ << ", id = " << renderBufferId;
+ } else {
+ // This can happen if the client dies and is likely unrecoverable.
+ // To avoid consuming resources generating failing calls, we stop sending
+ // frames. Note, however, that the stream remains in the "STREAMING" state
+ // until cleaned up on the main thread.
+ LOG(ERROR) << __func__ << ": Frame delivery call failed in the transport layer.";
+ doneWithFrame(renderBufferDescs);
+ }
+}
+
+void EvsVideoEmulatedCamera::renderOneFrame() {
+ using std::chrono::duration_cast;
+ using std::chrono::microseconds;
+ using namespace std::chrono_literals;
+
+ // push to codec input
+ while (true) {
+ int codecInputBufferIdx =
+ AMediaCodec_dequeueInputBuffer(mVideoCodec.get(), /* timeoutUs = */ 0);
+ if (codecInputBufferIdx < 0) {
+ if (codecInputBufferIdx != AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ LOG(ERROR) << __func__
+ << ": Received error in AMediaCodec_dequeueInputBuffer. Error code: "
+ << codecInputBufferIdx;
+ }
+ break;
+ }
+ onCodecInputAvailable(codecInputBufferIdx);
+ AMediaExtractor_advance(mVideoExtractor.get());
+ }
+
+ // pop from codec output
+
+ AMediaCodecBufferInfo info;
+ int codecOutputputBufferIdx = AMediaCodec_dequeueOutputBuffer(
+ mVideoCodec.get(), &info, /* timeoutUs = */ duration_cast<microseconds>(1ms).count());
+ if (codecOutputputBufferIdx < 0) {
+ if (codecOutputputBufferIdx != AMEDIACODEC_INFO_TRY_AGAIN_LATER) {
+ LOG(ERROR) << __func__
+ << ": Received error in AMediaCodec_dequeueOutputBuffer. Error code: "
+ << codecOutputputBufferIdx;
+ }
+ return;
+ }
+ onCodecOutputAvailable(codecOutputputBufferIdx, info);
+}
+
void EvsVideoEmulatedCamera::initializeParameters() {
mParams.emplace(
CameraParam::BRIGHTNESS,
@@ -52,22 +320,54 @@
new CameraParameterDesc(/* min= */ 0, /* max= */ 255, /* step= */ 1, /* value= */ 255));
}
-::android::status_t EvsVideoEmulatedCamera::allocateOneFrame(buffer_handle_t* /* handle */) {
- LOG(FATAL) << __func__ << ": Not implemented yet.";
- return ::android::UNKNOWN_ERROR;
+::android::status_t EvsVideoEmulatedCamera::allocateOneFrame(buffer_handle_t* handle) {
+ static auto& alloc = ::android::GraphicBufferAllocator::get();
+ unsigned pixelsPerLine = 0;
+ const auto result = alloc.allocate(mWidth, mHeight, mFormat, 1, mUsage, handle, &pixelsPerLine,
+ 0, "EvsVideoEmulatedCamera");
+ if (mStride == 0) {
+ // Gralloc defines stride in terms of pixels per line
+ mStride = pixelsPerLine;
+ } else if (mStride != pixelsPerLine) {
+ LOG(ERROR) << "We did not expect to get buffers with different strides!";
+ }
+ return result;
}
bool EvsVideoEmulatedCamera::startVideoStreamImpl_locked(
- const std::shared_ptr<evs::IEvsCameraStream>& /* receiver */,
- ndk::ScopedAStatus& /* status */, std::unique_lock<std::mutex>& /* lck */) {
- LOG(FATAL) << __func__ << ": Not implemented yet.";
- return false;
+ const std::shared_ptr<evs::IEvsCameraStream>& receiver, ndk::ScopedAStatus& /* status */,
+ std::unique_lock<std::mutex>& /* lck */) {
+ mStream = receiver;
+
+ const media_status_t status = AMediaCodec_start(mVideoCodec.get());
+ if (status != AMEDIA_OK) {
+ LOG(ERROR) << __func__ << ": Received error in starting decoder. Error code: " << status
+ << ".";
+ return false;
+ }
+ mCaptureThread = std::thread([this]() { generateFrames(); });
+
+ return true;
}
bool EvsVideoEmulatedCamera::stopVideoStreamImpl_locked(ndk::ScopedAStatus& /* status */,
- std::unique_lock<std::mutex>& /* lck */) {
- LOG(FATAL) << __func__ << ": Not implemented yet.";
- return false;
+ std::unique_lock<std::mutex>& lck) {
+ const media_status_t status = AMediaCodec_stop(mVideoCodec.get());
+ lck.unlock();
+ if (mCaptureThread.joinable()) {
+ mCaptureThread.join();
+ }
+ lck.lock();
+ return status == AMEDIA_OK;
+}
+
+bool EvsVideoEmulatedCamera::postVideoStreamStop_locked(ndk::ScopedAStatus& status,
+ std::unique_lock<std::mutex>& lck) {
+ if (!Base::postVideoStreamStop_locked(status, lck)) {
+ return false;
+ }
+ mStream = nullptr;
+ return true;
}
ndk::ScopedAStatus EvsVideoEmulatedCamera::forcePrimaryClient(
@@ -189,10 +489,19 @@
LOG(ERROR) << "Failed to instantiate EvsVideoEmulatedCamera.";
return nullptr;
}
- c->mDescription.vendorFlags = 0xFFFFFFFF; // Arbitrary test value
+ if (!c->initialize()) {
+ LOG(ERROR) << "Failed to initialize EvsVideoEmulatedCamera.";
+ return nullptr;
+ }
return c;
}
-EvsVideoEmulatedCamera::~EvsVideoEmulatedCamera() {}
+void EvsVideoEmulatedCamera::shutdown() {
+ mVideoCodec.reset();
+ mVideoExtractor.reset();
+ close(mVideoFd);
+ mVideoFd = 0;
+ Base::shutdown();
+}
} // namespace aidl::android::hardware::automotive::evs::implementation
diff --git a/automotive/vehicle/TEST_MAPPING b/automotive/vehicle/TEST_MAPPING
index 02ad8bb..e1a90cb 100644
--- a/automotive/vehicle/TEST_MAPPING
+++ b/automotive/vehicle/TEST_MAPPING
@@ -51,6 +51,9 @@
"include-filter": "com.android.car.hal.fakevhal.FakeVehicleStubUnitTest"
}
]
+ },
+ {
+ "name": "VehicleHalProtoMessageConverterTest"
}
]
}
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
index 91e7c14..1b1696b 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/SubscribeOptions.aidl
@@ -37,4 +37,6 @@
int propId;
int[] areaIds;
float sampleRate;
+ float resolution = 0.0f;
+ boolean enableVariableUpdateRate;
}
diff --git a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
index 3322769..08d4ee4 100644
--- a/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
+++ b/automotive/vehicle/aidl/aidl_api/android.hardware.automotive.vehicle/current/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
@@ -43,4 +43,5 @@
float maxFloatValue;
@nullable long[] supportedEnumValues;
android.hardware.automotive.vehicle.VehiclePropertyAccess access = android.hardware.automotive.vehicle.VehiclePropertyAccess.NONE;
+ boolean supportVariableUpdateRate;
}
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
index e68f7e3..69f6190 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/SubscribeOptions.aidl
@@ -39,4 +39,29 @@
* This value indicates how many updates per second client wants to receive.
*/
float sampleRate;
+
+ /**
+ * Requested resolution of property updates.
+ *
+ * This value indicates the resolution at which continuous property updates should be sent to
+ * the platform. For example, if resolution is 0.01, the subscribed property value should be
+ * rounded to two decimal places. If the incoming resolution value is not an integer multiple of
+ * 10, VHAL should return a StatusCode::INVALID_ARG.
+ */
+ float resolution = 0.0f;
+
+ /**
+ * Whether to enable variable update rate.
+ *
+ * This only applies for continuous property. If variable update rate is
+ * enabled, for each given areaId, if VHAL supports variable update rate for
+ * the [propId, areaId], VHAL must ignore duplicate property value events
+ * and only sends changed value events (a.k.a treat continuous as an
+ * on-change property).
+ *
+ * If VHAL does not support variable update rate for the [propId, areaId],
+ * indicated by 'supportVariableUpdateRate' in 'VehicleAreaConfig', or if
+ * this property is not a continuous property, this option must be ignored.
+ */
+ boolean enableVariableUpdateRate;
}
diff --git a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
index 9fd9bda..08863b2 100644
--- a/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
+++ b/automotive/vehicle/aidl/android/hardware/automotive/vehicle/VehicleAreaConfig.aidl
@@ -70,4 +70,31 @@
* well.
*/
VehiclePropertyAccess access = VehiclePropertyAccess.NONE;
+
+ /**
+ * Whether variable update rate is supported.
+ *
+ * This applies for continuous property only.
+ *
+ * It is HIGHLY RECOMMENDED to support variable update rate for all non-heartbeat continuous
+ * properties for better performance.
+ *
+ * If variable update rate is supported and 'enableVariableUpdateRate' is true in subscribe
+ * options, VHAL must only sends property update event when the property's value changes
+ * (a.k.a treat continuous as an on-change property).
+ *
+ * E.g. if the client is subscribing at 5hz at time 0. If the property's value is 0 initially
+ * and becomes 1 after 1 second.
+
+ * If variable update rate is not enabled, VHAL clients will receive 5 property change events
+ * with value 0 and 5 events with value 1 after 2 seconds.
+ *
+ * If variable update rate is enabled, VHAL clients will receive 1 property change event
+ * with value 1 at time 1s. VHAL may/may not send a property event for the initial value (e.g.
+ * a property change event with value 0 at time 0s). VHAL client must not rely on the first
+ * property event, and must use getValues to fetch the initial value. In fact, car service is
+ * using getValues to fetch the initial value, convert it to a property event and deliver to
+ * car service clients.
+ */
+ boolean supportVariableUpdateRate;
}
diff --git a/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp b/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
index 39ce10e..82dc8a6 100644
--- a/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
+++ b/automotive/vehicle/aidl/impl/default_config/JsonConfigLoader/src/JsonConfigLoader.cpp
@@ -275,6 +275,16 @@
}
template <>
+Result<bool> JsonValueParser::convertValueToType<bool>(const std::string& fieldName,
+ const Json::Value& value) {
+ if (!value.isBool()) {
+ return Error() << "The value: " << value << " for field: " << fieldName
+ << " is not in correct type, expect bool";
+ }
+ return value.asBool();
+}
+
+template <>
Result<int32_t> JsonValueParser::convertValueToType<int32_t>(const std::string& fieldName,
const Json::Value& value) {
if (!value.isInt()) {
@@ -531,6 +541,12 @@
tryParseJsonValueToVariable(jsonAreaConfig, "maxFloatValue", /*optional=*/true,
&areaConfig.maxFloatValue, errors);
+ // By default we support variable update rate for all properties except it is explicitly
+ // disabled.
+ areaConfig.supportVariableUpdateRate = true;
+ tryParseJsonValueToVariable(jsonAreaConfig, "supportVariableUpdateRate", /*optional=*/true,
+ &areaConfig.supportVariableUpdateRate, errors);
+
std::vector<int64_t> supportedEnumValues;
tryParseJsonArrayToVariable(jsonAreaConfig, "supportedEnumValues", /*optional=*/true,
&supportedEnumValues, errors);
@@ -585,6 +601,16 @@
if (errors->size() != initialErrorCount) {
return std::nullopt;
}
+
+ // If there is no area config, by default we allow variable update rate, so we have to add
+ // a global area config.
+ if (configDecl.config.areaConfigs.size() == 0) {
+ VehicleAreaConfig areaConfig = {
+ .areaId = 0,
+ .supportVariableUpdateRate = true,
+ };
+ configDecl.config.areaConfigs.push_back(std::move(areaConfig));
+ }
return configDecl;
}
diff --git a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
index 6c8d59c..d3bb60c 100644
--- a/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
+++ b/automotive/vehicle/aidl/impl/default_config/config/DefaultProperties.json
@@ -3579,7 +3579,13 @@
"property": "VehicleProperty::WATCHDOG_TERMINATED_PROCESS"
},
{
- "property": "VehicleProperty::VHAL_HEARTBEAT"
+ "property": "VehicleProperty::VHAL_HEARTBEAT",
+ "areas": [
+ {
+ "areaId": 0,
+ "supportVariableUpdateRate": false
+ }
+ ]
},
{
"property": "VehicleProperty::CLUSTER_SWITCH_UI",
@@ -3641,6 +3647,12 @@
0,
16
],
+ "areas": [
+ {
+ "areaId": 0,
+ "supportVariableUpdateRate": false
+ }
+ ],
"comment": "configArray specifies it consists of int64[2] and byte[16]."
},
{
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
index 844bea5..26fdee6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/include/FakeVehicleHardware.h
@@ -91,9 +91,13 @@
void registerOnPropertySetErrorEvent(
std::unique_ptr<const PropertySetErrorCallback> callback) override;
- // Update the sample rate for the [propId, areaId] pair.
- aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
- int32_t propId, int32_t areaId, float sampleRate) override;
+ // Subscribe to a new [propId, areaId] or change the update rate.
+ aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+ aidl::android::hardware::automotive::vehicle::SubscribeOptions options) override;
+
+ // Unsubscribe to a [propId, areaId].
+ aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(int32_t propId,
+ int32_t areaId) override;
protected:
// mValuePool is also used in mServerSidePropStore.
@@ -154,6 +158,7 @@
mRecurrentActions GUARDED_BY(mLock);
std::unordered_map<PropIdAreaId, VehiclePropValuePool::RecyclableType, PropIdAreaIdHash>
mSavedProps GUARDED_BY(mLock);
+ std::unordered_set<PropIdAreaId, PropIdAreaIdHash> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
// PendingRequestHandler is thread-safe.
mutable PendingRequestHandler<GetValuesCallback,
aidl::android::hardware::automotive::vehicle::GetValueRequest>
@@ -176,7 +181,8 @@
void storePropInitialValue(const ConfigDeclaration& config);
// The callback that would be called when a vehicle property value change happens.
void onValueChangeCallback(
- const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value);
+ const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value)
+ EXCLUDES(mLock);
// Load the config files in format '*.json' from the directory and parse the config files
// into a map from property ID to ConfigDeclarations.
void loadPropConfigsFromDir(const std::string& dirPath,
@@ -216,6 +222,9 @@
const aidl::android::hardware::automotive::vehicle::VehiclePropValue& value) const;
bool isHvacPropAndHvacNotAvailable(int32_t propId, int32_t areaId) const;
VhalResult<void> isAdasPropertyAvailable(int32_t adasStatePropertyId) const;
+ VhalResult<void> synchronizeHvacTemp(int32_t hvacDualOnAreaId,
+ std::optional<float> newTempC) const;
+ std::optional<int32_t> getSyncedAreaIdIfHvacDualOn(int32_t hvacTemperatureSetAreaId) const;
std::unordered_map<int32_t, ConfigDeclaration> loadConfigDeclarations();
@@ -262,6 +271,11 @@
void generateVendorConfigs(
std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig>&) const;
+ aidl::android::hardware::automotive::vehicle::StatusCode subscribePropIdAreaIdLocked(
+ int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
+ const aidl::android::hardware::automotive::vehicle::VehiclePropConfig&
+ vehiclePropConfig) REQUIRES(mLock);
+
static aidl::android::hardware::automotive::vehicle::VehiclePropValue createHwInputKeyProp(
aidl::android::hardware::automotive::vehicle::VehicleHwKeyInputAction action,
int32_t keyCode, int32_t targetDisplay);
@@ -275,6 +289,10 @@
static std::string genFakeDataHelp();
static std::string parseErrMsg(std::string fieldName, std::string value, std::string type);
+ static bool isVariableUpdateRateSupported(
+ const aidl::android::hardware::automotive::vehicle::VehiclePropConfig&
+ vehiclePropConfig,
+ int32_t areaId);
};
} // namespace fake
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
index ee24fbd..7ff03c6 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/src/FakeVehicleHardware.cpp
@@ -60,6 +60,8 @@
using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
+using ::aidl::android::hardware::automotive::vehicle::toString;
using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
using ::aidl::android::hardware::automotive::vehicle::VehicleArea;
@@ -67,6 +69,7 @@
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehicleProperty;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyAccess;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyChangeMode;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyGroup;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyType;
@@ -598,6 +601,65 @@
return {};
}
+VhalResult<void> FakeVehicleHardware::synchronizeHvacTemp(int32_t hvacDualOnAreaId,
+ std::optional<float> newTempC) const {
+ auto hvacTemperatureSetResults = mServerSidePropStore->readValuesForProperty(
+ toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+ if (!hvacTemperatureSetResults.ok()) {
+ return StatusError(StatusCode::NOT_AVAILABLE)
+ << "Failed to get HVAC_TEMPERATURE_SET, error: "
+ << getErrorMsg(hvacTemperatureSetResults);
+ }
+ auto& hvacTemperatureSetValues = hvacTemperatureSetResults.value();
+ std::optional<float> tempCToSynchronize = newTempC;
+ for (size_t i = 0; i < hvacTemperatureSetValues.size(); i++) {
+ int32_t areaId = hvacTemperatureSetValues[i]->areaId;
+ if ((hvacDualOnAreaId & areaId) != areaId) {
+ continue;
+ }
+ if (hvacTemperatureSetValues[i]->status != VehiclePropertyStatus::AVAILABLE) {
+ continue;
+ }
+ // When HVAC_DUAL_ON is initially enabled, synchronize all area IDs
+ // to the temperature of the first area ID, which is the driver's.
+ if (!tempCToSynchronize.has_value()) {
+ tempCToSynchronize = hvacTemperatureSetValues[i]->value.floatValues[0];
+ continue;
+ }
+ auto updatedValue = std::move(hvacTemperatureSetValues[i]);
+ updatedValue->value.floatValues[0] = tempCToSynchronize.value();
+ updatedValue->timestamp = elapsedRealtimeNano();
+ // This will trigger a property change event for the current hvac property value.
+ auto writeResult =
+ mServerSidePropStore->writeValue(std::move(updatedValue), /*updateStatus=*/true,
+ VehiclePropertyStore::EventMode::ALWAYS);
+ if (!writeResult.ok()) {
+ return StatusError(getErrorCode(writeResult))
+ << "Failed to write value into property store, error: "
+ << getErrorMsg(writeResult);
+ }
+ }
+ return {};
+}
+
+std::optional<int32_t> FakeVehicleHardware::getSyncedAreaIdIfHvacDualOn(
+ int32_t hvacTemperatureSetAreaId) const {
+ auto hvacDualOnResults =
+ mServerSidePropStore->readValuesForProperty(toInt(VehicleProperty::HVAC_DUAL_ON));
+ if (!hvacDualOnResults.ok()) {
+ return std::nullopt;
+ }
+ auto& hvacDualOnValues = hvacDualOnResults.value();
+ for (size_t i = 0; i < hvacDualOnValues.size(); i++) {
+ if ((hvacDualOnValues[i]->areaId & hvacTemperatureSetAreaId) == hvacTemperatureSetAreaId &&
+ hvacDualOnValues[i]->value.int32Values.size() == 1 &&
+ hvacDualOnValues[i]->value.int32Values[0] == 1) {
+ return hvacDualOnValues[i]->areaId;
+ }
+ }
+ return std::nullopt;
+}
+
FakeVehicleHardware::ValueResultType FakeVehicleHardware::getUserHalProp(
const VehiclePropValue& value) const {
auto propId = value.prop;
@@ -850,6 +912,28 @@
case toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION):
*isSpecialValue = true;
return setHvacTemperatureValueSuggestion(value);
+ case toInt(VehicleProperty::HVAC_TEMPERATURE_SET):
+ if (value.value.floatValues.size() != 1) {
+ *isSpecialValue = true;
+ return StatusError(StatusCode::INVALID_ARG)
+ << "HVAC_DUAL_ON requires only one float value";
+ }
+ if (auto hvacDualOnAreaId = getSyncedAreaIdIfHvacDualOn(value.areaId);
+ hvacDualOnAreaId.has_value()) {
+ *isSpecialValue = true;
+ return synchronizeHvacTemp(hvacDualOnAreaId.value(), value.value.floatValues[0]);
+ }
+ return {};
+ case toInt(VehicleProperty::HVAC_DUAL_ON):
+ if (value.value.int32Values.size() != 1) {
+ *isSpecialValue = true;
+ return StatusError(StatusCode::INVALID_ARG)
+ << "HVAC_DUAL_ON requires only one int32 value";
+ }
+ if (value.value.int32Values[0] == 1) {
+ synchronizeHvacTemp(value.areaId, std::nullopt);
+ }
+ return {};
case toInt(VehicleProperty::LANE_CENTERING_ASSIST_COMMAND): {
isAdasPropertyAvailableResult =
isAdasPropertyAvailable(toInt(VehicleProperty::LANE_CENTERING_ASSIST_STATE));
@@ -1926,43 +2010,109 @@
mOnPropertySetErrorCallback = std::move(callback);
}
-StatusCode FakeVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
- // DefaultVehicleHal makes sure that sampleRate must be within minSampleRate and maxSampleRate.
- // For fake implementation, we would write the same value with a new timestamp into propStore
- // at sample rate.
- std::scoped_lock<std::mutex> lockGuard(mLock);
+StatusCode FakeVehicleHardware::subscribe(SubscribeOptions options) {
+ int32_t propId = options.propId;
+ auto configResult = mServerSidePropStore->getConfig(propId);
+ if (!configResult.ok()) {
+ ALOGE("subscribe: property: %" PRId32 " is not supported", propId);
+ return StatusCode::INVALID_ARG;
+ }
+
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ for (int areaId : options.areaIds) {
+ if (StatusCode status = subscribePropIdAreaIdLocked(propId, areaId, options.sampleRate,
+ options.enableVariableUpdateRate,
+ *configResult.value());
+ status != StatusCode::OK) {
+ return status;
+ }
+ }
+ return StatusCode::OK;
+}
+
+bool FakeVehicleHardware::isVariableUpdateRateSupported(const VehiclePropConfig& vehiclePropConfig,
+ int32_t areaId) {
+ for (size_t i = 0; i < vehiclePropConfig.areaConfigs.size(); i++) {
+ const auto& areaConfig = vehiclePropConfig.areaConfigs[i];
+ if (areaConfig.areaId != areaId) {
+ continue;
+ }
+ if (areaConfig.supportVariableUpdateRate) {
+ return true;
+ }
+ break;
+ }
+ return false;
+}
+
+StatusCode FakeVehicleHardware::subscribePropIdAreaIdLocked(
+ int32_t propId, int32_t areaId, float sampleRateHz, bool enableVariableUpdateRate,
+ const VehiclePropConfig& vehiclePropConfig) {
+ PropIdAreaId propIdAreaId{
+ .propId = propId,
+ .areaId = areaId,
+ };
+ switch (vehiclePropConfig.changeMode) {
+ case VehiclePropertyChangeMode::STATIC:
+ ALOGW("subscribe to a static property, do nothing.");
+ return StatusCode::OK;
+ case VehiclePropertyChangeMode::ON_CHANGE:
+ mSubOnChangePropIdAreaIds.insert(std::move(propIdAreaId));
+ return StatusCode::OK;
+ case VehiclePropertyChangeMode::CONTINUOUS:
+ if (sampleRateHz == 0.f) {
+ ALOGE("Must not use sample rate 0 for a continuous property");
+ return StatusCode::INTERNAL_ERROR;
+ }
+ if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
+ mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
+ }
+ int64_t intervalInNanos = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
+
+ // For continuous properties, we must generate a new onPropertyChange event
+ // periodically according to the sample rate.
+ auto eventMode = VehiclePropertyStore::EventMode::ALWAYS;
+ if (isVariableUpdateRateSupported(vehiclePropConfig, areaId) &&
+ enableVariableUpdateRate) {
+ eventMode = VehiclePropertyStore::EventMode::ON_VALUE_CHANGE;
+ }
+ auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId,
+ eventMode] {
+ // Refresh the property value. In real implementation, this should poll the latest
+ // value from vehicle bus. Here, we are just refreshing the existing value with a
+ // new timestamp.
+ auto result = getValue(VehiclePropValue{
+ .areaId = areaId,
+ .prop = propId,
+ .value = {},
+ });
+ if (!result.ok()) {
+ // Failed to read current value, skip refreshing.
+ return;
+ }
+ result.value()->timestamp = elapsedRealtimeNano();
+
+ mServerSidePropStore->writeValue(std::move(result.value()), /*updateStatus=*/true,
+ eventMode);
+ });
+ mRecurrentTimer->registerTimerCallback(intervalInNanos, action);
+ mRecurrentActions[propIdAreaId] = action;
+ return StatusCode::OK;
+ }
+}
+
+StatusCode FakeVehicleHardware::unsubscribe(int32_t propId, int32_t areaId) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
PropIdAreaId propIdAreaId{
.propId = propId,
.areaId = areaId,
};
if (mRecurrentActions.find(propIdAreaId) != mRecurrentActions.end()) {
mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propIdAreaId]);
+ mRecurrentActions.erase(propIdAreaId);
}
- if (sampleRate == 0) {
- return StatusCode::OK;
- }
- int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
- auto action = std::make_shared<RecurrentTimer::Callback>([this, propId, areaId] {
- // Refresh the property value. In real implementation, this should poll the latest value
- // from vehicle bus. Here, we are just refreshing the existing value with a new timestamp.
- auto result = getValue(VehiclePropValue{
- .areaId = areaId,
- .prop = propId,
- .value = {},
- });
- if (!result.ok()) {
- // Failed to read current value, skip refreshing.
- return;
- }
- result.value()->timestamp = elapsedRealtimeNano();
- // For continuous properties, we must generate a new onPropertyChange event periodically
- // according to the sample rate.
- mServerSidePropStore->writeValue(std::move(result.value()), /*updateStatus=*/true,
- VehiclePropertyStore::EventMode::ALWAYS);
- });
- mRecurrentTimer->registerTimerCallback(interval, action);
- mRecurrentActions[propIdAreaId] = action;
+ mSubOnChangePropIdAreaIds.erase(propIdAreaId);
return StatusCode::OK;
}
@@ -1971,6 +2121,23 @@
return;
}
+ PropIdAreaId propIdAreaId{
+ .propId = value.prop,
+ .areaId = value.areaId,
+ };
+
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ if (mRecurrentActions.find(propIdAreaId) == mRecurrentActions.end() &&
+ mSubOnChangePropIdAreaIds.find(propIdAreaId) == mSubOnChangePropIdAreaIds.end()) {
+ if (FAKE_VEHICLEHARDWARE_DEBUG) {
+ ALOGD("The updated property value: %s is not subscribed, ignore",
+ value.toString().c_str());
+ }
+ return;
+ }
+ }
+
std::vector<VehiclePropValue> updatedValues;
updatedValues.push_back(value);
(*mOnPropertyChangeCallback)(std::move(updatedValues));
diff --git a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
index cf9beee..3b6f717 100644
--- a/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
+++ b/automotive/vehicle/aidl/impl/fake_impl/hardware/test/FakeVehicleHardwareTest.cpp
@@ -72,6 +72,7 @@
using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReport;
using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateReq;
using ::aidl::android::hardware::automotive::vehicle::VehicleApPowerStateShutdownParam;
@@ -94,6 +95,7 @@
using ::testing::Eq;
using ::testing::HasSubstr;
using ::testing::IsSubsetOf;
+using ::testing::UnorderedElementsAre;
using ::testing::WhenSortedBy;
using std::chrono::milliseconds;
@@ -149,6 +151,15 @@
mHardware = std::move(hardware);
}
+ static SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId,
+ float sampleRateHz) {
+ SubscribeOptions options;
+ options.areaIds = {areaId};
+ options.propId = propId;
+ options.sampleRate = sampleRateHz;
+ return options;
+ }
+
StatusCode setValues(const std::vector<SetValueRequest>& requests) {
{
std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -336,6 +347,13 @@
return mEventCount[propIdAreaId];
}
+ void subscribe(int32_t propId, int32_t areaId, float sampleRateHz) {
+ ASSERT_EQ(StatusCode::OK,
+ getHardware()->subscribe(newSubscribeOptions(propId, areaId, sampleRateHz)))
+ << "failed to subscribe to propId: " << propId << "areaId: " << areaId
+ << ", sampleRateHz: " << sampleRateHz;
+ }
+
static void addSetValueRequest(std::vector<SetValueRequest>& requests,
std::vector<SetValueResult>& expectedResults, int64_t requestId,
const VehiclePropValue& value, StatusCode expectedStatus) {
@@ -370,24 +388,24 @@
}
std::vector<VehiclePropValue> getTestPropValues() {
- VehiclePropValue fuelCapacity = {
- .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
- .value = {.floatValues = {1.0}},
+ VehiclePropValue oilLevel = {
+ .prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL),
+ .value = {.int32Values = {1}},
};
- VehiclePropValue leftTirePressure = {
- .prop = toInt(VehicleProperty::TIRE_PRESSURE),
+ VehiclePropValue leftHvacTemp = {
+ .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_CURRENT),
.value = {.floatValues = {170.0}},
- .areaId = WHEEL_FRONT_LEFT,
+ .areaId = SEAT_1_LEFT,
};
- VehiclePropValue rightTirePressure = {
- .prop = toInt(VehicleProperty::TIRE_PRESSURE),
+ VehiclePropValue rightHvacTemp = {
+ .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_CURRENT),
.value = {.floatValues = {180.0}},
- .areaId = WHEEL_FRONT_RIGHT,
+ .areaId = SEAT_1_RIGHT,
};
- return {fuelCapacity, leftTirePressure, rightTirePressure};
+ return {oilLevel, leftHvacTemp, rightHvacTemp};
}
struct PropValueCmp {
@@ -437,6 +455,29 @@
ASSERT_EQ(configs.size(), helper.loadConfigDeclarations().size());
}
+TEST_F(FakeVehicleHardwareTest, testGetAllPropertyConfigs_defaultSupportVUR) {
+ std::vector<VehiclePropConfig> configs = getHardware()->getAllPropertyConfigs();
+
+ for (const auto& config : configs) {
+ bool expectedSupportVUR = true;
+ if (config.prop == toInt(VehicleProperty::VHAL_HEARTBEAT) ||
+ config.prop == toInt(VehicleProperty::CLUSTER_HEARTBEAT)) {
+ expectedSupportVUR = false;
+ }
+ EXPECT_GE(config.areaConfigs.size(), 1u)
+ << "expect at least one area config, including global area config, propId: "
+ << config.prop;
+ if (config.areaConfigs.size() == 0) {
+ continue;
+ }
+ for (const auto& areaConfig : config.areaConfigs) {
+ EXPECT_EQ(areaConfig.supportVariableUpdateRate, expectedSupportVUR)
+ << "unexpected supportVariableUpdateRate for propId: " << config.prop
+ << ", areaId: " << areaConfig.areaId;
+ }
+ }
+}
+
TEST_F(FakeVehicleHardwareTest, testGetDefaultValues) {
std::vector<GetValueRequest> getValueRequests;
std::vector<GetValueResult> expectedGetValueResults;
@@ -559,17 +600,13 @@
ASSERT_THAT(getSetValueResults(), ContainerEq(expectedResults));
}
-TEST_F(FakeVehicleHardwareTest, testRegisterOnPropertyChangeEvent) {
- // We have already registered this callback in Setup, here we are registering again.
- auto callback = std::make_unique<IVehicleHardware::PropertyChangeCallback>(
- [this](const std::vector<VehiclePropValue>& values) { onPropertyChangeEvent(values); });
- getHardware()->registerOnPropertyChangeEvent(std::move(callback));
-
+TEST_F(FakeVehicleHardwareTest, testSetValues_getUpdateEvents) {
auto testValues = getTestPropValues();
std::vector<SetValueRequest> requests;
std::vector<SetValueResult> expectedResults;
int64_t requestId = 1;
for (auto& value : testValues) {
+ subscribe(value.prop, value.areaId, /*sampleRateHz=*/0);
addSetValueRequest(requests, expectedResults, requestId++, value, StatusCode::OK);
}
int64_t timestamp = elapsedRealtimeNano();
@@ -1624,27 +1661,30 @@
return info.param.name;
});
-TEST_F(FakeVehicleHardwareTest, testSetWaitForVhalAfterCarServiceCrash) {
- int32_t propId = toInt(VehicleProperty::AP_POWER_STATE_REPORT);
+TEST_F(FakeVehicleHardwareTest, testSetWaitForVhal_alwaysTriggerEvents) {
+ int32_t powerReq = toInt(VehicleProperty::AP_POWER_STATE_REQ);
+ subscribe(powerReq, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
+ int32_t powerReport = toInt(VehicleProperty::AP_POWER_STATE_REPORT);
VehiclePropValue request = VehiclePropValue{
- .prop = propId,
+ .prop = powerReport,
.value.int32Values = {toInt(VehicleApPowerStateReport::WAIT_FOR_VHAL)},
};
- ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << propId;
+ ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << powerReport;
// Clear existing events.
clearChangedProperties();
// Simulate a Car Service crash, Car Service would restart and send the message again.
- ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << propId;
+ ASSERT_EQ(setValue(request), StatusCode::OK) << "failed to set property " << powerReport;
std::vector<VehiclePropValue> events = getChangedProperties();
// Even though the state is already ON, we should receive another ON event.
- ASSERT_EQ(events.size(), 1u);
+ ASSERT_EQ(events.size(), 1u) << "failed to receive on-change events AP_POWER_STATE_REQ ON";
// Erase the timestamp for comparison.
events[0].timestamp = 0;
auto expectedValue = VehiclePropValue{
- .prop = toInt(VehicleProperty::AP_POWER_STATE_REQ),
+ .prop = powerReq,
.status = VehiclePropertyStatus::AVAILABLE,
.value.int32Values = {toInt(VehicleApPowerStateReq::ON), 0},
};
@@ -1849,6 +1889,101 @@
}
}
+TEST_F(FakeVehicleHardwareTest, testHvacDualOnSynchronizesTemp) {
+ auto hvacDualOnConfig = std::move(getVehiclePropConfig(toInt(VehicleProperty::HVAC_DUAL_ON)));
+ auto hvacTemperatureSetConfig =
+ std::move(getVehiclePropConfig(toInt(VehicleProperty::HVAC_TEMPERATURE_SET)));
+ EXPECT_NE(hvacDualOnConfig, nullptr);
+ EXPECT_NE(hvacTemperatureSetConfig, nullptr);
+ for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+ int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+ subscribe(toInt(VehicleProperty::HVAC_TEMPERATURE_SET), hvacTemperatureSetAreaId,
+ /*sampleRateHz*/ 0);
+ }
+ for (auto& hvacDualOnConfig : hvacDualOnConfig->areaConfigs) {
+ int32_t hvacDualOnAreaId = hvacDualOnConfig.areaId;
+ subscribe(toInt(VehicleProperty::HVAC_DUAL_ON), hvacDualOnAreaId, /*sampleRateHz*/ 0);
+ StatusCode status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_DUAL_ON),
+ .areaId = hvacDualOnAreaId,
+ .value.int32Values = {1}});
+ EXPECT_EQ(status, StatusCode::OK);
+
+ // Verify there's an event for all HVAC_TEMPERATURE_SET
+ // area IDs covered by the HVAC_DUAL_ON area ID
+ auto events = getChangedProperties();
+ std::unordered_set<float> temperatureValues;
+ for (const auto& event : events) {
+ // Ignore HVAC_DUAL_ON event
+ if (event.prop == toInt(VehicleProperty::HVAC_DUAL_ON)) {
+ continue;
+ }
+ EXPECT_EQ(event.prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+ EXPECT_EQ((hvacDualOnAreaId & event.areaId), event.areaId);
+ EXPECT_EQ(1u, event.value.floatValues.size());
+ temperatureValues.insert(event.value.floatValues[0]);
+ }
+ // Verify that the temperature value is the same for all events
+ // Ie the temperature in all area IDs are synchronized
+ EXPECT_EQ(1u, temperatureValues.size());
+ clearChangedProperties();
+
+ // Verify when any HVAC_TEMPERATURE_SET area ID is changed all
+ // area IDs covered by the HVAC_DUAL_ON area ID are also changed
+ for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+ int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+ if ((hvacDualOnAreaId & hvacTemperatureSetAreaId) != hvacTemperatureSetAreaId) {
+ continue;
+ }
+ float expectedValue = 25;
+ status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
+ .areaId = hvacTemperatureSetAreaId,
+ .value.floatValues = {expectedValue}});
+ EXPECT_EQ(status, StatusCode::OK);
+ events = getChangedProperties();
+ for (const auto& event : events) {
+ EXPECT_EQ(event.prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+ EXPECT_EQ(1u, event.value.floatValues.size());
+ EXPECT_EQ(expectedValue, event.value.floatValues[0]);
+ }
+ clearChangedProperties();
+ }
+
+ status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_DUAL_ON),
+ .areaId = hvacDualOnAreaId,
+ .value.int32Values = {0}});
+ EXPECT_EQ(status, StatusCode::OK);
+
+ // When HVAC_DUAL_ON is disabled, there should be no events created
+ // for HVAC_TEMPERATURE_SET ie no temperature synchronization.
+ events = getChangedProperties();
+ EXPECT_EQ(1u, events.size());
+ EXPECT_EQ(events[0].prop, toInt(VehicleProperty::HVAC_DUAL_ON));
+ EXPECT_EQ(events[0].areaId, hvacDualOnAreaId);
+ clearChangedProperties();
+
+ // Verify when any HVAC_TEMPERATURE_SET area ID is
+ // changed other area IDs do not change.
+ for (auto& hvacTemperatureSetConfig : hvacTemperatureSetConfig->areaConfigs) {
+ int32_t hvacTemperatureSetAreaId = hvacTemperatureSetConfig.areaId;
+ if ((hvacDualOnAreaId & hvacTemperatureSetAreaId) != hvacTemperatureSetAreaId) {
+ continue;
+ }
+ float expectedValue = 24;
+ status = setValue(VehiclePropValue{.prop = toInt(VehicleProperty::HVAC_TEMPERATURE_SET),
+ .areaId = hvacTemperatureSetAreaId,
+ .value.floatValues = {expectedValue}});
+ EXPECT_EQ(status, StatusCode::OK);
+ events = getChangedProperties();
+ EXPECT_EQ(1u, events.size());
+ EXPECT_EQ(events[0].prop, toInt(VehicleProperty::HVAC_TEMPERATURE_SET));
+ EXPECT_EQ(events[0].areaId, hvacTemperatureSetAreaId);
+ EXPECT_EQ(1u, events[0].value.floatValues.size());
+ EXPECT_EQ(expectedValue, events[0].value.floatValues[0]);
+ clearChangedProperties();
+ }
+ }
+}
+
TEST_F(FakeVehicleHardwareTest, testGetAdasPropNotAvailable) {
std::unordered_map<int32_t, std::vector<int32_t>> adasEnabledPropToDependentProps = {
{
@@ -2015,6 +2150,22 @@
},
},
};
+
+ // First subscribe to all the properties that we will change.
+ for (auto& enabledToErrorStateProps : adasEnabledPropToAdasPropWithErrorState) {
+ std::unordered_set<int32_t> expectedChangedPropIds(enabledToErrorStateProps.second.begin(),
+ enabledToErrorStateProps.second.end());
+ expectedChangedPropIds.insert(enabledToErrorStateProps.first);
+
+ for (int32_t propId : expectedChangedPropIds) {
+ int32_t areaId = 0;
+ if (propId == toInt(VehicleProperty::BLIND_SPOT_WARNING_STATE)) {
+ areaId = toInt(VehicleAreaMirror::DRIVER_LEFT);
+ }
+ subscribe(propId, areaId, /*sampleRateHz*/ 0);
+ }
+ }
+
for (auto& enabledToErrorStateProps : adasEnabledPropToAdasPropWithErrorState) {
int32_t adasEnabledPropertyId = enabledToErrorStateProps.first;
StatusCode status =
@@ -2095,9 +2246,16 @@
}
TEST_F(FakeVehicleHardwareTest, testSwitchUser) {
+ SubscribeOptions options;
+ int32_t propSwitchUser = toInt(VehicleProperty::SWITCH_USER);
+ options.propId = propSwitchUser;
+ options.areaIds = {0, 1};
+ ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+ << "failed to subscribe to propId: " << propSwitchUser;
+
// This is the same example as used in User HAL Emulation doc.
VehiclePropValue valueToSet = {
- .prop = toInt(VehicleProperty::SWITCH_USER),
+ .prop = propSwitchUser,
.areaId = 1,
.value.int32Values = {666, 3, 2},
};
@@ -2108,7 +2266,7 @@
// Simulate a request from Android side.
VehiclePropValue switchUserRequest = {
- .prop = toInt(VehicleProperty::SWITCH_USER),
+ .prop = propSwitchUser,
.areaId = 0,
.value.int32Values = {666, 3},
};
@@ -2138,7 +2296,7 @@
events[0].timestamp = 0;
auto expectedValue = VehiclePropValue{
.areaId = 0,
- .prop = toInt(VehicleProperty::SWITCH_USER),
+ .prop = propSwitchUser,
.value.int32Values =
{
// Request ID
@@ -2153,6 +2311,13 @@
}
TEST_F(FakeVehicleHardwareTest, testCreateUser) {
+ SubscribeOptions options;
+ int32_t propCreateUser = toInt(VehicleProperty::CREATE_USER);
+ options.propId = propCreateUser;
+ options.areaIds = {0, 1};
+ ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+ << "failed to subscribe to propId: " << propCreateUser;
+
// This is the same example as used in User HAL Emulation doc.
VehiclePropValue valueToSet = {
.prop = toInt(VehicleProperty::CREATE_USER),
@@ -2166,7 +2331,7 @@
// Simulate a request from Android side.
VehiclePropValue createUserRequest = {
- .prop = toInt(VehicleProperty::CREATE_USER),
+ .prop = propCreateUser,
.areaId = 0,
.value.int32Values = {666},
};
@@ -2195,7 +2360,7 @@
events[0].timestamp = 0;
auto expectedValue = VehiclePropValue{
.areaId = 0,
- .prop = toInt(VehicleProperty::CREATE_USER),
+ .prop = propCreateUser,
.value.int32Values =
{
// Request ID
@@ -2208,9 +2373,16 @@
}
TEST_F(FakeVehicleHardwareTest, testInitialUserInfo) {
+ SubscribeOptions options;
+ int32_t propInitialUserInfo = toInt(VehicleProperty::INITIAL_USER_INFO);
+ options.propId = propInitialUserInfo;
+ options.areaIds = {0, 1};
+ ASSERT_EQ(StatusCode::OK, getHardware()->subscribe(options))
+ << "failed to subscribe to propId: " << propInitialUserInfo;
+
// This is the same example as used in User HAL Emulation doc.
VehiclePropValue valueToSet = {
- .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+ .prop = propInitialUserInfo,
.areaId = 1,
.value.int32Values = {666, 1, 11},
};
@@ -2221,7 +2393,7 @@
// Simulate a request from Android side.
VehiclePropValue initialUserInfoRequest = {
- .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+ .prop = propInitialUserInfo,
.areaId = 0,
.value.int32Values = {3},
};
@@ -2238,7 +2410,7 @@
events[0].timestamp = 0;
auto expectedValue = VehiclePropValue{
.areaId = 0,
- .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+ .prop = propInitialUserInfo,
.value.int32Values = {3, 1, 11},
};
EXPECT_EQ(events[0], expectedValue);
@@ -2253,7 +2425,7 @@
events[0].timestamp = 0;
expectedValue = VehiclePropValue{
.areaId = 0,
- .prop = toInt(VehicleProperty::INITIAL_USER_INFO),
+ .prop = propInitialUserInfo,
.value.int32Values =
{
// Request ID
@@ -2395,13 +2567,14 @@
}
TEST_F(FakeVehicleHardwareTest, testDumpInjectEvent) {
- int32_t prop = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+ int32_t prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL);
std::string propIdStr = std::to_string(prop);
+ subscribe(prop, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
int64_t timestamp = elapsedRealtimeNano();
- // Inject an event with float value 123.4 and timestamp.
DumpResult result = getHardware()->dump(
- {"--inject-event", propIdStr, "-f", "123.4", "-t", std::to_string(timestamp)});
+ {"--inject-event", propIdStr, "-i", "1234", "-t", std::to_string(timestamp)});
ASSERT_FALSE(result.callerShouldDumpState);
ASSERT_THAT(result.buffer,
@@ -2412,7 +2585,7 @@
ASSERT_EQ(events.size(), 1u);
auto event = events[0];
ASSERT_EQ(event.timestamp, timestamp);
- ASSERT_EQ(event.value.floatValues, std::vector<float>({123.4}));
+ ASSERT_EQ(event.value.int32Values, std::vector<int32_t>({1234}));
}
TEST_F(FakeVehicleHardwareTest, testDumpInvalidOptions) {
@@ -2755,9 +2928,13 @@
});
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataLinear) {
- // Start a fake linear data generator for vehicle speed at 0.1s interval.
+ // Start a fake linear data generator for engine oil level at 0.1s interval.
// range: 0 - 100, current value: 30, step: 20.
- std::string propIdString = StringPrintf("%d", toInt(VehicleProperty::PERF_VEHICLE_SPEED));
+ int32_t prop = toInt(VehicleProperty::ENGINE_OIL_LEVEL);
+
+ subscribe(prop, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
+ std::string propIdString = StringPrintf("%d", prop);
std::vector<std::string> options = {"--genfakedata", "--startlinear", propIdString,
/*middleValue=*/"50",
/*currentValue=*/"30",
@@ -2770,15 +2947,14 @@
ASSERT_FALSE(result.callerShouldDumpState);
ASSERT_THAT(result.buffer, HasSubstr("successfully"));
- ASSERT_TRUE(waitForChangedProperties(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0, /*count=*/5,
- milliseconds(1000)))
+ ASSERT_TRUE(waitForChangedProperties(prop, 0, /*count=*/5, milliseconds(1000)))
<< "not enough events generated for linear data generator";
int32_t value = 30;
auto events = getChangedProperties();
for (size_t i = 0; i < 5; i++) {
- ASSERT_EQ(1u, events[i].value.floatValues.size());
- EXPECT_EQ(static_cast<float>(value), events[i].value.floatValues[0]);
+ ASSERT_EQ(1u, events[i].value.int32Values.size());
+ EXPECT_EQ(value, events[i].value.int32Values[0]);
value = (value + 20) % 100;
}
@@ -2794,7 +2970,7 @@
std::this_thread::sleep_for(std::chrono::milliseconds(200));
// There should be no new events generated.
- EXPECT_EQ(0u, getEventCount(toInt(VehicleProperty::PERF_VEHICLE_SPEED), 0));
+ EXPECT_EQ(0u, getEventCount(prop, 0));
}
std::string getTestFilePath(const char* filename) {
@@ -2803,6 +2979,8 @@
}
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJson) {
+ subscribe(toInt(VehicleProperty::GEAR_SELECTION), /*areaId*/ 0, /*sampleRateHz*/ 0);
+
std::vector<std::string> options = {"--genfakedata", "--startjson", "--path",
getTestFilePath("prop.json"), "2"};
@@ -2829,6 +3007,8 @@
}
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataJsonByContent) {
+ subscribe(toInt(VehicleProperty::GEAR_SELECTION), /*areaId*/ 0, /*sampleRateHz*/ 0);
+
std::vector<std::string> options = {
"--genfakedata", "--startjson", "--content",
"[{\"timestamp\":1000000,\"areaId\":0,\"value\":8,\"prop\":289408000}]", "1"};
@@ -2903,8 +3083,11 @@
}
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataKeyPress) {
+ int32_t propHwKeyInput = toInt(VehicleProperty::HW_KEY_INPUT);
std::vector<std::string> options = {"--genfakedata", "--keypress", "1", "2"};
+ subscribe(propHwKeyInput, /*areaId*/ 0, /*sampleRateHz*/ 0);
+
DumpResult result = getHardware()->dump(options);
ASSERT_FALSE(result.callerShouldDumpState);
@@ -2912,8 +3095,8 @@
auto events = getChangedProperties();
ASSERT_EQ(2u, events.size());
- EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[0].prop);
- EXPECT_EQ(toInt(VehicleProperty::HW_KEY_INPUT), events[1].prop);
+ EXPECT_EQ(propHwKeyInput, events[0].prop);
+ EXPECT_EQ(propHwKeyInput, events[1].prop);
ASSERT_EQ(3u, events[0].value.int32Values.size());
ASSERT_EQ(3u, events[1].value.int32Values.size());
EXPECT_EQ(toInt(VehicleHwKeyInputAction::ACTION_DOWN), events[0].value.int32Values[0]);
@@ -2925,8 +3108,11 @@
}
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataKeyInputV2) {
+ int32_t propHwKeyInputV2 = toInt(VehicleProperty::HW_KEY_INPUT_V2);
std::vector<std::string> options = {"--genfakedata", "--keyinputv2", "1", "2", "3", "4", "5"};
+ subscribe(propHwKeyInputV2, /*areaId*/ 1, /*sampleRateHz*/ 0);
+
DumpResult result = getHardware()->dump(options);
ASSERT_FALSE(result.callerShouldDumpState);
@@ -2944,6 +3130,7 @@
}
TEST_F(FakeVehicleHardwareTest, testDebugGenFakeDataMotionInput) {
+ int32_t propHwMotionInput = toInt(VehicleProperty::HW_MOTION_INPUT);
std::vector<std::string> options = {"--genfakedata",
"--motioninput",
"1",
@@ -2966,6 +3153,8 @@
"65.5",
"76.6"};
+ subscribe(propHwMotionInput, /*areaId*/ 1, /*sampleRateHz*/ 0);
+
DumpResult result = getHardware()->dump(options);
ASSERT_FALSE(result.callerShouldDumpState);
@@ -2973,7 +3162,7 @@
auto events = getChangedProperties();
ASSERT_EQ(1u, events.size());
- EXPECT_EQ(toInt(VehicleProperty::HW_MOTION_INPUT), events[0].prop);
+ EXPECT_EQ(propHwMotionInput, events[0].prop);
ASSERT_EQ(9u, events[0].value.int32Values.size());
EXPECT_EQ(2, events[0].value.int32Values[0]);
EXPECT_EQ(3, events[0].value.int32Values[1]);
@@ -3014,23 +3203,27 @@
ASSERT_EQ(result.value().value.byteValues, std::vector<uint8_t>({0x04, 0x03, 0x02, 0x01}));
}
-TEST_F(FakeVehicleHardwareTest, testUpdateSampleRate) {
+TEST_F(FakeVehicleHardwareTest, testSubscribeUnsubscribe_continuous) {
int32_t propSpeed = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
int32_t propSteering = toInt(VehicleProperty::PERF_STEERING_ANGLE);
int32_t areaId = 0;
- getHardware()->updateSampleRate(propSpeed, areaId, 5);
+
+ auto status = getHardware()->subscribe(newSubscribeOptions(propSpeed, areaId, 5));
+ ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
ASSERT_TRUE(waitForChangedProperties(propSpeed, areaId, /*count=*/5, milliseconds(1500)))
<< "not enough events generated for speed";
- getHardware()->updateSampleRate(propSteering, areaId, 10);
+ status = getHardware()->subscribe(newSubscribeOptions(propSteering, areaId, 10));
+ ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/10, milliseconds(1500)))
<< "not enough events generated for steering";
int64_t timestamp = elapsedRealtimeNano();
// Disable refreshing for propSpeed.
- getHardware()->updateSampleRate(propSpeed, areaId, 0);
+ status = getHardware()->unsubscribe(propSpeed, areaId);
+ ASSERT_EQ(status, StatusCode::OK) << "failed to unsubscribe";
clearChangedProperties();
ASSERT_TRUE(waitForChangedProperties(propSteering, areaId, /*count=*/5, milliseconds(1500)))
@@ -3043,12 +3236,99 @@
}
}
+TEST_F(FakeVehicleHardwareTest, testSubscribe_enableVUR) {
+ int32_t propSpeed = toInt(VehicleProperty::PERF_VEHICLE_SPEED);
+ int32_t areaId = 0;
+ SubscribeOptions options;
+ options.propId = propSpeed;
+ options.areaIds = {areaId};
+ options.enableVariableUpdateRate = true;
+ options.sampleRate = 5;
+ int64_t timestamp = elapsedRealtimeNano();
+
+ auto status = getHardware()->subscribe(options);
+ ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
+
+ status = setValue({
+ .prop = propSpeed,
+ .areaId = 0,
+ .value.floatValues = {1.1f},
+ });
+ ASSERT_EQ(status, StatusCode::OK) << "failed to set speed";
+
+ status = setValue({
+ .prop = propSpeed,
+ .areaId = 0,
+ .value.floatValues = {1.2f},
+ });
+ ASSERT_EQ(status, StatusCode::OK) << "failed to set speed";
+
+ ASSERT_TRUE(waitForChangedProperties(propSpeed, areaId, /*count=*/2, milliseconds(100)))
+ << "not enough events generated for speed";
+ auto updatedValues = getChangedProperties();
+ std::unordered_set<float> gotValues;
+ for (auto& value : updatedValues) {
+ EXPECT_GE(value.timestamp, timestamp) << "timestamp must be updated";
+ EXPECT_EQ(value.prop, propSpeed) << "propId must be correct";
+ EXPECT_EQ(value.areaId, areaId) << "areaId must be correct";
+ gotValues.insert(value.value.floatValues[0]);
+ }
+ EXPECT_THAT(gotValues, UnorderedElementsAre(1.1f, 1.2f))
+ << "must only receive property event for changed value";
+}
+
+TEST_F(FakeVehicleHardwareTest, testSubscribeUnusubscribe_onChange) {
+ int32_t propHvac = toInt(VehicleProperty::HVAC_TEMPERATURE_SET);
+ int32_t areaId = SEAT_1_LEFT;
+
+ auto status = getHardware()->subscribe(newSubscribeOptions(propHvac, areaId, 0));
+ ASSERT_EQ(status, StatusCode::OK) << "failed to subscribe";
+
+ status = setValue({
+ .prop = propHvac,
+ .areaId = areaId,
+ .value.floatValues = {20.0f},
+ });
+ ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+ ASSERT_TRUE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+ << "not enough on change events generated for hvac";
+ clearChangedProperties();
+
+ status = setValue({
+ .prop = propHvac,
+ .areaId = areaId,
+ .value.floatValues = {21.0f},
+ });
+ ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+ ASSERT_TRUE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+ << "not enough on change events generated for hvac";
+ clearChangedProperties();
+
+ status = getHardware()->unsubscribe(propHvac, areaId);
+ ASSERT_EQ(status, StatusCode::OK);
+
+ status = setValue({
+ .prop = propHvac,
+ .areaId = areaId,
+ .value.floatValues = {22.0f},
+ });
+ ASSERT_EQ(status, StatusCode::OK) << "failed to set hvac value";
+
+ ASSERT_FALSE(waitForChangedProperties(propHvac, areaId, /*count=*/1, milliseconds(100)))
+ << "must not receive on change events if the propId, areaId is unsubscribed";
+}
+
TEST_F(FakeVehicleHardwareTest, testSetHvacTemperatureValueSuggestion) {
float CELSIUS = static_cast<float>(toInt(VehicleUnit::CELSIUS));
float FAHRENHEIT = static_cast<float>(toInt(VehicleUnit::FAHRENHEIT));
+ int32_t propHvacTempValueSuggest = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION);
+
+ subscribe(propHvacTempValueSuggest, HVAC_ALL, /*sampleRateHz*/ 0);
VehiclePropValue floatArraySizeFour = {
- .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {0, CELSIUS, 0, 0},
};
@@ -3056,14 +3336,14 @@
EXPECT_EQ(status, StatusCode::OK);
VehiclePropValue floatArraySizeZero = {
- .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
};
status = setValue(floatArraySizeZero);
EXPECT_EQ(status, StatusCode::INVALID_ARG);
VehiclePropValue floatArraySizeFive = {
- .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {0, CELSIUS, 0, 0, 0},
};
@@ -3071,7 +3351,7 @@
EXPECT_EQ(status, StatusCode::INVALID_ARG);
VehiclePropValue invalidUnit = {
- .prop = toInt(VehicleProperty::HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {0, 0, 0, 0},
};
@@ -3102,9 +3382,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInCelsius, CELSIUS, 0, 0},
},
@@ -3112,9 +3390,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInCelsius, CELSIUS,
minTempInCelsius,
@@ -3127,9 +3403,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInFahrenheit, FAHRENHEIT,
0, 0},
@@ -3138,9 +3412,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInFahrenheit, FAHRENHEIT,
minTempInCelsius,
@@ -3153,9 +3425,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInCelsius, CELSIUS, 0, 0},
},
@@ -3163,9 +3433,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInCelsius, CELSIUS,
maxTempInCelsius,
@@ -3178,9 +3446,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInFahrenheit, FAHRENHEIT,
0, 0},
@@ -3189,9 +3455,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInFahrenheit, FAHRENHEIT,
maxTempInCelsius,
@@ -3204,9 +3468,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInCelsius - 1, CELSIUS, 0,
0},
@@ -3215,9 +3477,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInCelsius - 1, CELSIUS,
minTempInCelsius,
@@ -3230,9 +3490,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInFahrenheit - 1,
FAHRENHEIT, 0, 0},
@@ -3241,9 +3499,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInFahrenheit - 1,
FAHRENHEIT, minTempInCelsius,
@@ -3256,9 +3512,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInCelsius + 1, CELSIUS, 0,
0},
@@ -3267,9 +3521,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInCelsius + 1, CELSIUS,
maxTempInCelsius,
@@ -3282,9 +3534,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInFahrenheit + 1,
FAHRENHEIT, 0, 0},
@@ -3293,9 +3543,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {maxTempInFahrenheit + 1,
FAHRENHEIT, maxTempInCelsius,
@@ -3308,9 +3556,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInCelsius +
incrementInCelsius * 2.5f,
@@ -3320,9 +3566,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues =
{minTempInCelsius + incrementInCelsius * 2.5f,
@@ -3338,9 +3582,7 @@
.valuesToSet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues = {minTempInFahrenheit +
incrementInFahrenheit *
@@ -3351,9 +3593,7 @@
.expectedValuesToGet =
{
VehiclePropValue{
- .prop = toInt(
- VehicleProperty::
- HVAC_TEMPERATURE_VALUE_SUGGESTION),
+ .prop = propHvacTempValueSuggest,
.areaId = HVAC_ALL,
.value.floatValues =
{minTempInFahrenheit +
diff --git a/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp b/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
index 6b789bb..491aa10 100644
--- a/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
+++ b/automotive/vehicle/aidl/impl/grpc/utils/proto_message_converter/src/ProtoMessageConverter.cpp
@@ -78,6 +78,7 @@
protoACfg->add_supported_enum_values(supportedEnumValue);
}
}
+ protoACfg->set_support_variable_update_rate(areaConfig.supportVariableUpdateRate);
}
}
@@ -100,9 +101,14 @@
.maxInt64Value = protoAcfg.max_int64_value(),
.minFloatValue = protoAcfg.min_float_value(),
.maxFloatValue = protoAcfg.max_float_value(),
+ .supportVariableUpdateRate = protoAcfg.support_variable_update_rate(),
};
- COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoAcfg, supported_enum_values, (&vehicleAreaConfig),
- supportedEnumValues.value());
+ if (protoAcfg.supported_enum_values().size() != 0) {
+ vehicleAreaConfig.supportedEnumValues = std::vector<int64_t>();
+ COPY_PROTOBUF_VEC_TO_VHAL_TYPE(protoAcfg, supported_enum_values, (&vehicleAreaConfig),
+ supportedEnumValues.value());
+ }
+
return vehicleAreaConfig;
};
CAST_COPY_PROTOBUF_VEC_TO_VHAL_TYPE(in, area_configs, out, areaConfigs, cast_to_acfg);
diff --git a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
index e53947e..f49d91b 100644
--- a/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/hardware/include/IVehicleHardware.h
@@ -82,6 +82,117 @@
const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&
requests) const = 0;
+ // Dump debug information in the server.
+ virtual DumpResult dump(const std::vector<std::string>& options) = 0;
+
+ // Check whether the system is healthy, return {@code StatusCode::OK} for healthy.
+ virtual aidl::android::hardware::automotive::vehicle::StatusCode checkHealth() = 0;
+
+ // Register a callback that would be called when there is a property change event from vehicle.
+ // This function must only be called once during initialization.
+ virtual void registerOnPropertyChangeEvent(
+ std::unique_ptr<const PropertyChangeCallback> callback) = 0;
+
+ // Register a callback that would be called when there is a property set error event from
+ // vehicle. Must only be called once during initialization.
+ virtual void registerOnPropertySetErrorEvent(
+ std::unique_ptr<const PropertySetErrorCallback> callback) = 0;
+
+ // Gets the batching window used by DefaultVehicleHal for property change events.
+ //
+ // In DefaultVehicleHal, all the property change events generated within the batching window
+ // will be delivered through one callback to the VHAL client. This affects the maximum supported
+ // subscription rate. For example, if this returns 10ms, then only one callback for property
+ // change events will be called per 10ms, meaining that the max subscription rate for all
+ // continuous properties would be 100hz.
+ //
+ // A higher batching window means less callbacks to the VHAL client, causing a better
+ // performance. However, it also means a longer average latency for every property change
+ // events.
+ //
+ // 0 means no batching should be enabled in DefaultVehicleHal. In this case, batching can
+ // be optionally implemented in IVehicleHardware layer.
+ virtual std::chrono::nanoseconds getPropertyOnChangeEventBatchingWindow() {
+ // By default batching is disabled.
+ return std::chrono::nanoseconds(0);
+ }
+
+ // A [propId, areaId] is newly subscribed or the subscribe options are changed.
+ //
+ // The subscribe options contain sample rate in Hz or enable/disable variable update rate.
+ //
+ // For continuous properties:
+ //
+ // The sample rate is never 0 and indicates the desired polling rate for this property. The
+ // sample rate is guaranteed to be within supported {@code minSampleRate} and
+ // {@code maxSampleRate} as specified in {@code VehiclePropConfig}.
+ //
+ // If the specified sample rate is not supported, e.g. vehicle bus only supports 5hz and 10hz
+ // polling rate but the sample rate is 8hz, impl must choose the higher polling rate (10hz).
+ //
+ // Whether variable update rate is enabled is specified by {@code enableVariableUpdateRate} in
+ // {@code SubscribeOptions}. If variable update rate is not supported for the
+ // [propId, areaId], impl must ignore this option and always treat it as disabled.
+ //
+ // If variable update rate is disabled/not supported, impl must report all the property events
+ // for this [propId, areaId] through {@code propertyChangeCallback} according to the sample
+ // rate. E.g. a sample rate of 10hz must generate at least 10 property change events per second.
+ //
+ // If variable update rate is enabled AND supported, impl must only report property events
+ // when the [propId, areaId]'s value or status changes (a.k.a same as on-change property).
+ // The sample rate still guides the polling rate, but duplicate property events must be dropped
+ // and not reported via {@code propertyChangeCallback}.
+ //
+ // Async property set error events are not affected by variable update rate and must always
+ // be reported.
+ //
+ // If the impl is always polling at {@code maxSampleRate} for all continuous [propId, areaId]s,
+ // and do not support variable update rate for any [propId, areaId], then this function can be a
+ // no-op.
+ //
+ // For on-change properties:
+ //
+ // The sample rate is always 0 and must be ignored. If the impl is always subscribing to all
+ // on-change properties, then this function can be no-op.
+ //
+ // For all properties:
+ //
+ // It is recommended to only deliver the subscribed property events to DefaultVehicleHal to
+ // improve performance. However, even if unsubscribed property events are delivered, they
+ // will be filtered out by DefaultVehicleHal.
+ //
+ // A subscription from VHAL client might not necessarily trigger this function.
+ // DefaultVehicleHal will aggregate all the subscriptions from all the clients and notify
+ // IVehicleHardware if new subscriptions are required or subscribe options are updated.
+ //
+ // For example:
+ // 1. VHAL initially have no subscriber for speed.
+ // 2. A new subscriber is subscribing speed for 10 times/s, 'subscribe' is called
+ // with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
+ // 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
+ // times/sec, 'subscribe' is not called.
+ // 4. The initial subscriber is removed, 'subscribe' is called with sampleRate as
+ // 5, because now it only needs to report event 5times/sec. The impl can now poll vehicle
+ // speed 5 times/s. If the impl is still polling at 10 times/s, that is okay as long as
+ // the polling rate is larger than 5times/s. DefaultVehicleHal would ignore the additional
+ // events.
+ // 5. The second subscriber is removed, 'unsubscribe' is called.
+ // The impl can optionally disable the polling for vehicle speed.
+ //
+ virtual aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+ [[maybe_unused]] aidl::android::hardware::automotive::vehicle::SubscribeOptions
+ options) {
+ return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+ }
+
+ // A [propId, areaId] is unsubscribed. This applies for both continuous or on-change property.
+ virtual aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(
+ [[maybe_unused]] int32_t propId, [[maybe_unused]] int32_t areaId) {
+ return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
+ }
+
+ // This function is deprecated, subscribe/unsubscribe should be used instead.
+ //
// Update the sampling rate for the specified property and the specified areaId (0 for global
// property) if server supports it. The property must be a continuous property.
// {@code sampleRate} means that for this specific property, the server must generate at least
@@ -91,7 +202,7 @@
// This would be called if sample rate is updated for a subscriber, a new subscriber is added
// or an existing subscriber is removed. For example:
// 1. We have no subscriber for speed.
- // 2. A new subscriber is subscribing speed for 10 times/s, updsateSampleRate would be called
+ // 2. A new subscriber is subscribing speed for 10 times/s, updateSampleRate would be called
// with sampleRate as 10. The impl is now polling vehicle speed from bus 10 times/s.
// 3. A new subscriber is subscribing speed for 5 times/s, because it is less than 10
// times/sec, updateSampleRate would not be called.
@@ -110,22 +221,6 @@
[[maybe_unused]] float sampleRate) {
return aidl::android::hardware::automotive::vehicle::StatusCode::OK;
}
-
- // Dump debug information in the server.
- virtual DumpResult dump(const std::vector<std::string>& options) = 0;
-
- // Check whether the system is healthy, return {@code StatusCode::OK} for healthy.
- virtual aidl::android::hardware::automotive::vehicle::StatusCode checkHealth() = 0;
-
- // Register a callback that would be called when there is a property change event from vehicle.
- // Must only be called once during initialization.
- virtual void registerOnPropertyChangeEvent(
- std::unique_ptr<const PropertyChangeCallback> callback) = 0;
-
- // Register a callback that would be called when there is a property set error event from
- // vehicle. Must only be called once during initialization.
- virtual void registerOnPropertySetErrorEvent(
- std::unique_ptr<const PropertySetErrorCallback> callback) = 0;
};
} // namespace vehicle
diff --git a/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto b/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
index 04b7dd4..8093658 100644
--- a/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
+++ b/automotive/vehicle/aidl/impl/proto/android/hardware/automotive/vehicle/VehicleAreaConfig.proto
@@ -44,4 +44,5 @@
*/
repeated int64 supported_enum_values = 8;
int32 access = 9;
+ bool support_variable_update_rate = 10;
};
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h b/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
index 327c0dc..b636aa3 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/ConcurrentQueue.h
@@ -69,6 +69,19 @@
mCond.notify_one();
}
+ void push(std::vector<T>&& items) {
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ if (!mIsActive) {
+ return;
+ }
+ for (T& item : items) {
+ mQueue.push(std::move(item));
+ }
+ }
+ mCond.notify_one();
+ }
+
// Deactivates the queue, thus no one can push items to it, also notifies all waiting thread.
// The items already in the queue could still be flushed even after the queue is deactivated.
void deactivate() {
@@ -92,6 +105,69 @@
std::queue<T> mQueue GUARDED_BY(mLock);
};
+template <typename T>
+class BatchingConsumer {
+ private:
+ enum class State {
+ INIT = 0,
+ RUNNING = 1,
+ STOP_REQUESTED = 2,
+ STOPPED = 3,
+ };
+
+ public:
+ BatchingConsumer() : mState(State::INIT) {}
+
+ BatchingConsumer(const BatchingConsumer&) = delete;
+ BatchingConsumer& operator=(const BatchingConsumer&) = delete;
+
+ using OnBatchReceivedFunc = std::function<void(std::vector<T> vec)>;
+
+ void run(ConcurrentQueue<T>* queue, std::chrono::nanoseconds batchInterval,
+ const OnBatchReceivedFunc& func) {
+ mQueue = queue;
+ mBatchInterval = batchInterval;
+
+ mWorkerThread = std::thread(&BatchingConsumer<T>::runInternal, this, func);
+ }
+
+ void requestStop() { mState = State::STOP_REQUESTED; }
+
+ void waitStopped() {
+ if (mWorkerThread.joinable()) {
+ mWorkerThread.join();
+ }
+ }
+
+ private:
+ void runInternal(const OnBatchReceivedFunc& onBatchReceived) {
+ if (mState.exchange(State::RUNNING) == State::INIT) {
+ while (State::RUNNING == mState) {
+ mQueue->waitForItems();
+ if (State::STOP_REQUESTED == mState) break;
+
+ std::this_thread::sleep_for(mBatchInterval);
+ if (State::STOP_REQUESTED == mState) break;
+
+ std::vector<T> items = mQueue->flush();
+
+ if (items.size() > 0) {
+ onBatchReceived(std::move(items));
+ }
+ }
+ }
+
+ mState = State::STOPPED;
+ }
+
+ private:
+ std::thread mWorkerThread;
+
+ std::atomic<State> mState;
+ std::chrono::nanoseconds mBatchInterval;
+ ConcurrentQueue<T>* mQueue;
+};
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
index 3d25cd3..b74dff5 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehiclePropertyStore.h
@@ -92,7 +92,7 @@
// used as the key.
void registerProperty(
const aidl::android::hardware::automotive::vehicle::VehiclePropConfig& config,
- TokenFunction tokenFunc = nullptr);
+ TokenFunction tokenFunc = nullptr) EXCLUDES(mLock);
// Stores provided value. Returns error if config wasn't registered. If 'updateStatus' is
// true, the 'status' in 'propValue' would be stored. Otherwise, if this is a new value,
@@ -102,44 +102,47 @@
// 'EventMode' controls whether the 'OnValueChangeCallback' will be called for this operation.
VhalResult<void> writeValue(VehiclePropValuePool::RecyclableType propValue,
bool updateStatus = false,
- EventMode mode = EventMode::ON_VALUE_CHANGE);
+ EventMode mode = EventMode::ON_VALUE_CHANGE) EXCLUDES(mLock);
// Remove a given property value from the property store. The 'propValue' would be used to
// generate the key for the value to remove.
void removeValue(
- const aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue);
+ const aidl::android::hardware::automotive::vehicle::VehiclePropValue& propValue)
+ EXCLUDES(mLock);
// Remove all the values for the property.
- void removeValuesForProperty(int32_t propId);
+ void removeValuesForProperty(int32_t propId) EXCLUDES(mLock);
// Read all the stored values.
- std::vector<VehiclePropValuePool::RecyclableType> readAllValues() const;
+ std::vector<VehiclePropValuePool::RecyclableType> readAllValues() const EXCLUDES(mLock);
// Read all the values for the property.
- ValuesResultType readValuesForProperty(int32_t propId) const;
+ ValuesResultType readValuesForProperty(int32_t propId) const EXCLUDES(mLock);
// Read the value for the requested property. Returns {@code StatusCode::NOT_AVAILABLE} if the
// value has not been set yet. Returns {@code StatusCode::INVALID_ARG} if the property is
// not configured.
ValueResultType readValue(
- const aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const;
+ const aidl::android::hardware::automotive::vehicle::VehiclePropValue& request) const
+ EXCLUDES(mLock);
// Read the value for the requested property. Returns {@code StatusCode::NOT_AVAILABLE} if the
// value has not been set yet. Returns {@code StatusCode::INVALID_ARG} if the property is
// not configured.
- ValueResultType readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const;
+ ValueResultType readValue(int32_t prop, int32_t area = 0, int64_t token = 0) const
+ EXCLUDES(mLock);
// Get all property configs.
std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropConfig> getAllConfigs()
- const;
+ const EXCLUDES(mLock);
// Get the property config for the requested property.
android::base::Result<const aidl::android::hardware::automotive::vehicle::VehiclePropConfig*,
VhalError>
- getConfig(int32_t propId) const;
+ getConfig(int32_t propId) const EXCLUDES(mLock);
// Set a callback that would be called when a property value has been updated.
- void setOnValueChangeCallback(const OnValueChangeCallback& callback);
+ void setOnValueChangeCallback(const OnValueChangeCallback& callback) EXCLUDES(mLock);
inline std::shared_ptr<VehiclePropValuePool> getValuePool() { return mValuePool; }
diff --git a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
index c94bad6..546421e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
+++ b/automotive/vehicle/aidl/impl/utils/common/include/VehicleUtils.h
@@ -329,6 +329,11 @@
}
};
+inline std::string propIdToString(int32_t propId) {
+ return toString(
+ static_cast<aidl::android::hardware::automotive::vehicle::VehicleProperty>(propId));
+}
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
index 646dc0e..3fd2aa8 100644
--- a/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/src/VehiclePropertyStore.cpp
@@ -108,51 +108,62 @@
VhalResult<void> VehiclePropertyStore::writeValue(VehiclePropValuePool::RecyclableType propValue,
bool updateStatus,
VehiclePropertyStore::EventMode eventMode) {
- std::scoped_lock<std::mutex> g(mLock);
-
- int32_t propId = propValue->prop;
-
- VehiclePropertyStore::Record* record = getRecordLocked(propId);
- if (record == nullptr) {
- return StatusError(StatusCode::INVALID_ARG) << "property: " << propId << " not registered";
- }
-
- if (!isGlobalProp(propId) && getAreaConfig(*propValue, record->propConfig) == nullptr) {
- return StatusError(StatusCode::INVALID_ARG)
- << "no config for property: " << propId << " area: " << propValue->areaId;
- }
-
- VehiclePropertyStore::RecordId recId = getRecordIdLocked(*propValue, *record);
bool valueUpdated = true;
- if (auto it = record->values.find(recId); it != record->values.end()) {
- const VehiclePropValue* valueToUpdate = it->second.get();
- int64_t oldTimestamp = valueToUpdate->timestamp;
- VehiclePropertyStatus oldStatus = valueToUpdate->status;
- // propValue is outdated and drops it.
- if (oldTimestamp > propValue->timestamp) {
+ VehiclePropValue updatedValue;
+ OnValueChangeCallback onValueChangeCallback = nullptr;
+ {
+ std::scoped_lock<std::mutex> g(mLock);
+
+ int32_t propId = propValue->prop;
+
+ VehiclePropertyStore::Record* record = getRecordLocked(propId);
+ if (record == nullptr) {
return StatusError(StatusCode::INVALID_ARG)
- << "outdated timestamp: " << propValue->timestamp;
- }
- if (!updateStatus) {
- propValue->status = oldStatus;
+ << "property: " << propId << " not registered";
}
- valueUpdated = (valueToUpdate->value != propValue->value ||
- valueToUpdate->status != propValue->status ||
- valueToUpdate->prop != propValue->prop ||
- valueToUpdate->areaId != propValue->areaId);
- } else if (!updateStatus) {
- propValue->status = VehiclePropertyStatus::AVAILABLE;
+ if (!isGlobalProp(propId) && getAreaConfig(*propValue, record->propConfig) == nullptr) {
+ return StatusError(StatusCode::INVALID_ARG)
+ << "no config for property: " << propId << " area ID: " << propValue->areaId;
+ }
+
+ VehiclePropertyStore::RecordId recId = getRecordIdLocked(*propValue, *record);
+ if (auto it = record->values.find(recId); it != record->values.end()) {
+ const VehiclePropValue* valueToUpdate = it->second.get();
+ int64_t oldTimestampNanos = valueToUpdate->timestamp;
+ VehiclePropertyStatus oldStatus = valueToUpdate->status;
+ // propValue is outdated and drops it.
+ if (oldTimestampNanos > propValue->timestamp) {
+ return StatusError(StatusCode::INVALID_ARG)
+ << "outdated timestampNanos: " << propValue->timestamp;
+ }
+ if (!updateStatus) {
+ propValue->status = oldStatus;
+ }
+
+ valueUpdated = (valueToUpdate->value != propValue->value ||
+ valueToUpdate->status != propValue->status ||
+ valueToUpdate->prop != propValue->prop ||
+ valueToUpdate->areaId != propValue->areaId);
+ } else if (!updateStatus) {
+ propValue->status = VehiclePropertyStatus::AVAILABLE;
+ }
+
+ record->values[recId] = std::move(propValue);
+
+ if (eventMode == EventMode::NEVER) {
+ return {};
+ }
+ updatedValue = *(record->values[recId]);
+ if (mOnValueChangeCallback == nullptr) {
+ return {};
+ }
+ onValueChangeCallback = mOnValueChangeCallback;
}
- record->values[recId] = std::move(propValue);
-
- if (eventMode == EventMode::NEVER) {
- return {};
- }
-
- if ((eventMode == EventMode::ALWAYS || valueUpdated) && mOnValueChangeCallback != nullptr) {
- mOnValueChangeCallback(*(record->values[recId]));
+ // Invoke the callback outside the lock to prevent dead-lock.
+ if (eventMode == EventMode::ALWAYS || valueUpdated) {
+ onValueChangeCallback(updatedValue);
}
return {};
}
diff --git a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
index fea5034..625652e 100644
--- a/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
+++ b/automotive/vehicle/aidl/impl/utils/common/test/VehiclePropertyStoreTest.cpp
@@ -509,6 +509,24 @@
ASSERT_EQ(updatedValue.prop, INVALID_PROP_ID);
}
+TEST_F(VehiclePropertyStoreTest, testPropertyChangeCallbackUseVehiclePropertyStore_noDeadLock) {
+ VehiclePropValue fuelCapacity = {
+ .prop = toInt(VehicleProperty::INFO_FUEL_CAPACITY),
+ .value = {.floatValues = {1.0}},
+ };
+
+ std::vector<VehiclePropConfig> configs;
+
+ mStore->setOnValueChangeCallback(
+ [this, &configs]([[maybe_unused]] const VehiclePropValue& value) {
+ configs = mStore->getAllConfigs();
+ });
+
+ ASSERT_RESULT_OK(mStore->writeValue(mValuePool->obtain(fuelCapacity), /*updateStatus=*/true,
+ VehiclePropertyStore::EventMode::ALWAYS));
+ ASSERT_EQ(configs.size(), static_cast<size_t>(2));
+}
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
index 419e16b..9a42180 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/DefaultVehicleHal.h
@@ -144,6 +144,15 @@
std::shared_ptr<PendingRequestPool> mPendingRequestPool;
// SubscriptionManager is thread-safe.
std::shared_ptr<SubscriptionManager> mSubscriptionManager;
+ // ConcurrentQueue is thread-safe.
+ std::shared_ptr<ConcurrentQueue<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
+ mBatchedEventQueue;
+ // BatchingConsumer is thread-safe.
+ std::shared_ptr<
+ BatchingConsumer<aidl::android::hardware::automotive::vehicle::VehiclePropValue>>
+ mPropertyChangeEventsBatchingConsumer;
+ // Only set once during initialization.
+ std::chrono::nanoseconds mEventBatchingWindow;
std::mutex mLock;
std::unordered_map<const AIBinder*, std::unique_ptr<OnBinderDiedContext>> mOnBinderDiedContexts
@@ -209,6 +218,19 @@
size_t countSubscribeClients();
+ // Handles the property change events in batch.
+ void handleBatchedPropertyEvents(
+ std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
+ batchedEvents);
+
+ // Puts the property change events into a queue so that they can handled in batch.
+ static void batchPropertyChangeEvent(
+ const std::weak_ptr<ConcurrentQueue<
+ aidl::android::hardware::automotive::vehicle::VehiclePropValue>>&
+ batchedEventQueue,
+ std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
+ updatedValues);
+
// Gets or creates a {@code T} object for the client to or from {@code clients}.
template <class T>
static std::shared_ptr<T> getOrCreateClient(
@@ -217,7 +239,7 @@
static void onPropertyChangeEvent(
const std::weak_ptr<SubscriptionManager>& subscriptionManager,
- const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
+ std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&&
updatedValues);
static void onPropertySetErrorEvent(
diff --git a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
index 512d906..5053c96 100644
--- a/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
+++ b/automotive/vehicle/aidl/impl/vhal/include/SubscriptionManager.h
@@ -36,20 +36,29 @@
namespace automotive {
namespace vehicle {
+// A structure to represent subscription config for one subscription client.
+struct SubConfig {
+ float sampleRateHz;
+ bool enableVur;
+};
+
// A class to represent all the subscription configs for a continuous [propId, areaId].
class ContSubConfigs final {
public:
using ClientIdType = const AIBinder*;
- void addClient(const ClientIdType& clientId, float sampleRateHz);
+ void addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur);
void removeClient(const ClientIdType& clientId);
float getMaxSampleRateHz() const;
+ bool isVurEnabled() const;
+ bool isVurEnabledForClient(const ClientIdType& clientId);
private:
float mMaxSampleRateHz = 0.;
- std::unordered_map<ClientIdType, float> mSampleRateHzByClient;
+ bool mEnableVur;
+ std::unordered_map<ClientIdType, SubConfig> mConfigByClient;
- void refreshMaxSampleRateHz();
+ void refreshCombinedConfig();
};
// A thread-safe subscription manager that manages all VHAL subscriptions.
@@ -58,6 +67,7 @@
using ClientIdType = const AIBinder*;
using CallbackType =
std::shared_ptr<aidl::android::hardware::automotive::vehicle::IVehicleCallback>;
+ using VehiclePropValue = aidl::android::hardware::automotive::vehicle::VehiclePropValue;
explicit SubscriptionManager(IVehicleHardware* vehicleHardware);
~SubscriptionManager();
@@ -92,12 +102,8 @@
// For a list of updated properties, returns a map that maps clients subscribing to
// the updated properties to a list of updated values. This would only return on-change property
// clients that should be informed for the given updated values.
- std::unordered_map<
- CallbackType,
- std::vector<const aidl::android::hardware::automotive::vehicle::VehiclePropValue*>>
- getSubscribedClients(
- const std::vector<aidl::android::hardware::automotive::vehicle::VehiclePropValue>&
- updatedValues);
+ std::unordered_map<CallbackType, std::vector<VehiclePropValue>> getSubscribedClients(
+ std::vector<VehiclePropValue>&& updatedValues);
// For a list of set property error events, returns a map that maps clients subscribing to the
// properties to a list of errors for each client.
@@ -117,28 +123,60 @@
IVehicleHardware* mVehicleHardware;
+ struct VehiclePropValueHashPropIdAreaId {
+ inline size_t operator()(const VehiclePropValue& vehiclePropValue) const {
+ size_t res = 0;
+ hashCombine(res, vehiclePropValue.prop);
+ hashCombine(res, vehiclePropValue.areaId);
+ return res;
+ }
+ };
+
+ struct VehiclePropValueEqualPropIdAreaId {
+ inline bool operator()(const VehiclePropValue& left, const VehiclePropValue& right) const {
+ return left.prop == right.prop && left.areaId == right.areaId;
+ }
+ };
+
mutable std::mutex mLock;
std::unordered_map<PropIdAreaId, std::unordered_map<ClientIdType, CallbackType>,
PropIdAreaIdHash>
- mClientsByPropIdArea GUARDED_BY(mLock);
+ mClientsByPropIdAreaId GUARDED_BY(mLock);
std::unordered_map<ClientIdType, std::unordered_set<PropIdAreaId, PropIdAreaIdHash>>
mSubscribedPropsByClient GUARDED_BY(mLock);
std::unordered_map<PropIdAreaId, ContSubConfigs, PropIdAreaIdHash> mContSubConfigsByPropIdArea
GUARDED_BY(mLock);
+ std::unordered_map<CallbackType,
+ std::unordered_set<VehiclePropValue, VehiclePropValueHashPropIdAreaId,
+ VehiclePropValueEqualPropIdAreaId>>
+ mContSubValuesByCallback GUARDED_BY(mLock);
VhalResult<void> addContinuousSubscriberLocked(const ClientIdType& clientId,
const PropIdAreaId& propIdAreaId,
- float sampleRateHz) REQUIRES(mLock);
+ float sampleRateHz, bool enableVur)
+ REQUIRES(mLock);
+ VhalResult<void> addOnChangeSubscriberLocked(const PropIdAreaId& propIdAreaId) REQUIRES(mLock);
+ // Removes the subscription client for the continuous [propId, areaId].
VhalResult<void> removeContinuousSubscriberLocked(const ClientIdType& clientId,
const PropIdAreaId& propIdAreaId)
REQUIRES(mLock);
+ // Removes one subscription client for the on-change [propId, areaId].
+ VhalResult<void> removeOnChangeSubscriberLocked(const PropIdAreaId& propIdAreaId)
+ REQUIRES(mLock);
- VhalResult<void> updateContSubConfigs(const PropIdAreaId& PropIdAreaId,
- const ContSubConfigs& newConfig) REQUIRES(mLock);
+ VhalResult<void> updateContSubConfigsLocked(const PropIdAreaId& PropIdAreaId,
+ const ContSubConfigs& newConfig) REQUIRES(mLock);
+
+ VhalResult<void> unsubscribePropIdAreaIdLocked(SubscriptionManager::ClientIdType clientId,
+ const PropIdAreaId& propIdAreaId)
+ REQUIRES(mLock);
// Checks whether the manager is empty. For testing purpose.
bool isEmpty();
+ bool isValueUpdatedLocked(const CallbackType& callback, const VehiclePropValue& value)
+ REQUIRES(mLock);
+
// Get the interval in nanoseconds accroding to sample rate.
static android::base::Result<int64_t> getIntervalNanos(float sampleRateHz);
};
diff --git a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
index f3eba2e..d85cc09 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/DefaultVehicleHal.cpp
@@ -32,6 +32,7 @@
#include <utils/Trace.h>
#include <inttypes.h>
+#include <chrono>
#include <set>
#include <unordered_set>
@@ -101,12 +102,32 @@
IVehicleHardware* vehicleHardwarePtr = mVehicleHardware.get();
mSubscriptionManager = std::make_shared<SubscriptionManager>(vehicleHardwarePtr);
+ mEventBatchingWindow = mVehicleHardware->getPropertyOnChangeEventBatchingWindow();
+ if (mEventBatchingWindow != std::chrono::nanoseconds(0)) {
+ mBatchedEventQueue = std::make_shared<ConcurrentQueue<VehiclePropValue>>();
+ mPropertyChangeEventsBatchingConsumer =
+ std::make_shared<BatchingConsumer<VehiclePropValue>>();
+ mPropertyChangeEventsBatchingConsumer->run(
+ mBatchedEventQueue.get(), mEventBatchingWindow,
+ [this](std::vector<VehiclePropValue> batchedEvents) {
+ handleBatchedPropertyEvents(std::move(batchedEvents));
+ });
+ }
+ std::weak_ptr<ConcurrentQueue<VehiclePropValue>> batchedEventQueueCopy = mBatchedEventQueue;
+ std::chrono::nanoseconds eventBatchingWindow = mEventBatchingWindow;
std::weak_ptr<SubscriptionManager> subscriptionManagerCopy = mSubscriptionManager;
mVehicleHardware->registerOnPropertyChangeEvent(
std::make_unique<IVehicleHardware::PropertyChangeCallback>(
- [subscriptionManagerCopy](std::vector<VehiclePropValue> updatedValues) {
- onPropertyChangeEvent(subscriptionManagerCopy, updatedValues);
+ [subscriptionManagerCopy, batchedEventQueueCopy,
+ eventBatchingWindow](std::vector<VehiclePropValue> updatedValues) {
+ if (eventBatchingWindow != std::chrono::nanoseconds(0)) {
+ batchPropertyChangeEvent(batchedEventQueueCopy,
+ std::move(updatedValues));
+ } else {
+ onPropertyChangeEvent(subscriptionManagerCopy,
+ std::move(updatedValues));
+ }
}));
mVehicleHardware->registerOnPropertySetErrorEvent(
std::make_unique<IVehicleHardware::PropertySetErrorCallback>(
@@ -139,26 +160,47 @@
// mRecurrentAction uses pointer to mVehicleHardware, so it has to be unregistered before
// mVehicleHardware.
mRecurrentTimer.unregisterTimerCallback(mRecurrentAction);
+
+ if (mBatchedEventQueue) {
+ // mPropertyChangeEventsBatchingConsumer uses mSubscriptionManager and mBatchedEventQueue.
+ mBatchedEventQueue->deactivate();
+ mPropertyChangeEventsBatchingConsumer->requestStop();
+ mPropertyChangeEventsBatchingConsumer->waitStopped();
+ mPropertyChangeEventsBatchingConsumer.reset();
+ mBatchedEventQueue.reset();
+ }
+
// mSubscriptionManager uses pointer to mVehicleHardware, so it has to be destroyed before
// mVehicleHardware.
mSubscriptionManager.reset();
mVehicleHardware.reset();
}
+void DefaultVehicleHal::batchPropertyChangeEvent(
+ const std::weak_ptr<ConcurrentQueue<VehiclePropValue>>& batchedEventQueue,
+ std::vector<VehiclePropValue>&& updatedValues) {
+ auto batchedEventQueueStrong = batchedEventQueue.lock();
+ if (batchedEventQueueStrong == nullptr) {
+ ALOGW("the batched property events queue is destroyed, DefaultVehicleHal is ending");
+ return;
+ }
+ batchedEventQueueStrong->push(std::move(updatedValues));
+}
+
+void DefaultVehicleHal::handleBatchedPropertyEvents(std::vector<VehiclePropValue>&& batchedEvents) {
+ onPropertyChangeEvent(mSubscriptionManager, std::move(batchedEvents));
+}
+
void DefaultVehicleHal::onPropertyChangeEvent(
const std::weak_ptr<SubscriptionManager>& subscriptionManager,
- const std::vector<VehiclePropValue>& updatedValues) {
+ std::vector<VehiclePropValue>&& updatedValues) {
auto manager = subscriptionManager.lock();
if (manager == nullptr) {
ALOGW("the SubscriptionManager is destroyed, DefaultVehicleHal is ending");
return;
}
- auto updatedValuesByClients = manager->getSubscribedClients(updatedValues);
- for (const auto& [callback, valuePtrs] : updatedValuesByClients) {
- std::vector<VehiclePropValue> values;
- for (const VehiclePropValue* valuePtr : valuePtrs) {
- values.push_back(*valuePtr);
- }
+ auto updatedValuesByClients = manager->getSubscribedClients(std::move(updatedValues));
+ for (auto& [callback, values] : updatedValuesByClients) {
SubscriptionClient::sendUpdatedValues(callback, std::move(values));
}
}
@@ -653,7 +695,39 @@
if (config.changeMode == VehiclePropertyChangeMode::CONTINUOUS) {
optionCopy.sampleRate = getDefaultSampleRateHz(
optionCopy.sampleRate, config.minSampleRate, config.maxSampleRate);
- continuousSubscriptions.push_back(std::move(optionCopy));
+ if (!optionCopy.enableVariableUpdateRate) {
+ continuousSubscriptions.push_back(std::move(optionCopy));
+ } else {
+ // If clients enables to VUR, we need to check whether VUR is supported for the
+ // specific [propId, areaId] and overwrite the option to disable if not supported.
+ std::vector<int32_t> areasVurEnabled;
+ std::vector<int32_t> areasVurDisabled;
+ for (int32_t areaId : optionCopy.areaIds) {
+ const VehicleAreaConfig* areaConfig = getAreaConfig(propId, areaId, config);
+ if (areaConfig == nullptr) {
+ areasVurDisabled.push_back(areaId);
+ continue;
+ }
+ if (!areaConfig->supportVariableUpdateRate) {
+ areasVurDisabled.push_back(areaId);
+ continue;
+ }
+ areasVurEnabled.push_back(areaId);
+ }
+ if (!areasVurEnabled.empty()) {
+ SubscribeOptions optionVurEnabled = optionCopy;
+ optionVurEnabled.areaIds = areasVurEnabled;
+ optionVurEnabled.enableVariableUpdateRate = true;
+ continuousSubscriptions.push_back(std::move(optionVurEnabled));
+ }
+
+ if (!areasVurDisabled.empty()) {
+ // We use optionCopy for areas with VUR disabled.
+ optionCopy.areaIds = areasVurDisabled;
+ optionCopy.enableVariableUpdateRate = false;
+ continuousSubscriptions.push_back(std::move(optionCopy));
+ }
+ }
} else {
onChangeSubscriptions.push_back(std::move(optionCopy));
}
@@ -742,12 +816,12 @@
return;
}
std::vector<VehiclePropValue> values = {{
- .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
.areaId = 0,
+ .prop = toInt(VehicleProperty::VHAL_HEARTBEAT),
.status = VehiclePropertyStatus::AVAILABLE,
.value.int64Values = {uptimeMillis()},
}};
- onPropertyChangeEvent(subscriptionManager, values);
+ onPropertyChangeEvent(subscriptionManager, std::move(values));
return;
}
diff --git a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
index 17683ae..29d81a7 100644
--- a/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/src/SubscriptionManager.cpp
@@ -16,6 +16,7 @@
#include "SubscriptionManager.h"
+#include <VehicleUtils.h>
#include <android-base/stringprintf.h>
#include <utils/Log.h>
#include <utils/SystemClock.h>
@@ -29,10 +30,6 @@
namespace {
-constexpr float ONE_SECOND_IN_NANO = 1'000'000'000.;
-
-} // namespace
-
using ::aidl::android::hardware::automotive::vehicle::IVehicleCallback;
using ::aidl::android::hardware::automotive::vehicle::StatusCode;
using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
@@ -43,13 +40,28 @@
using ::android::base::StringPrintf;
using ::ndk::ScopedAStatus;
+constexpr float ONE_SECOND_IN_NANOS = 1'000'000'000.;
+
+SubscribeOptions newSubscribeOptions(int32_t propId, int32_t areaId, float sampleRateHz,
+ bool enableVur) {
+ SubscribeOptions subscribedOptions;
+ subscribedOptions.propId = propId;
+ subscribedOptions.areaIds = {areaId};
+ subscribedOptions.sampleRate = sampleRateHz;
+ subscribedOptions.enableVariableUpdateRate = enableVur;
+
+ return subscribedOptions;
+}
+
+} // namespace
+
SubscriptionManager::SubscriptionManager(IVehicleHardware* vehicleHardware)
: mVehicleHardware(vehicleHardware) {}
SubscriptionManager::~SubscriptionManager() {
std::scoped_lock<std::mutex> lockGuard(mLock);
- mClientsByPropIdArea.clear();
+ mClientsByPropIdAreaId.clear();
mSubscribedPropsByClient.clear();
}
@@ -62,45 +74,83 @@
if (sampleRateHz <= 0) {
return Error() << "invalid sample rate, must be a positive number";
}
- if (sampleRateHz <= (ONE_SECOND_IN_NANO / static_cast<float>(INT64_MAX))) {
+ if (sampleRateHz <= (ONE_SECOND_IN_NANOS / static_cast<float>(INT64_MAX))) {
return Error() << "invalid sample rate: " << sampleRateHz << ", too small";
}
- intervalNanos = static_cast<int64_t>(ONE_SECOND_IN_NANO / sampleRateHz);
+ intervalNanos = static_cast<int64_t>(ONE_SECOND_IN_NANOS / sampleRateHz);
return intervalNanos;
}
-void ContSubConfigs::refreshMaxSampleRateHz() {
+void ContSubConfigs::refreshCombinedConfig() {
float maxSampleRateHz = 0.;
+ bool enableVur = true;
// This is not called frequently so a brute-focre is okay. More efficient way exists but this
// is simpler.
- for (const auto& [_, sampleRateHz] : mSampleRateHzByClient) {
- if (sampleRateHz > maxSampleRateHz) {
- maxSampleRateHz = sampleRateHz;
+ for (const auto& [_, subConfig] : mConfigByClient) {
+ if (subConfig.sampleRateHz > maxSampleRateHz) {
+ maxSampleRateHz = subConfig.sampleRateHz;
+ }
+ if (!subConfig.enableVur) {
+ // If one client does not enable variable update rate, we cannot enable variable update
+ // rate in IVehicleHardware.
+ enableVur = false;
}
}
mMaxSampleRateHz = maxSampleRateHz;
+ mEnableVur = enableVur;
}
-void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz) {
- mSampleRateHzByClient[clientId] = sampleRateHz;
- refreshMaxSampleRateHz();
+void ContSubConfigs::addClient(const ClientIdType& clientId, float sampleRateHz, bool enableVur) {
+ mConfigByClient[clientId] = {
+ .sampleRateHz = sampleRateHz,
+ .enableVur = enableVur,
+ };
+ refreshCombinedConfig();
}
void ContSubConfigs::removeClient(const ClientIdType& clientId) {
- mSampleRateHzByClient.erase(clientId);
- refreshMaxSampleRateHz();
+ mConfigByClient.erase(clientId);
+ refreshCombinedConfig();
}
float ContSubConfigs::getMaxSampleRateHz() const {
return mMaxSampleRateHz;
}
+bool ContSubConfigs::isVurEnabled() const {
+ return mEnableVur;
+}
+
+bool ContSubConfigs::isVurEnabledForClient(const ClientIdType& clientId) {
+ return mConfigByClient[clientId].enableVur;
+}
+
+VhalResult<void> SubscriptionManager::addOnChangeSubscriberLocked(
+ const PropIdAreaId& propIdAreaId) {
+ if (mClientsByPropIdAreaId.find(propIdAreaId) != mClientsByPropIdAreaId.end()) {
+ // This propId, areaId is already subscribed, ignore the request.
+ return {};
+ }
+
+ int32_t propId = propIdAreaId.propId;
+ int32_t areaId = propIdAreaId.areaId;
+ if (auto status = mVehicleHardware->subscribe(
+ newSubscribeOptions(propId, areaId, /*updateRateHz=*/0, /*enableVur*/ false));
+ status != StatusCode::OK) {
+ return StatusError(status)
+ << StringPrintf("failed subscribe for prop: %s, areaId: %" PRId32,
+ propIdToString(propId).c_str(), areaId);
+ }
+ return {};
+}
+
VhalResult<void> SubscriptionManager::addContinuousSubscriberLocked(
- const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz) {
+ const ClientIdType& clientId, const PropIdAreaId& propIdAreaId, float sampleRateHz,
+ bool enableVur) {
// Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
ContSubConfigs newConfig = mContSubConfigsByPropIdArea[propIdAreaId];
- newConfig.addClient(clientId, sampleRateHz);
- return updateContSubConfigs(propIdAreaId, newConfig);
+ newConfig.addClient(clientId, sampleRateHz, enableVur);
+ return updateContSubConfigsLocked(propIdAreaId, newConfig);
}
VhalResult<void> SubscriptionManager::removeContinuousSubscriberLocked(
@@ -108,25 +158,62 @@
// Make a copy so that we don't modify 'mContSubConfigsByPropIdArea' on failure cases.
ContSubConfigs newConfig = mContSubConfigsByPropIdArea[propIdAreaId];
newConfig.removeClient(clientId);
- return updateContSubConfigs(propIdAreaId, newConfig);
+ return updateContSubConfigsLocked(propIdAreaId, newConfig);
}
-VhalResult<void> SubscriptionManager::updateContSubConfigs(const PropIdAreaId& propIdAreaId,
- const ContSubConfigs& newConfig) {
- if (newConfig.getMaxSampleRateHz() ==
- mContSubConfigsByPropIdArea[propIdAreaId].getMaxSampleRateHz()) {
+VhalResult<void> SubscriptionManager::removeOnChangeSubscriberLocked(
+ const PropIdAreaId& propIdAreaId) {
+ if (mClientsByPropIdAreaId[propIdAreaId].size() > 1) {
+ // After unsubscribing this client, there is still client subscribed, so do nothing.
+ return {};
+ }
+
+ int32_t propId = propIdAreaId.propId;
+ int32_t areaId = propIdAreaId.areaId;
+ if (auto status = mVehicleHardware->unsubscribe(propId, areaId); status != StatusCode::OK) {
+ return StatusError(status)
+ << StringPrintf("failed unsubscribe for prop: %s, areaId: %" PRId32,
+ propIdToString(propId).c_str(), areaId);
+ }
+ return {};
+}
+
+VhalResult<void> SubscriptionManager::updateContSubConfigsLocked(const PropIdAreaId& propIdAreaId,
+ const ContSubConfigs& newConfig) {
+ const auto& oldConfig = mContSubConfigsByPropIdArea[propIdAreaId];
+ float newRateHz = newConfig.getMaxSampleRateHz();
+ float oldRateHz = oldConfig.getMaxSampleRateHz();
+ if (newRateHz == oldRateHz && newConfig.isVurEnabled() == oldConfig.isVurEnabled()) {
mContSubConfigsByPropIdArea[propIdAreaId] = newConfig;
return {};
}
- float newRateHz = newConfig.getMaxSampleRateHz();
int32_t propId = propIdAreaId.propId;
int32_t areaId = propIdAreaId.areaId;
- if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRateHz);
- status != StatusCode::OK) {
- return StatusError(status) << StringPrintf("failed to update sample rate for prop: %" PRId32
- ", area"
- ": %" PRId32 ", sample rate: %f HZ",
- propId, areaId, newRateHz);
+ if (newRateHz != oldRateHz) {
+ if (auto status = mVehicleHardware->updateSampleRate(propId, areaId, newRateHz);
+ status != StatusCode::OK) {
+ return StatusError(status)
+ << StringPrintf("failed to update sample rate for prop: %s, areaId: %" PRId32
+ ", sample rate: %f HZ",
+ propIdToString(propId).c_str(), areaId, newRateHz);
+ }
+ }
+ if (newRateHz != 0) {
+ if (auto status = mVehicleHardware->subscribe(
+ newSubscribeOptions(propId, areaId, newRateHz, newConfig.isVurEnabled()));
+ status != StatusCode::OK) {
+ return StatusError(status) << StringPrintf(
+ "failed subscribe for prop: %s, areaId"
+ ": %" PRId32 ", sample rate: %f HZ",
+ propIdToString(propId).c_str(), areaId, newRateHz);
+ }
+ } else {
+ if (auto status = mVehicleHardware->unsubscribe(propId, areaId); status != StatusCode::OK) {
+ return StatusError(status) << StringPrintf(
+ "failed unsubscribe for prop: %s, areaId"
+ ": %" PRId32,
+ propIdToString(propId).c_str(), areaId);
+ }
}
mContSubConfigsByPropIdArea[propIdAreaId] = newConfig;
return {};
@@ -163,21 +250,54 @@
.propId = propId,
.areaId = areaId,
};
+ VhalResult<void> result;
if (isContinuousProperty) {
- if (auto result = addContinuousSubscriberLocked(clientId, propIdAreaId,
- option.sampleRate);
- !result.ok()) {
- return result;
- }
+ result = addContinuousSubscriberLocked(clientId, propIdAreaId, option.sampleRate,
+ option.enableVariableUpdateRate);
+ } else {
+ result = addOnChangeSubscriberLocked(propIdAreaId);
+ }
+
+ if (!result.ok()) {
+ return result;
}
mSubscribedPropsByClient[clientId].insert(propIdAreaId);
- mClientsByPropIdArea[propIdAreaId][clientId] = callback;
+ mClientsByPropIdAreaId[propIdAreaId][clientId] = callback;
}
}
return {};
}
+VhalResult<void> SubscriptionManager::unsubscribePropIdAreaIdLocked(
+ SubscriptionManager::ClientIdType clientId, const PropIdAreaId& propIdAreaId) {
+ if (mContSubConfigsByPropIdArea.find(propIdAreaId) != mContSubConfigsByPropIdArea.end()) {
+ // This is a subscribed continuous property.
+ if (auto result = removeContinuousSubscriberLocked(clientId, propIdAreaId); !result.ok()) {
+ return result;
+ }
+ } else {
+ if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
+ ALOGW("Unsubscribe: The property: %s, areaId: %" PRId32
+ " was not previously subscribed, do nothing",
+ propIdToString(propIdAreaId.propId).c_str(), propIdAreaId.areaId);
+ return {};
+ }
+ // This is an on-change property.
+ if (auto result = removeOnChangeSubscriberLocked(propIdAreaId); !result.ok()) {
+ return result;
+ }
+ }
+
+ auto& clients = mClientsByPropIdAreaId[propIdAreaId];
+ clients.erase(clientId);
+ if (clients.empty()) {
+ mClientsByPropIdAreaId.erase(propIdAreaId);
+ mContSubConfigsByPropIdArea.erase(propIdAreaId);
+ }
+ return {};
+}
+
VhalResult<void> SubscriptionManager::unsubscribe(SubscriptionManager::ClientIdType clientId,
const std::vector<int32_t>& propIds) {
std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -186,39 +306,27 @@
return StatusError(StatusCode::INVALID_ARG)
<< "No property was subscribed for the callback";
}
- std::unordered_set<int32_t> subscribedPropIds;
- for (auto const& propIdAreaId : mSubscribedPropsByClient[clientId]) {
- subscribedPropIds.insert(propIdAreaId.propId);
- }
+ std::vector<PropIdAreaId> propIdAreaIdsToUnsubscribe;
+ std::unordered_set<int32_t> propIdSet;
for (int32_t propId : propIds) {
- if (subscribedPropIds.find(propId) == subscribedPropIds.end()) {
- return StatusError(StatusCode::INVALID_ARG)
- << "property ID: " << propId << " is not subscribed";
+ propIdSet.insert(propId);
+ }
+ auto& subscribedPropIdsAreaIds = mSubscribedPropsByClient[clientId];
+ for (const auto& propIdAreaId : subscribedPropIdsAreaIds) {
+ if (propIdSet.find(propIdAreaId.propId) != propIdSet.end()) {
+ propIdAreaIdsToUnsubscribe.push_back(propIdAreaId);
}
}
- auto& propIdAreaIds = mSubscribedPropsByClient[clientId];
- auto it = propIdAreaIds.begin();
- while (it != propIdAreaIds.end()) {
- int32_t propId = it->propId;
- if (std::find(propIds.begin(), propIds.end(), propId) != propIds.end()) {
- if (auto result = removeContinuousSubscriberLocked(clientId, *it); !result.ok()) {
- return result;
- }
-
- auto& clients = mClientsByPropIdArea[*it];
- clients.erase(clientId);
- if (clients.empty()) {
- mClientsByPropIdArea.erase(*it);
- mContSubConfigsByPropIdArea.erase(*it);
- }
- it = propIdAreaIds.erase(it);
- } else {
- it++;
+ for (const auto& propIdAreaId : propIdAreaIdsToUnsubscribe) {
+ if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
+ return result;
}
+ subscribedPropIdsAreaIds.erase(propIdAreaId);
}
- if (propIdAreaIds.empty()) {
+
+ if (subscribedPropIdsAreaIds.empty()) {
mSubscribedPropsByClient.erase(clientId);
}
return {};
@@ -233,38 +341,68 @@
auto& subscriptions = mSubscribedPropsByClient[clientId];
for (auto const& propIdAreaId : subscriptions) {
- if (auto result = removeContinuousSubscriberLocked(clientId, propIdAreaId); !result.ok()) {
+ if (auto result = unsubscribePropIdAreaIdLocked(clientId, propIdAreaId); !result.ok()) {
return result;
}
-
- auto& clients = mClientsByPropIdArea[propIdAreaId];
- clients.erase(clientId);
- if (clients.empty()) {
- mClientsByPropIdArea.erase(propIdAreaId);
- mContSubConfigsByPropIdArea.erase(propIdAreaId);
- }
}
mSubscribedPropsByClient.erase(clientId);
return {};
}
-std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<const VehiclePropValue*>>
-SubscriptionManager::getSubscribedClients(const std::vector<VehiclePropValue>& updatedValues) {
- std::scoped_lock<std::mutex> lockGuard(mLock);
- std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<const VehiclePropValue*>>
- clients;
+bool SubscriptionManager::isValueUpdatedLocked(const std::shared_ptr<IVehicleCallback>& callback,
+ const VehiclePropValue& value) {
+ const auto& it = mContSubValuesByCallback[callback].find(value);
+ if (it == mContSubValuesByCallback[callback].end()) {
+ mContSubValuesByCallback[callback].insert(value);
+ return true;
+ }
- for (const auto& value : updatedValues) {
+ if (it->timestamp > value.timestamp) {
+ ALOGE("The updated property value: %s is outdated, ignored", value.toString().c_str());
+ return false;
+ }
+
+ if (it->value == value.value && it->status == value.status) {
+ // Even though the property value is the same, we need to store the new property event to
+ // update the timestamp.
+ mContSubValuesByCallback[callback].insert(value);
+ ALOGD("The updated property value for propId: %" PRId32 ", areaId: %" PRId32
+ " has the "
+ "same value and status, ignored if VUR is enabled",
+ it->prop, it->areaId);
+ return false;
+ }
+
+ mContSubValuesByCallback[callback].insert(value);
+ return true;
+}
+
+std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>>
+SubscriptionManager::getSubscribedClients(std::vector<VehiclePropValue>&& updatedValues) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ std::unordered_map<std::shared_ptr<IVehicleCallback>, std::vector<VehiclePropValue>> clients;
+
+ for (auto& value : updatedValues) {
PropIdAreaId propIdAreaId{
.propId = value.prop,
.areaId = value.areaId,
};
- if (mClientsByPropIdArea.find(propIdAreaId) == mClientsByPropIdArea.end()) {
+ if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
continue;
}
- for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
- clients[client].push_back(&value);
+ for (const auto& [client, callback] : mClientsByPropIdAreaId[propIdAreaId]) {
+ auto& subConfigs = mContSubConfigsByPropIdArea[propIdAreaId];
+ // If client wants VUR (and VUR is supported as checked in DefaultVehicleHal), it is
+ // possible that VUR is not enabled in IVehicleHardware because another client does not
+ // enable VUR. We will implement VUR filtering here for the client that enables it.
+ if (subConfigs.isVurEnabledForClient(client) && !subConfigs.isVurEnabled()) {
+ if (isValueUpdatedLocked(callback, value)) {
+ clients[callback].push_back(value);
+ }
+ } else {
+ clients[callback].push_back(value);
+ }
}
}
return clients;
@@ -281,11 +419,11 @@
.propId = errorEvent.propId,
.areaId = errorEvent.areaId,
};
- if (mClientsByPropIdArea.find(propIdAreaId) == mClientsByPropIdArea.end()) {
+ if (mClientsByPropIdAreaId.find(propIdAreaId) == mClientsByPropIdAreaId.end()) {
continue;
}
- for (const auto& [_, client] : mClientsByPropIdArea[propIdAreaId]) {
+ for (const auto& [_, client] : mClientsByPropIdAreaId[propIdAreaId]) {
clients[client].push_back({
.propId = errorEvent.propId,
.areaId = errorEvent.areaId,
@@ -298,7 +436,7 @@
bool SubscriptionManager::isEmpty() {
std::scoped_lock<std::mutex> lockGuard(mLock);
- return mSubscribedPropsByClient.empty() && mClientsByPropIdArea.empty();
+ return mSubscribedPropsByClient.empty() && mClientsByPropIdAreaId.empty();
}
size_t SubscriptionManager::countClients() {
diff --git a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
index fb14373..7195d97 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/DefaultVehicleHalTest.cpp
@@ -98,12 +98,22 @@
constexpr int32_t READ_ONLY_PROP = 10006 + 0x10000000 + 0x01000000 + 0x00400000;
// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
constexpr int32_t WRITE_ONLY_PROP = 10007 + 0x10000000 + 0x01000000 + 0x00400000;
+// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32
+constexpr int32_t GLOBAL_CONTINUOUS_PROP_NO_VUR = 10008 + 0x10000000 + 0x01000000 + 0x00400000;
int32_t testInt32VecProp(size_t i) {
// VehiclePropertyGroup:SYSTEM,VehicleArea:GLOBAL,VehiclePropertyType:INT32_VEC
return static_cast<int32_t>(i) + 0x10000000 + 0x01000000 + 0x00410000;
}
+std::string toString(const std::vector<SubscribeOptions>& options) {
+ std::string optionsStr;
+ for (const auto& option : options) {
+ optionsStr += option.toString() + "\n";
+ }
+ return optionsStr;
+}
+
struct PropConfigCmp {
bool operator()(const VehiclePropConfig& a, const VehiclePropConfig& b) const {
return (a.prop < b.prop);
@@ -211,8 +221,9 @@
class DefaultVehicleHalTest : public testing::Test {
public:
- void SetUp() override {
- auto hardware = std::make_unique<MockVehicleHardware>();
+ void SetUp() override { init(std::make_unique<MockVehicleHardware>()); }
+
+ void init(std::unique_ptr<MockVehicleHardware> hardware) {
std::vector<VehiclePropConfig> testConfigs;
for (size_t i = 0; i < 10000; i++) {
testConfigs.push_back(VehiclePropConfig{
@@ -244,8 +255,18 @@
.changeMode = VehiclePropertyChangeMode::ON_CHANGE,
});
// A global continuous property.
+ testConfigs.push_back(VehiclePropConfig{.prop = GLOBAL_CONTINUOUS_PROP,
+ .access = VehiclePropertyAccess::READ_WRITE,
+ .changeMode = VehiclePropertyChangeMode::CONTINUOUS,
+ .minSampleRate = 0.0,
+ .maxSampleRate = 100.0,
+ .areaConfigs = {{
+ .areaId = 0,
+ .supportVariableUpdateRate = true,
+ }}});
+ // A global continuous property that does not support VUR.
testConfigs.push_back(VehiclePropConfig{
- .prop = GLOBAL_CONTINUOUS_PROP,
+ .prop = GLOBAL_CONTINUOUS_PROP_NO_VUR,
.access = VehiclePropertyAccess::READ_WRITE,
.changeMode = VehiclePropertyChangeMode::CONTINUOUS,
.minSampleRate = 0.0,
@@ -285,11 +306,13 @@
.areaId = toInt(VehicleAreaWindow::ROW_1_LEFT),
.minInt32Value = 0,
.maxInt32Value = 100,
+ .supportVariableUpdateRate = true,
},
{
.areaId = toInt(VehicleAreaWindow::ROW_1_RIGHT),
.minInt32Value = 0,
.maxInt32Value = 100,
+ .supportVariableUpdateRate = false,
},
},
});
@@ -1351,6 +1374,62 @@
EXPECT_EQ(countClients(), static_cast<size_t>(1));
}
+TEST_F(DefaultVehicleHalTest, testSubscribeContinuous_propNotSupportVur) {
+ std::vector<SubscribeOptions> options = {
+ {
+ .propId = GLOBAL_CONTINUOUS_PROP,
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = true,
+ },
+ {
+ .propId = GLOBAL_CONTINUOUS_PROP_NO_VUR,
+ .sampleRate = 30.0,
+ .enableVariableUpdateRate = true,
+ },
+ };
+
+ auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+ ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+ auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+ ASSERT_THAT(receivedSubscribeOptions, UnorderedElementsAre(
+ SubscribeOptions{
+ .propId = GLOBAL_CONTINUOUS_PROP,
+ .areaIds = {0},
+ .enableVariableUpdateRate = true,
+ .sampleRate = 20.0,
+ },
+ SubscribeOptions{
+ .propId = GLOBAL_CONTINUOUS_PROP_NO_VUR,
+ .areaIds = {0},
+ .enableVariableUpdateRate = false,
+ .sampleRate = 30.0,
+ }))
+ << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
+TEST_F(DefaultVehicleHalTest, testSubscribeContinuous_propSupportVurNotEnabled) {
+ std::vector<SubscribeOptions> options = {
+ {
+ .propId = GLOBAL_CONTINUOUS_PROP,
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = false,
+ },
+ };
+
+ auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+ ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+ auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+ ASSERT_THAT(receivedSubscribeOptions, UnorderedElementsAre(SubscribeOptions{
+ .propId = GLOBAL_CONTINUOUS_PROP,
+ .areaIds = {0},
+ .enableVariableUpdateRate = false,
+ .sampleRate = 20.0,
+ }))
+ << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
TEST_F(DefaultVehicleHalTest, testSubscribeAreaContinuous) {
std::vector<SubscribeOptions> options = {
{
@@ -1403,6 +1482,44 @@
ASSERT_GE(rightCount, static_cast<size_t>(5));
}
+TEST_F(DefaultVehicleHalTest, testAreaContinuous_areaNotSupportVur) {
+ std::vector<SubscribeOptions> options = {
+ {
+ .propId = AREA_CONTINUOUS_PROP,
+ .sampleRate = 20.0,
+ .areaIds = {toInt(VehicleAreaWindow::ROW_1_LEFT)},
+ .enableVariableUpdateRate = true,
+ },
+ {
+ .propId = AREA_CONTINUOUS_PROP,
+ .sampleRate = 10.0,
+ .areaIds = {toInt(VehicleAreaWindow::ROW_1_RIGHT)},
+ .enableVariableUpdateRate = true,
+ },
+ };
+
+ auto status = getClient()->subscribe(getCallbackClient(), options, 0);
+
+ ASSERT_TRUE(status.isOk()) << "subscribe failed: " << status.getMessage();
+ auto receivedSubscribeOptions = getHardware()->getSubscribeOptions();
+ ASSERT_THAT(receivedSubscribeOptions,
+ UnorderedElementsAre(
+ SubscribeOptions{
+ .propId = AREA_CONTINUOUS_PROP,
+ .sampleRate = 20.0,
+ .areaIds = {toInt(VehicleAreaWindow::ROW_1_LEFT)},
+ .enableVariableUpdateRate = true,
+ },
+ SubscribeOptions{
+ .propId = AREA_CONTINUOUS_PROP,
+ .sampleRate = 10.0,
+ .areaIds = {toInt(VehicleAreaWindow::ROW_1_RIGHT)},
+ // Area2 actually does not support VUR.
+ .enableVariableUpdateRate = false,
+ }))
+ << "received unexpected subscribe options: " << toString(receivedSubscribeOptions);
+}
+
TEST_F(DefaultVehicleHalTest, testUnsubscribeOnChange) {
std::vector<SubscribeOptions> options = {
{
@@ -1711,6 +1828,93 @@
ASSERT_THAT(vehiclePropErrors.payloads, UnorderedElementsAreArray(expectedResults));
}
+TEST_F(DefaultVehicleHalTest, testBatchOnPropertyChangeEvents) {
+ auto hardware = std::make_unique<MockVehicleHardware>();
+ hardware->setPropertyOnChangeEventBatchingWindow(std::chrono::milliseconds(10));
+ init(std::move(hardware));
+
+ std::vector<SubscribeOptions> options = {
+ {
+ .propId = GLOBAL_ON_CHANGE_PROP,
+ },
+ {
+ .propId = AREA_ON_CHANGE_PROP,
+ // No areaIds means subscribing to all area IDs.
+ .areaIds = {},
+ },
+ };
+
+ getClient()->subscribe(getCallbackClient(), options, 0);
+ VehiclePropValue testValue1 = {
+ .prop = GLOBAL_ON_CHANGE_PROP,
+ .value.int32Values = {0},
+ };
+ SetValueRequest request1 = {
+ .requestId = 1,
+ .value = testValue1,
+ };
+ SetValueResult result1 = {
+ .requestId = 1,
+ .status = StatusCode::OK,
+ };
+ VehiclePropValue testValue2 = {
+ .prop = AREA_ON_CHANGE_PROP,
+ .areaId = toInt(VehicleAreaWindow::ROW_1_LEFT),
+ .value.int32Values = {1},
+ };
+ SetValueRequest request2 = {
+ .requestId = 2,
+ .value = testValue2,
+ };
+ SetValueResult result2 = {
+ .requestId = 2,
+ .status = StatusCode::OK,
+ };
+ VehiclePropValue testValue3 = {
+ .prop = AREA_ON_CHANGE_PROP,
+ .areaId = toInt(VehicleAreaWindow::ROW_1_RIGHT),
+ .value.int32Values = {1},
+ };
+ SetValueRequest request3 = {
+ .requestId = 3,
+ .value = testValue3,
+ };
+ SetValueResult result3 = {
+ .requestId = 3,
+ .status = StatusCode::OK,
+ };
+ // Prepare the responses
+ for (int i = 0; i < 2; i++) {
+ getHardware()->addSetValueResponses({result1});
+ getHardware()->addSetValueResponses({result2, result3});
+ }
+
+ // Try to cause two batches, each with three on property change events.
+ // Set GLOBAL_ON_CHANGE_PROP causing one event.
+ // Set AREA_ON_CHANGE_PROP with two areas causing two events.
+ for (int i = 0; i < 2; i++) {
+ auto status = getClient()->setValues(getCallbackClient(),
+ SetValueRequests{.payloads = {request1}});
+ ASSERT_TRUE(status.isOk()) << "setValues failed: " << status.getMessage();
+
+ status = getClient()->setValues(getCallbackClient(),
+ SetValueRequests{.payloads = {request2, request3}});
+ ASSERT_TRUE(status.isOk()) << "setValues failed: " << status.getMessage();
+
+ ASSERT_TRUE(getCallback()->waitForOnPropertyEventResults(/*size=*/1,
+ /*timeoutInNano=*/1'000'000'000))
+ << "not received enough property change events before timeout";
+
+ auto maybeResults = getCallback()->nextOnPropertyEventResults();
+ ASSERT_TRUE(maybeResults.has_value()) << "no results in callback";
+ ASSERT_THAT(maybeResults.value().payloads,
+ UnorderedElementsAre(testValue1, testValue2, testValue3))
+ << "results mismatch, expect 3 batched on change events";
+ ASSERT_FALSE(getCallback()->nextOnPropertyEventResults().has_value())
+ << "more results than expected";
+ }
+}
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
index 54fede1..c272123 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.cpp
@@ -137,6 +137,14 @@
});
}
+bool MockVehicleCallback::waitForOnPropertyEventResults(size_t size, size_t timeoutInNano) {
+ std::unique_lock lk(mLock);
+ return mCond.wait_for(lk, std::chrono::nanoseconds(timeoutInNano), [this, size] {
+ ScopedLockAssertion lockAssertion(mLock);
+ return mOnPropertyEventResults.size() >= size;
+ });
+}
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
index 1545eae..672ff4f 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleCallback.h
@@ -69,6 +69,7 @@
size_t countOnPropertyEventResults();
bool waitForSetValueResults(size_t size, size_t timeoutInNano);
bool waitForGetValueResults(size_t size, size_t timeoutInNano);
+ bool waitForOnPropertyEventResults(size_t size, size_t timeoutInNano);
private:
std::mutex mLock;
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
index ba0d33d..db15c89 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.cpp
@@ -29,6 +29,7 @@
using ::aidl::android::hardware::automotive::vehicle::SetValueRequest;
using ::aidl::android::hardware::automotive::vehicle::SetValueResult;
using ::aidl::android::hardware::automotive::vehicle::StatusCode;
+using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropConfig;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
@@ -88,7 +89,40 @@
return StatusCode::OK;
}
-StatusCode MockVehicleHardware::updateSampleRate(int32_t propId, int32_t areaId, float sampleRate) {
+StatusCode MockVehicleHardware::subscribe(SubscribeOptions options) {
+ {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ mSubscribeOptions.push_back(options);
+ }
+ for (int32_t areaId : options.areaIds) {
+ if (auto status = subscribePropIdAreaId(options.propId, areaId, options.sampleRate);
+ status != StatusCode::OK) {
+ return status;
+ }
+ }
+ return StatusCode::OK;
+}
+
+std::vector<SubscribeOptions> MockVehicleHardware::getSubscribeOptions() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ return mSubscribeOptions;
+}
+
+void MockVehicleHardware::clearSubscribeOptions() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ mSubscribeOptions.clear();
+}
+
+StatusCode MockVehicleHardware::subscribePropIdAreaId(int32_t propId, int32_t areaId,
+ float sampleRateHz) {
+ if (sampleRateHz == 0) {
+ // on-change property.
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ mSubOnChangePropIdAreaIds.insert(std::pair<int32_t, int32_t>(propId, areaId));
+ return StatusCode::OK;
+ }
+
+ // continuous property.
std::shared_ptr<std::function<void()>> action;
{
@@ -97,9 +131,6 @@
// Remove the previous action register for this [propId, areaId].
mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propId][areaId]);
}
- if (sampleRate == 0) {
- return StatusCode::OK;
- }
// We are sure 'propertyChangeCallback' would be alive because we would unregister timer
// before destroying 'this' which owns mPropertyChangeCallback.
@@ -107,8 +138,8 @@
action = std::make_shared<std::function<void()>>([propertyChangeCallback, propId, areaId] {
std::vector<VehiclePropValue> values = {
{
- .prop = propId,
.areaId = areaId,
+ .prop = propId,
},
};
(*propertyChangeCallback)(values);
@@ -119,11 +150,45 @@
// In mock implementation, we generate a new property change event for this property at sample
// rate.
- int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRate);
+ int64_t interval = static_cast<int64_t>(1'000'000'000. / sampleRateHz);
mRecurrentTimer->registerTimerCallback(interval, action);
return StatusCode::OK;
}
+StatusCode MockVehicleHardware::unsubscribe(int32_t propId, int32_t areaId) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ // For on-change property.
+ mSubOnChangePropIdAreaIds.erase(std::make_pair(propId, areaId));
+ // for continuous property.
+ if (mRecurrentActions[propId][areaId] != nullptr) {
+ // Remove the previous action register for this [propId, areaId].
+ mRecurrentTimer->unregisterTimerCallback(mRecurrentActions[propId][areaId]);
+ mRecurrentActions[propId].erase(areaId);
+ if (mRecurrentActions[propId].empty()) {
+ mRecurrentActions.erase(propId);
+ }
+ }
+ return StatusCode::OK;
+}
+
+std::set<std::pair<int32_t, int32_t>> MockVehicleHardware::getSubscribedOnChangePropIdAreaIds() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ std::set<std::pair<int32_t, int32_t>> propIdAreaIds;
+ propIdAreaIds = mSubOnChangePropIdAreaIds;
+ return propIdAreaIds;
+}
+
+std::set<std::pair<int32_t, int32_t>> MockVehicleHardware::getSubscribedContinuousPropIdAreaIds() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ std::set<std::pair<int32_t, int32_t>> propIdAreaIds;
+ for (const auto& [propId, actionByAreaId] : mRecurrentActions) {
+ for (const auto& [areaId, _] : actionByAreaId) {
+ propIdAreaIds.insert(std::make_pair(propId, areaId));
+ }
+ }
+ return propIdAreaIds;
+}
+
void MockVehicleHardware::registerOnPropertyChangeEvent(
std::unique_ptr<const PropertyChangeCallback> callback) {
std::scoped_lock<std::mutex> lockGuard(mLock);
@@ -186,6 +251,16 @@
mSleepTime = timeInNano;
}
+void MockVehicleHardware::setPropertyOnChangeEventBatchingWindow(std::chrono::nanoseconds window) {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ mEventBatchingWindow = window;
+}
+
+std::chrono::nanoseconds MockVehicleHardware::getPropertyOnChangeEventBatchingWindow() {
+ std::scoped_lock<std::mutex> lockGuard(mLock);
+ return mEventBatchingWindow;
+}
+
template <class ResultType>
StatusCode MockVehicleHardware::returnResponse(
std::shared_ptr<const std::function<void(std::vector<ResultType>)>> callback,
diff --git a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
index 46b30b9..eeca582 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
+++ b/automotive/vehicle/aidl/impl/vhal/test/MockVehicleHardware.h
@@ -24,10 +24,12 @@
#include <android-base/thread_annotations.h>
#include <atomic>
+#include <chrono>
#include <condition_variable>
#include <list>
#include <memory>
#include <mutex>
+#include <set>
#include <thread>
#include <unordered_map>
#include <vector>
@@ -58,8 +60,11 @@
void registerOnPropertyChangeEvent(
std::unique_ptr<const PropertyChangeCallback> callback) override;
void registerOnPropertySetErrorEvent(std::unique_ptr<const PropertySetErrorCallback>) override;
- aidl::android::hardware::automotive::vehicle::StatusCode updateSampleRate(
- int32_t propId, int32_t areaId, float sampleRate) override;
+ aidl::android::hardware::automotive::vehicle::StatusCode subscribe(
+ aidl::android::hardware::automotive::vehicle::SubscribeOptions options) override;
+ aidl::android::hardware::automotive::vehicle::StatusCode unsubscribe(int32_t propId,
+ int32_t areaId) override;
+ std::chrono::nanoseconds getPropertyOnChangeEventBatchingWindow() override;
// Test functions.
void setPropertyConfigs(
@@ -86,6 +91,13 @@
void setSleepTime(int64_t timeInNano);
void setDumpResult(DumpResult result);
void sendOnPropertySetErrorEvent(const std::vector<SetValueErrorEvent>& errorEvents);
+ void setPropertyOnChangeEventBatchingWindow(std::chrono::nanoseconds window);
+
+ std::set<std::pair<int32_t, int32_t>> getSubscribedOnChangePropIdAreaIds();
+ std::set<std::pair<int32_t, int32_t>> getSubscribedContinuousPropIdAreaIds();
+ std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions>
+ getSubscribeOptions();
+ void clearSubscribeOptions();
private:
mutable std::mutex mLock;
@@ -110,6 +122,10 @@
std::shared_ptr<const GetValuesCallback>,
const std::vector<aidl::android::hardware::automotive::vehicle::GetValueRequest>&)>
mGetValueResponder GUARDED_BY(mLock);
+ std::chrono::nanoseconds mEventBatchingWindow GUARDED_BY(mLock) = std::chrono::nanoseconds(0);
+ std::set<std::pair<int32_t, int32_t>> mSubOnChangePropIdAreaIds GUARDED_BY(mLock);
+ std::vector<aidl::android::hardware::automotive::vehicle::SubscribeOptions> mSubscribeOptions
+ GUARDED_BY(mLock);
template <class ResultType>
aidl::android::hardware::automotive::vehicle::StatusCode returnResponse(
@@ -122,6 +138,8 @@
const std::vector<RequestType>& requests,
std::list<std::vector<RequestType>>* storedRequests,
std::list<std::vector<ResultType>>* storedResponses) const REQUIRES(mLock);
+ aidl::android::hardware::automotive::vehicle::StatusCode subscribePropIdAreaId(
+ int32_t propId, int32_t areaId, float sampleRateHz);
DumpResult mDumpResult;
diff --git a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
index cb8c8d1..aa5f003 100644
--- a/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
+++ b/automotive/vehicle/aidl/impl/vhal/test/SubscriptionManagerTest.cpp
@@ -43,12 +43,14 @@
using ::aidl::android::hardware::automotive::vehicle::SetValueResults;
using ::aidl::android::hardware::automotive::vehicle::SubscribeOptions;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropErrors;
+using ::aidl::android::hardware::automotive::vehicle::VehiclePropertyStatus;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValue;
using ::aidl::android::hardware::automotive::vehicle::VehiclePropValues;
using ::ndk::ScopedAStatus;
using ::ndk::SpAIBinder;
+using ::testing::Contains;
using ::testing::ElementsAre;
-using ::testing::WhenSorted;
+using ::testing::UnorderedElementsAre;
class PropertyCallback final : public BnVehicleCallback {
public:
@@ -114,6 +116,8 @@
void clearEvents() { return getCallback()->clearEvents(); }
+ std::shared_ptr<MockVehicleHardware> getHardware() { return mHardware; }
+
private:
std::unique_ptr<SubscriptionManager> mManager;
std::shared_ptr<PropertyCallback> mCallback;
@@ -132,6 +136,9 @@
auto result = getManager()->subscribe(getCallbackClient(), options, true);
ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+ ASSERT_THAT(getHardware()->getSubscribedContinuousPropIdAreaIds(),
+ UnorderedElementsAre(std::pair<int32_t, int32_t>(0, 0)));
+
std::this_thread::sleep_for(std::chrono::seconds(1));
// Theoretically trigger 10 times, but check for at least 9 times to be stable.
@@ -240,6 +247,8 @@
result = getManager()->unsubscribe(getCallbackClient()->asBinder().get());
ASSERT_TRUE(result.ok()) << "failed to unsubscribe: " << result.error().message();
+ ASSERT_EQ(getHardware()->getSubscribedContinuousPropIdAreaIds().size(), 0u);
+
// Wait for the last events to come.
std::this_thread::sleep_for(std::chrono::milliseconds(100));
@@ -316,7 +325,7 @@
EXPECT_TRUE(getEvents().empty());
}
-TEST_F(SubscriptionManagerTest, testUnsubscribeFailure) {
+TEST_F(SubscriptionManagerTest, testUnsubscribeUnsubscribedPropId) {
std::vector<SubscribeOptions> options = {
{
.propId = 0,
@@ -334,14 +343,21 @@
// Property ID: 2 was not subscribed.
result = getManager()->unsubscribe(getCallbackClient()->asBinder().get(),
std::vector<int32_t>({0, 1, 2}));
- ASSERT_FALSE(result.ok()) << "unsubscribe an unsubscribed property must fail";
+ ASSERT_TRUE(result.ok()) << "unsubscribe an unsubscribed property must do nothing";
- // Since property 0 and property 1 was not unsubscribed successfully, we should be able to
- // unsubscribe them again.
- result = getManager()->unsubscribe(getCallbackClient()->asBinder().get(),
- std::vector<int32_t>({0, 1}));
- ASSERT_TRUE(result.ok()) << "a failed unsubscription must not unsubscribe any properties"
- << result.error().message();
+ std::vector<VehiclePropValue> updatedValues = {
+ {
+ .prop = 0,
+ .areaId = 0,
+ },
+ {
+ .prop = 1,
+ .areaId = 0,
+ },
+ };
+ auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
+
+ ASSERT_EQ(clients.size(), 0u) << "all subscribed properties must be unsubscribed";
}
TEST_F(SubscriptionManagerTest, testSubscribeOnchange) {
@@ -370,6 +386,11 @@
ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
result = getManager()->subscribe(client2, options2, false);
ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+ ASSERT_THAT(getHardware()->getSubscribedOnChangePropIdAreaIds(),
+ UnorderedElementsAre(std::pair<int32_t, int32_t>(0, 0),
+ std::pair<int32_t, int32_t>(0, 1),
+ std::pair<int32_t, int32_t>(1, 0)));
+ ASSERT_EQ(getHardware()->getSubscribedContinuousPropIdAreaIds().size(), 0u);
std::vector<VehiclePropValue> updatedValues = {
{
@@ -389,11 +410,11 @@
.areaId = 1,
},
};
- auto clients = getManager()->getSubscribedClients(updatedValues);
+ auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
ASSERT_THAT(clients[client1],
- WhenSorted(ElementsAre(&updatedValues[0], &updatedValues[1], &updatedValues[2])));
- ASSERT_THAT(clients[client2], ElementsAre(&updatedValues[0]));
+ UnorderedElementsAre(updatedValues[0], updatedValues[1], updatedValues[2]));
+ ASSERT_THAT(clients[client2], ElementsAre(updatedValues[0]));
}
TEST_F(SubscriptionManagerTest, testSubscribeInvalidOption) {
@@ -480,9 +501,11 @@
.areaId = 0,
},
};
- auto clients = getManager()->getSubscribedClients(updatedValues);
+ auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>(updatedValues));
- ASSERT_THAT(clients[getCallbackClient()], ElementsAre(&updatedValues[1]));
+ ASSERT_THAT(clients[getCallbackClient()], ElementsAre(updatedValues[1]));
+ ASSERT_THAT(getHardware()->getSubscribedOnChangePropIdAreaIds(),
+ UnorderedElementsAre(std::pair<int32_t, int32_t>(1, 0)));
}
TEST_F(SubscriptionManagerTest, testCheckSampleRateHzValid) {
@@ -497,6 +520,257 @@
ASSERT_FALSE(SubscriptionManager::checkSampleRateHz(0));
}
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur) {
+ std::vector<SubscribeOptions> options = {{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = true,
+ }};
+
+ auto result = getManager()->subscribe(getCallbackClient(), options, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(options[0]));
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_VurStateChange) {
+ std::vector<SubscribeOptions> options = {{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = true,
+ }};
+
+ auto result = getManager()->subscribe(getCallbackClient(), options, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(options[0]));
+
+ getHardware()->clearSubscribeOptions();
+ result = getManager()->subscribe(getCallbackClient(), options, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_TRUE(getHardware()->getSubscribeOptions().empty());
+
+ std::vector<SubscribeOptions> newOptions = {{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = false,
+ }};
+ result = getManager()->subscribe(getCallbackClient(), newOptions, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(), ElementsAre(newOptions[0]));
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_filterUnchangedEvents) {
+ SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+ SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+ SubscribeOptions client1Option = {
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = false,
+ };
+ auto result = getManager()->subscribe(client1, {client1Option}, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(), UnorderedElementsAre(client1Option));
+
+ getHardware()->clearSubscribeOptions();
+ SubscribeOptions client2Option = {
+ .propId = 0,
+ .areaIds = {0, 1},
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = true,
+ };
+
+ result = getManager()->subscribe(client2, {client2Option}, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(),
+ UnorderedElementsAre(
+ SubscribeOptions{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 20.0,
+ // This is enabled for client2, but disabled for client1.
+ .enableVariableUpdateRate = false,
+ },
+ SubscribeOptions{
+ .propId = 0,
+ .areaIds = {1},
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = true,
+ }));
+
+ std::vector<VehiclePropValue> propertyEvents = {{
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .timestamp = 1,
+ },
+ {
+ .prop = 0,
+ .areaId = 1,
+ .value = {.int32Values = {1}},
+ .timestamp = 1,
+ }};
+ auto clients =
+ getManager()->getSubscribedClients(std::vector<VehiclePropValue>(propertyEvents));
+
+ ASSERT_THAT(clients[client1], UnorderedElementsAre(propertyEvents[0]));
+ ASSERT_THAT(clients[client2], UnorderedElementsAre(propertyEvents[0], propertyEvents[1]));
+
+ // If the same property events happen again with a new timestamp.
+ // VUR is disabled for client1, enabled for client2.
+ clients = getManager()->getSubscribedClients({{
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .timestamp = 2,
+ }});
+
+ ASSERT_FALSE(clients.find(client1) == clients.end())
+ << "Must not filter out property events if VUR is not enabled";
+ ASSERT_TRUE(clients.find(client2) == clients.end())
+ << "Must filter out property events if VUR is enabled";
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_mustNotFilterStatusChange) {
+ SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+ SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+ SubscribeOptions client1Option = {
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = false,
+ };
+ auto result = getManager()->subscribe(client1, {client1Option}, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(), UnorderedElementsAre(client1Option));
+
+ getHardware()->clearSubscribeOptions();
+ SubscribeOptions client2Option = {
+ .propId = 0,
+ .areaIds = {0, 1},
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = true,
+ };
+
+ result = getManager()->subscribe(client2, {client2Option}, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ ASSERT_THAT(getHardware()->getSubscribeOptions(),
+ UnorderedElementsAre(
+ SubscribeOptions{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 20.0,
+ // This is enabled for client2, but disabled for client1.
+ .enableVariableUpdateRate = false,
+ },
+ SubscribeOptions{
+ .propId = 0,
+ .areaIds = {1},
+ .sampleRate = 20.0,
+ .enableVariableUpdateRate = true,
+ }));
+
+ VehiclePropValue propValue1 = {
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .timestamp = 1,
+ };
+ auto clients = getManager()->getSubscribedClients(std::vector<VehiclePropValue>({propValue1}));
+
+ ASSERT_THAT(clients[client1], UnorderedElementsAre(propValue1));
+
+ // A new event with the same value, but different status must not be filtered out.
+ VehiclePropValue propValue2 = {
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .status = VehiclePropertyStatus::UNAVAILABLE,
+ .timestamp = 2,
+ };
+ clients = getManager()->getSubscribedClients({propValue2});
+
+ ASSERT_THAT(clients[client1], UnorderedElementsAre(propValue2))
+ << "Must not filter out property events that has status change";
+}
+
+TEST_F(SubscriptionManagerTest, testSubscribe_enableVur_timestampUpdated_filterOutdatedEvent) {
+ SpAIBinder binder1 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client1 = IVehicleCallback::fromBinder(binder1);
+ SpAIBinder binder2 = ndk::SharedRefBase::make<PropertyCallback>()->asBinder();
+ std::shared_ptr<IVehicleCallback> client2 = IVehicleCallback::fromBinder(binder2);
+ std::vector<SubscribeOptions> options = {{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = true,
+ }};
+
+ // client1 subscribe with VUR enabled.
+ auto result = getManager()->subscribe(client1, options, true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ // Let client2 subscribe with VUR disabled so that we enabled VUR in DefaultVehicleHal layer.
+ result = getManager()->subscribe(client2,
+ {{
+ .propId = 0,
+ .areaIds = {0},
+ .sampleRate = 10.0,
+ .enableVariableUpdateRate = false,
+ }},
+ true);
+ ASSERT_TRUE(result.ok()) << "failed to subscribe: " << result.error().message();
+
+ VehiclePropValue value0 = {
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .timestamp = 1,
+ };
+ auto clients = getManager()->getSubscribedClients({value0});
+
+ ASSERT_THAT(clients[client1], UnorderedElementsAre(value0));
+
+ // A new event with the same value arrived. This must update timestamp to 3.
+ VehiclePropValue value1 = {
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {0}},
+ .timestamp = 3,
+ };
+ clients = getManager()->getSubscribedClients({value1});
+
+ ASSERT_TRUE(clients.find(client1) == clients.end())
+ << "Must filter out duplicate property events if VUR is enabled";
+
+ // The latest timestamp is 3, so even though the value is not the same, this is outdated and
+ // must be ignored.
+ VehiclePropValue value2 = {
+ .prop = 0,
+ .areaId = 0,
+ .value = {.int32Values = {1}},
+ .timestamp = 2,
+ };
+ clients = getManager()->getSubscribedClients({value1});
+
+ ASSERT_TRUE(clients.find(client1) == clients.end())
+ << "Must filter out outdated property events if VUR is enabled";
+}
+
} // namespace vehicle
} // namespace automotive
} // namespace hardware
diff --git a/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl b/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
index 714d514..b4f6850 100644
--- a/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
+++ b/automotive/vehicle/aidl_property/aidl_api/android.hardware.automotive.vehicle.property/current/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
@@ -36,5 +36,6 @@
enum VehiclePropertyGroup {
SYSTEM = 0x10000000,
VENDOR = 0x20000000,
+ BACKPORTED = 0x30000000,
MASK = 0xf0000000,
}
diff --git a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
index a2cbdec..a417388 100644
--- a/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
+++ b/automotive/vehicle/aidl_property/android/hardware/automotive/vehicle/VehiclePropertyGroup.aidl
@@ -29,5 +29,34 @@
*/
VENDOR = 0x20000000,
+ /**
+ * Group reserved for backporting system properties introduced in a newer Android
+ * release to an older Android release.
+ *
+ * It is recommended to map the system property ID to a backported property ID by replacing the
+ * VehiclePropertyGroup, e.g. backported PERF_VEHICLE_SPEED(0x11600207) would be 0x31600207.
+ *
+ * When updated to a newer Android release where the property is defined as system properties,
+ * the backported properties must be migrated to system properties.
+ *
+ * In Android system, the backported property is treated the same as a vendor defined property
+ * with the same vendor permission model, a.k.a. Default required permission is
+ * `android.car.Car.PERMISSION_VENDOR_EXTENSION`, or customized by
+ * `SUPPORT_CUSTOMIZE_VENDOR_PERMISSION` VHAL property.
+ *
+ * Only applications with vendor permissions may access these backported properties.
+ *
+ * Vendors must also make sure this property's behavior is consistent with what is expected for
+ * the backported system property, e.g. the access mode, the change mode and the config array
+ * must be correct.
+ *
+ * When vendors define custom properties, they must use {@code VENDOR} flag, instead of
+ * {@code BACKPORTED}
+ */
+ BACKPORTED = 0x30000000,
+
+ /**
+ * The bit mask for {@code VehiclePropertyGroup}. This is not a group by itself.
+ */
MASK = 0xf0000000,
}
diff --git a/biometrics/fingerprint/aidl/default/Android.bp b/biometrics/fingerprint/aidl/default/Android.bp
index 3bb3f3a..a173a00 100644
--- a/biometrics/fingerprint/aidl/default/Android.bp
+++ b/biometrics/fingerprint/aidl/default/Android.bp
@@ -11,8 +11,6 @@
name: "android.hardware.biometrics.fingerprint-service.example",
vendor: true,
relative_install_path: "hw",
- init_rc: [":fingerprint-example.rc"],
- vintf_fragments: [":fingerprint-example.xml"],
local_include_dirs: ["include"],
srcs: [
"FakeLockoutTracker.cpp",
@@ -24,15 +22,21 @@
"Session.cpp",
"main.cpp",
],
+ stl: "c++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
+ "liblog",
+ ],
+ static_libs: [
+ "libandroid.hardware.biometrics.fingerprint.VirtualProps",
+ "libbase",
"android.hardware.biometrics.fingerprint-V3-ndk",
"android.hardware.biometrics.common-V3-ndk",
"android.hardware.biometrics.common.thread",
"android.hardware.biometrics.common.util",
+ "android.hardware.keymaster-V4-ndk",
],
- static_libs: ["libandroid.hardware.biometrics.fingerprint.VirtualProps"],
+ installable: false, // install APEX instead
}
cc_test {
@@ -143,12 +147,35 @@
vendor: true,
}
-filegroup {
+prebuilt_etc {
name: "fingerprint-example.rc",
- srcs: ["fingerprint-example.rc"],
+ src: "fingerprint-example.rc",
+ installable: false,
}
-filegroup {
+prebuilt_etc {
name: "fingerprint-example.xml",
- srcs: ["fingerprint-example.xml"],
+ src: "fingerprint-example.xml",
+ sub_dir: "vintf",
+ installable: false,
+}
+
+apex {
+ name: "com.android.hardware.biometrics.fingerprint.virtual",
+ manifest: "apex_manifest.json",
+ file_contexts: "apex_file_contexts",
+ key: "com.android.hardware.key",
+ certificate: ":com.android.hardware.certificate",
+ updatable: false,
+ vendor: true,
+
+ binaries: [
+ "android.hardware.biometrics.fingerprint-service.example",
+ ],
+ prebuilts: [
+ // init_rc
+ "fingerprint-example.rc",
+ // vintf_fragment
+ "fingerprint-example.xml",
+ ],
}
diff --git a/biometrics/fingerprint/aidl/default/README.md b/biometrics/fingerprint/aidl/default/README.md
index 823cd18..4b4533a 100644
--- a/biometrics/fingerprint/aidl/default/README.md
+++ b/biometrics/fingerprint/aidl/default/README.md
@@ -11,12 +11,6 @@
following to your device's `.mk` file to include it:
```
-PRODUCT_PACKAGES_DEBUG += android.hardware.biometrics.fingerprint-service.example
-```
-
-or add the following to include it as an apex:
-
-```
PRODUCT_PACKAGES_DEBUG += com.android.hardware.biometrics.fingerprint.virtual
```
diff --git a/biometrics/fingerprint/aidl/default/apex/Android.bp b/biometrics/fingerprint/aidl/default/apex/Android.bp
deleted file mode 100644
index 75d84a9..0000000
--- a/biometrics/fingerprint/aidl/default/apex/Android.bp
+++ /dev/null
@@ -1,61 +0,0 @@
-// Copyright (C) 2023 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-apex {
- name: "com.android.hardware.biometrics.fingerprint.virtual",
- manifest: "manifest.json",
- file_contexts: "file_contexts",
- key: "com.android.hardware.key",
- certificate: ":com.android.hardware.certificate",
- updatable: false,
- vendor: true,
-
- binaries: [
- "android.hardware.biometrics.fingerprint-service.example",
- ],
- prebuilts: [
- // init_rc
- "fingerprint-example-apex.rc",
- // vintf_fragment
- "fingerprint-example-apex.xml",
- ],
-
- overrides: [
- "android.hardware.biometrics.fingerprint-service.example",
- ],
-}
-
-genrule {
- name: "gen-fingerprint-example-apex.rc",
- srcs: [":fingerprint-example.rc"],
- out: ["fingerprint-example-apex.rc"],
- cmd: "sed -e 's@/vendor/bin/@/apex/com.android.hardware.biometrics.fingerprint.virtual/bin/@' $(in) > $(out)",
-}
-
-prebuilt_etc {
- name: "fingerprint-example-apex.rc",
- src: ":gen-fingerprint-example-apex.rc",
- installable: false,
-}
-
-prebuilt_etc {
- name: "fingerprint-example-apex.xml",
- src: ":fingerprint-example.xml",
- sub_dir: "vintf",
- installable: false,
-}
diff --git a/biometrics/fingerprint/aidl/default/apex/file_contexts b/biometrics/fingerprint/aidl/default/apex_file_contexts
similarity index 100%
rename from biometrics/fingerprint/aidl/default/apex/file_contexts
rename to biometrics/fingerprint/aidl/default/apex_file_contexts
diff --git a/biometrics/fingerprint/aidl/default/apex/manifest.json b/biometrics/fingerprint/aidl/default/apex_manifest.json
similarity index 100%
rename from biometrics/fingerprint/aidl/default/apex/manifest.json
rename to biometrics/fingerprint/aidl/default/apex_manifest.json
diff --git a/biometrics/fingerprint/aidl/default/fingerprint-example.rc b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
index ee4713c..da4ea45 100644
--- a/biometrics/fingerprint/aidl/default/fingerprint-example.rc
+++ b/biometrics/fingerprint/aidl/default/fingerprint-example.rc
@@ -1,4 +1,4 @@
-service vendor.fingerprint-example /vendor/bin/hw/android.hardware.biometrics.fingerprint-service.example
+service vendor.fingerprint-example /apex/com.android.hardware.biometrics.fingerprint.virtual/bin/hw/android.hardware.biometrics.fingerprint-service.example
class hal
user nobody
group nobody
diff --git a/broadcastradio/aidl/Android.bp b/broadcastradio/aidl/Android.bp
index 3f89029..e8bc5eb 100644
--- a/broadcastradio/aidl/Android.bp
+++ b/broadcastradio/aidl/Android.bp
@@ -43,6 +43,6 @@
imports: [],
},
],
- frozen: true,
+ frozen: false,
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
index fe8489c..b96def3 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/AmFmRegionConfig.aidl
@@ -37,8 +37,8 @@
android.hardware.broadcastradio.AmFmBandRange[] ranges;
int fmDeemphasis;
int fmRds;
- const int DEEMPHASIS_D50 = (1 << 0);
- const int DEEMPHASIS_D75 = (1 << 1);
- const int RDS = (1 << 0);
- const int RBDS = (1 << 1);
+ const int DEEMPHASIS_D50 = (1 << 0) /* 1 */;
+ const int DEEMPHASIS_D75 = (1 << 1) /* 2 */;
+ const int RDS = (1 << 0) /* 1 */;
+ const int RBDS = (1 << 1) /* 2 */;
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
index 98af437..d6d33bc 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -35,6 +35,9 @@
@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
enum ConfigFlag {
FORCE_MONO = 1,
+ /**
+ * @deprecated Use {link #FORCE_ANALOG_FM} instead
+ */
FORCE_ANALOG,
FORCE_DIGITAL,
RDS_AF,
@@ -43,4 +46,6 @@
DAB_FM_LINKING,
DAB_DAB_SOFT_LINKING,
DAB_FM_SOFT_LINKING,
+ FORCE_ANALOG_FM,
+ FORCE_ANALOG_AM,
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl
new file mode 100644
index 0000000..dd06134
--- /dev/null
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/HdSubChannel.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.broadcastradio;
+@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
+enum HdSubChannel {
+ HD1 = 0,
+ HD2 = 1,
+ HD3 = 2,
+ HD4 = 3,
+ HD5 = 4,
+ HD6 = 5,
+ HD7 = 6,
+ HD8 = 7,
+}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
index 4df272c..ed41af0 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/IdentifierType.aidl
@@ -47,6 +47,13 @@
DAB_FREQUENCY_KHZ,
DRMO_SERVICE_ID,
DRMO_FREQUENCY_KHZ,
- SXM_SERVICE_ID = (DRMO_FREQUENCY_KHZ + 2),
+ /**
+ * @deprecated SiriusXM Satellite Radio is not supported.
+ */
+ SXM_SERVICE_ID = (DRMO_FREQUENCY_KHZ + 2) /* 12 */,
+ /**
+ * @deprecated SiriusXM Satellite Radio is not supported.
+ */
SXM_CHANNEL,
+ HD_STATION_LOCATION,
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
index e02b6b1..b4a1efa 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Metadata.aidl
@@ -50,4 +50,12 @@
String dabServiceNameShort;
String dabComponentName;
String dabComponentNameShort;
+ String genre;
+ String commentShortDescription;
+ String commentActualText;
+ String commercial;
+ String[] ufids;
+ String hdStationNameShort;
+ String hdStationNameLong;
+ int hdSubChannelsAvailable;
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
index b14023a..997cdd7 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -42,10 +42,13 @@
int signalQuality;
android.hardware.broadcastradio.Metadata[] metadata;
android.hardware.broadcastradio.VendorKeyValue[] vendorInfo;
- const int FLAG_LIVE = (1 << 0);
- const int FLAG_MUTED = (1 << 1);
- const int FLAG_TRAFFIC_PROGRAM = (1 << 2);
- const int FLAG_TRAFFIC_ANNOUNCEMENT = (1 << 3);
- const int FLAG_TUNABLE = (1 << 4);
- const int FLAG_STEREO = (1 << 5);
+ const int FLAG_LIVE = (1 << 0) /* 1 */;
+ const int FLAG_MUTED = (1 << 1) /* 2 */;
+ const int FLAG_TRAFFIC_PROGRAM = (1 << 2) /* 4 */;
+ const int FLAG_TRAFFIC_ANNOUNCEMENT = (1 << 3) /* 8 */;
+ const int FLAG_TUNABLE = (1 << 4) /* 16 */;
+ const int FLAG_STEREO = (1 << 5) /* 32 */;
+ const int FLAG_SIGNAL_ACQUISITION = (1 << 6) /* 64 */;
+ const int FLAG_HD_SIS_ACQUISITION = (1 << 7) /* 128 */;
+ const int FLAG_HD_AUDIO_ACQUISITION = (1 << 8) /* 256 */;
}
diff --git a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
index 8af74c7..b0fc018 100644
--- a/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
+++ b/broadcastradio/aidl/aidl_api/android.hardware.broadcastradio/current/android/hardware/broadcastradio/Result.aidl
@@ -34,7 +34,7 @@
package android.hardware.broadcastradio;
@Backing(type="int") @JavaDerive(equals=true, toString=true) @VintfStability
enum Result {
- OK,
+ OK = 0,
INTERNAL_ERROR,
INVALID_ARGUMENTS,
INVALID_STATE,
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
index 11da39c..ddf60e0 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ConfigFlag.aidl
@@ -36,10 +36,12 @@
* Forces the analog playback for the supporting radio technology.
*
* User may disable digital playback for FM HD Radio or hybrid FM/DAB with
- * this option. This is purely user choice, ie. does not reflect digital-
+ * this option. This is purely user choice, i.e. does not reflect digital-
* analog handover state managed from the HAL implementation side.
*
- * Some radio technologies may not support this, ie. DAB.
+ * Some radio technologies may not support this, i.e. DAB.
+ *
+ * @deprecated Use {link #FORCE_ANALOG_FM} instead
*/
FORCE_ANALOG,
@@ -89,4 +91,26 @@
* Enables DAB-FM soft-linking (related content).
*/
DAB_FM_SOFT_LINKING,
+
+ /**
+ * Forces the FM analog playback for the supporting radio technology.
+ *
+ * User may disable FM digital playback for FM HD Radio or hybrid FM/DAB
+ * with this option. This is purely user choice, i.e. does not reflect
+ * digital-analog handover state managed from the HAL implementation side.
+ *
+ * Some radio technologies may not support this, i.e. DAB.
+ */
+ FORCE_ANALOG_FM,
+
+ /**
+ * Forces the AM analog playback for the supporting radio technology.
+ *
+ * User may disable AM digital playback for AM HD Radio or hybrid AM/DAB
+ * with this option. This is purely user choice, i.e. does not reflect
+ * digital-analog handover state managed from the HAL implementation side.
+ *
+ * Some radio technologies may not support this, i.e. DAB.
+ */
+ FORCE_ANALOG_AM,
}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl
new file mode 100644
index 0000000..46a3e0c
--- /dev/null
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/HdSubChannel.aidl
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.broadcastradio;
+
+/**
+ * Index of HD radio subchannel.
+ */
+@VintfStability
+@Backing(type="int")
+@JavaDerive(equals=true, toString=true)
+enum HdSubChannel {
+ /**
+ * Index of HD radio subchannel 1.
+ *
+ * <p>There are at most 8 HD radio subchannels of 1-based om HD radio standard. It is
+ * converted to 0-based index. 0 is the index of main program service (MPS). 1 to 7
+ * are indexes of additional supplemental program services (SPS).
+ */
+ HD1 = 0,
+ /**
+ * {@see HD1}
+ */
+ HD2 = 1,
+ /**
+ * {@see HD1}
+ */
+ HD3 = 2,
+ /**
+ * {@see HD1}
+ */
+ HD4 = 3,
+ /**
+ * {@see HD1}
+ */
+ HD5 = 4,
+ /**
+ * {@see HD1}
+ */
+ HD6 = 5,
+ /**
+ * {@see HD1}
+ */
+ HD7 = 6,
+ /**
+ * {@see HD1}
+ */
+ HD8 = 7,
+}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
index 646c502..4a95a41 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/IdentifierType.aidl
@@ -154,11 +154,42 @@
/**
* 32bit primary identifier for SiriusXM Satellite Radio.
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported.
*/
SXM_SERVICE_ID = DRMO_FREQUENCY_KHZ + 2,
/**
* 0-999 range
+ *
+ * @deprecated SiriusXM Satellite Radio is not supported.
*/
SXM_CHANNEL,
+
+ /**
+ * 64bit additional identifier for HD Radio representing station location.
+ *
+ * Consists of (from the LSB):
+ * - 4 bit: Bits 0:3 of altitude
+ * - 13 bit: Fractional bits of longitude
+ * - 8 bit: Integer bits of longitude
+ * - 1 bit: 0 for east and 1 for west for longitude
+ * - 1 bit: 0, representing latitude
+ * - 5 bit: pad of zeros separating longitude and latitude
+ * - 4 bit: Bits 4:7 of altitude
+ * - 13 bit: Fractional bits of latitude
+ * - 8 bit: Integer bits of latitude
+ * - 1 bit: 0 for north and 1 for south for latitude
+ * - 1 bit: 1, representing latitude
+ * - 5 bit: pad of zeros
+ *
+ * This format is defined in NRSC-5-C document: SY_IDD_1020s.
+ *
+ * Due to Station ID abuse, some HD_STATION_ID_EXT identifiers may be not
+ * globally unique. To provide a best-effort solution, the station’s
+ * broadcast antenna containing the latitude and longitude may be carried
+ * as additional identifier and may be used by the tuner hardware to
+ * double-check tuning.
+ */
+ HD_STATION_LOCATION,
}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
index 7769b8c..0ce967f 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/Metadata.aidl
@@ -116,4 +116,70 @@
* <p>Note: The string must be up to 8 characters long.
*/
String dabComponentNameShort;
+
+ /**
+ * Genre of the current audio piece (string)
+ *
+ * <p>(see NRSC-G200-A and id3v2.3.0 for more info)
+ */
+ String genre;
+
+ /**
+ * Short context description of comment (string)
+ *
+ * <p>Comment could relate to the current audio program content, or it might
+ * be unrelated information that the station chooses to send. It is
+ * composed of short content description and actual text (see NRSC-G200-A
+ * and id3v2.3.0 for more info).
+ */
+ String commentShortDescription;
+
+ /**
+ * Actual text of comment (string)
+ *
+ * @see #commentShortDescription
+ */
+ String commentActualText;
+
+ /**
+ * Commercial (string)
+ *
+ * <p>Commercial is application specific and generally used to facilitate the
+ * sale of products and services (see NRSC-G200-A and id3v2.3.0 for more info).
+ */
+ String commercial;
+
+ /**
+ * HD Unique File Identifiers (Array of strings)
+ *
+ * <p>Unique File Identifier (UFID) can be used to transmit an alphanumeric
+ * identifier of the current content, or of an advertised product or service
+ * (see NRSC-G200-A and id3v2.3.0 for more info).
+ */
+ String[] ufids;
+
+ /**
+ * HD short station name or HD universal short station name
+ *
+ * <p>It can be up to 12 characters (see SY_IDD_1020s for more info).
+ */
+ String hdStationNameShort;
+
+ /**
+ * HD long station name, HD station slogan or HD station message
+ *
+ * <p>(see SY_IDD_1020s for more info)
+ */
+ String hdStationNameLong;
+
+ /**
+ * Bit mask of all HD Radio subchannels available (uint8_t)
+ *
+ * <p>Bit {@link HdSubChannel#HD1} from LSB represents the availability
+ * of HD-1 subchannel (main program service, MPS). Bits
+ * {@link HdSubChannel#HD2} to {@link HdSubChannel#HD8} from LSB represent
+ * HD-2 to HD-8 subchannel (supplemental program services, SPS)
+ * respectively.
+ */
+ int hdSubChannelsAvailable;
}
diff --git a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
index 7632c81..d4ccd01 100644
--- a/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
+++ b/broadcastradio/aidl/android/hardware/broadcastradio/ProgramInfo.aidl
@@ -71,6 +71,23 @@
const int FLAG_STEREO = 1 << 5;
/**
+ * A signal has been acquired if this bit is set.
+ */
+
+ const int FLAG_SIGNAL_ACQUISITION = 1 << 6;
+ /**
+ * An HD Station Information Service (SIS) information is available if this
+ * bit is set.
+ */
+
+ const int FLAG_HD_SIS_ACQUISITION = 1 << 7;
+
+ /**
+ * An HD digital audio is available if this bit is set.
+ */
+ const int FLAG_HD_AUDIO_ACQUISITION = 1 << 8;
+
+ /**
* An identifier used to point at the program (primarily to tune to it).
*
* This field is required - its type field must not be set to
@@ -153,7 +170,8 @@
*
* It can be a combination of {@link #FLAG_LIVE}, {@link #FLAG_MUTED},
* {@link #FLAG_TRAFFIC_PROGRAM}, {@link #FLAG_TRAFFIC_ANNOUNCEMENT},
- * {@link #FLAG_TUNABLE}, and {@link #FLAG_STEREO}.
+ * {@link #FLAG_TUNABLE}, {@link #FLAG_STEREO}, {@link #FLAG_SIGNAL_ACQUISITION},
+ * {@link #FLAG_HD_SIS_ACQUISITION}, and {@link #FLAG_HD_AUDIO_ACQUISITION}.
*/
int infoFlags;
diff --git a/broadcastradio/aidl/default/Android.bp b/broadcastradio/aidl/default/Android.bp
index 1d1bef7..743365a 100644
--- a/broadcastradio/aidl/default/Android.bp
+++ b/broadcastradio/aidl/default/Android.bp
@@ -26,11 +26,11 @@
cc_defaults {
name: "BroadcastRadioHalDefaults",
static_libs: [
- "android.hardware.broadcastradio@common-utils-aidl-lib",
+ "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
"android.hardware.broadcastradio@common-utils-lib",
],
shared_libs: [
- "android.hardware.broadcastradio-V1-ndk",
+ "android.hardware.broadcastradio-V2-ndk",
"libbase",
"libbinder_ndk",
"liblog",
@@ -62,7 +62,7 @@
cc_library {
name: "DefaultBroadcastRadioHal",
- vendor: true,
+ vendor_available: true,
export_include_dirs: ["."],
defaults: [
"BroadcastRadioHalDefaults",
@@ -76,14 +76,15 @@
cc_fuzz {
name: "android.hardware.broadcastradio-service.default_fuzzer",
- vendor: true,
+ // TODO(b/307611931): avoid fuzzing on vendor until hermiticity issue is fixed
+ // vendor: true,
defaults: [
"BroadcastRadioHalDefaults",
"service_fuzzer_defaults",
],
static_libs: [
"DefaultBroadcastRadioHal",
- "android.hardware.broadcastradio-V1-ndk",
+ "android.hardware.broadcastradio-V2-ndk",
],
srcs: [
"fuzzer.cpp",
diff --git a/broadcastradio/aidl/default/BroadcastRadio.cpp b/broadcastradio/aidl/default/BroadcastRadio.cpp
index 8584921..54186b0 100644
--- a/broadcastradio/aidl/default/BroadcastRadio.cpp
+++ b/broadcastradio/aidl/default/BroadcastRadio.cpp
@@ -16,6 +16,7 @@
#include "BroadcastRadio.h"
#include <broadcastradio-utils-aidl/Utils.h>
+#include <broadcastradio-utils-aidl/UtilsV2.h>
#include "resources.h"
#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
@@ -221,7 +222,7 @@
resultToInt(Result::NOT_SUPPORTED), "selector is not supported");
}
- if (!utils::isValid(program)) {
+ if (!utils::isValidV2(program)) {
LOG(ERROR) << __func__ << ": selector is not valid: " << program.toString();
return ScopedAStatus::fromServiceSpecificErrorWithMessage(
resultToInt(Result::INVALID_ARGUMENTS), "selector is not valid");
diff --git a/broadcastradio/aidl/default/broadcastradio-default.xml b/broadcastradio/aidl/default/broadcastradio-default.xml
index 1555822..a57b724 100644
--- a/broadcastradio/aidl/default/broadcastradio-default.xml
+++ b/broadcastradio/aidl/default/broadcastradio-default.xml
@@ -1,6 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.broadcastradio</name>
+ <version>2</version>
<fqname>IBroadcastRadio/amfm</fqname>
<fqname>IBroadcastRadio/dab</fqname>
</hal>
diff --git a/broadcastradio/aidl/vts/Android.bp b/broadcastradio/aidl/vts/Android.bp
index b60387e..87e48a9 100644
--- a/broadcastradio/aidl/vts/Android.bp
+++ b/broadcastradio/aidl/vts/Android.bp
@@ -35,8 +35,8 @@
"libxml2",
],
static_libs: [
- "android.hardware.broadcastradio-V1-ndk",
- "android.hardware.broadcastradio@common-utils-aidl-lib",
+ "android.hardware.broadcastradio-V2-ndk",
+ "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
"android.hardware.broadcastradio@vts-utils-lib",
"libgmock",
],
diff --git a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
index 790d60b..72869cc 100644
--- a/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
+++ b/broadcastradio/aidl/vts/src/VtsHalBroadcastradioAidlTargetTest.cpp
@@ -32,6 +32,7 @@
#include <aidl/Gtest.h>
#include <aidl/Vintf.h>
#include <broadcastradio-utils-aidl/Utils.h>
+#include <broadcastradio-utils-aidl/UtilsV2.h>
#include <cutils/bitops.h>
#include <gmock/gmock.h>
@@ -50,7 +51,6 @@
using ::aidl::android::hardware::broadcastradio::utils::resultToInt;
using ::ndk::ScopedAStatus;
using ::ndk::SharedRefBase;
-using ::std::string;
using ::std::vector;
using ::testing::_;
using ::testing::AnyNumber;
@@ -73,20 +73,29 @@
ConfigFlag::DAB_FM_SOFT_LINKING,
};
-void printSkipped(const string& msg) {
+constexpr int32_t kAidlVersion1 = 1;
+constexpr int32_t kAidlVersion2 = 2;
+
+void printSkipped(const std::string& msg) {
const auto testInfo = testing::UnitTest::GetInstance()->current_test_info();
LOG(INFO) << "[ SKIPPED ] " << testInfo->test_case_name() << "." << testInfo->name()
<< " with message: " << msg;
}
-bool isValidAmFmFreq(int64_t freq) {
+bool isValidAmFmFreq(int64_t freq, int aidlVersion) {
ProgramIdentifier id = bcutils::makeIdentifier(IdentifierType::AMFM_FREQUENCY_KHZ, freq);
- return bcutils::isValid(id);
+ if (aidlVersion == kAidlVersion1) {
+ return bcutils::isValid(id);
+ } else if (aidlVersion == kAidlVersion2) {
+ return bcutils::isValidV2(id);
+ }
+ LOG(ERROR) << "Unknown AIDL version " << aidlVersion;
+ return false;
}
-void validateRange(const AmFmBandRange& range) {
- EXPECT_TRUE(isValidAmFmFreq(range.lowerBound));
- EXPECT_TRUE(isValidAmFmFreq(range.upperBound));
+void validateRange(const AmFmBandRange& range, int aidlVersion) {
+ EXPECT_TRUE(isValidAmFmFreq(range.lowerBound, aidlVersion));
+ EXPECT_TRUE(isValidAmFmFreq(range.upperBound, aidlVersion));
EXPECT_LT(range.lowerBound, range.upperBound);
EXPECT_GT(range.spacing, 0u);
EXPECT_EQ((range.upperBound - range.lowerBound) % range.spacing, 0u);
@@ -142,7 +151,7 @@
class TunerCallbackImpl final : public BnTunerCallback {
public:
- TunerCallbackImpl();
+ explicit TunerCallbackImpl(int32_t aidlVersion);
ScopedAStatus onTuneFailed(Result result, const ProgramSelector& selector) override;
ScopedAStatus onCurrentProgramInfoChanged(const ProgramInfo& info) override;
ScopedAStatus onProgramListUpdated(const ProgramListChunk& chunk) override;
@@ -160,6 +169,7 @@
private:
std::mutex mLock;
+ int32_t mCallbackAidlVersion;
bool mAntennaConnectionState GUARDED_BY(mLock);
ProgramInfo mCurrentProgramInfo GUARDED_BY(mLock);
bcutils::ProgramInfoSet mProgramList GUARDED_BY(mLock);
@@ -171,7 +181,7 @@
MOCK_METHOD1(onListUpdated, ScopedAStatus(const vector<Announcement>&));
};
-class BroadcastRadioHalTest : public testing::TestWithParam<string> {
+class BroadcastRadioHalTest : public testing::TestWithParam<std::string> {
protected:
void SetUp() override;
void TearDown() override;
@@ -183,14 +193,17 @@
std::shared_ptr<IBroadcastRadio> mModule;
Properties mProperties;
std::shared_ptr<TunerCallbackImpl> mCallback;
+ int32_t mAidlVersion;
};
-MATCHER_P(InfoHasId, id, string(negation ? "does not contain" : "contains") + " " + id.toString()) {
+MATCHER_P(InfoHasId, id,
+ std::string(negation ? "does not contain" : "contains") + " " + id.toString()) {
vector<int> ids = bcutils::getAllIds(arg.selector, id.type);
return ids.end() != find(ids.begin(), ids.end(), id.value);
}
-TunerCallbackImpl::TunerCallbackImpl() {
+TunerCallbackImpl::TunerCallbackImpl(int32_t aidlVersion) {
+ mCallbackAidlVersion = aidlVersion;
mAntennaConnectionState = true;
}
@@ -230,7 +243,12 @@
physically > IdentifierType::SXM_CHANNEL);
if (logically == IdentifierType::AMFM_FREQUENCY_KHZ) {
- std::optional<string> ps = bcutils::getMetadataString(info, Metadata::rdsPs);
+ std::optional<std::string> ps;
+ if (mCallbackAidlVersion == kAidlVersion1) {
+ ps = bcutils::getMetadataString(info, Metadata::rdsPs);
+ } else {
+ ps = bcutils::getMetadataStringV2(info, Metadata::rdsPs);
+ }
if (ps.has_value()) {
EXPECT_NE(::android::base::Trim(*ps), "")
<< "Don't use empty RDS_PS as an indicator of missing RSD PS data.";
@@ -323,9 +341,13 @@
EXPECT_FALSE(mProperties.product.empty());
EXPECT_GT(mProperties.supportedIdentifierTypes.size(), 0u);
- mCallback = SharedRefBase::make<TunerCallbackImpl>();
+ // get AIDL HAL version
+ ASSERT_TRUE(mModule->getInterfaceVersion(&mAidlVersion).isOk());
+ EXPECT_GE(mAidlVersion, kAidlVersion1);
+ EXPECT_LE(mAidlVersion, kAidlVersion2);
// set callback
+ mCallback = SharedRefBase::make<TunerCallbackImpl>(mAidlVersion);
EXPECT_TRUE(mModule->setTunerCallback(mCallback).isOk());
}
@@ -443,7 +465,7 @@
EXPECT_GT(config.ranges.size(), 0u);
for (const auto& range : config.ranges) {
- validateRange(range);
+ validateRange(range, mAidlVersion);
EXPECT_EQ(range.seekSpacing % range.spacing, 0u);
EXPECT_GE(range.seekSpacing, range.spacing);
}
@@ -494,7 +516,7 @@
EXPECT_GT(config.ranges.size(), 0u);
for (const auto& range : config.ranges) {
- validateRange(range);
+ validateRange(range, mAidlVersion);
EXPECT_EQ(range.seekSpacing, 0u);
}
}
@@ -522,11 +544,17 @@
std::regex re("^[A-Z0-9][A-Z0-9 ]{0,5}[A-Z0-9]$");
for (const auto& entry : config) {
- EXPECT_TRUE(std::regex_match(string(entry.label), re));
+ EXPECT_TRUE(std::regex_match(std::string(entry.label), re));
ProgramIdentifier id =
bcutils::makeIdentifier(IdentifierType::DAB_FREQUENCY_KHZ, entry.frequencyKhz);
- EXPECT_TRUE(bcutils::isValid(id));
+ if (mAidlVersion == kAidlVersion1) {
+ EXPECT_TRUE(bcutils::isValid(id));
+ } else if (mAidlVersion == kAidlVersion2) {
+ EXPECT_TRUE(bcutils::isValidV2(id));
+ } else {
+ LOG(ERROR) << "Unknown callback AIDL version " << mAidlVersion;
+ }
}
}
@@ -1175,10 +1203,21 @@
continue;
}
- std::optional<string> name = bcutils::getMetadataString(program, Metadata::programName);
- if (!name) {
- name = bcutils::getMetadataString(program, Metadata::rdsPs);
+ std::optional<std::string> name;
+ if (mAidlVersion == kAidlVersion1) {
+ name = bcutils::getMetadataString(program, Metadata::programName);
+ if (!name) {
+ name = bcutils::getMetadataString(program, Metadata::rdsPs);
+ }
+ } else if (mAidlVersion == kAidlVersion2) {
+ name = bcutils::getMetadataStringV2(program, Metadata::programName);
+ if (!name) {
+ name = bcutils::getMetadataStringV2(program, Metadata::rdsPs);
+ }
+ } else {
+ LOG(ERROR) << "Unknown HAL AIDL version " << mAidlVersion;
}
+
ASSERT_TRUE(name.has_value());
ProgramIdentifier expectedId = bcutils::makeHdRadioStationName(*name);
diff --git a/broadcastradio/common/utilsaidl/Android.bp b/broadcastradio/common/utilsaidl/Android.bp
index fa6de19..4ec635b 100644
--- a/broadcastradio/common/utilsaidl/Android.bp
+++ b/broadcastradio/common/utilsaidl/Android.bp
@@ -25,6 +25,29 @@
cc_library_static {
name: "android.hardware.broadcastradio@common-utils-aidl-lib",
+ defaults: [
+ "VtsBroadcastRadioDefaults",
+ ],
+ shared_libs: [
+ "android.hardware.broadcastradio-V1-ndk",
+ ],
+}
+
+cc_library_static {
+ name: "android.hardware.broadcastradio@common-utils-aidl-lib-V2",
+ defaults: [
+ "VtsBroadcastRadioDefaults",
+ ],
+ srcs: [
+ "src/UtilsV2.cpp",
+ ],
+ shared_libs: [
+ "android.hardware.broadcastradio-V2-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "VtsBroadcastRadioDefaults",
vendor_available: true,
relative_install_path: "hw",
cflags: [
@@ -37,11 +60,10 @@
"-std=c++1z",
],
srcs: [
- "Utils.cpp",
+ "src/Utils.cpp",
],
export_include_dirs: ["include"],
shared_libs: [
- "android.hardware.broadcastradio-V1-ndk",
"libbase",
],
static_libs: [
diff --git a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
index ee85a17..b6fb33f 100644
--- a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
+++ b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/Utils.h
@@ -17,7 +17,6 @@
#pragma once
#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
-#include <aidl/android/hardware/broadcastradio/Metadata.h>
#include <aidl/android/hardware/broadcastradio/ProgramFilter.h>
#include <aidl/android/hardware/broadcastradio/ProgramIdentifier.h>
#include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
diff --git a/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h
new file mode 100644
index 0000000..e411aa4
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/include/broadcastradio-utils-aidl/UtilsV2.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+#include <aidl/android/hardware/broadcastradio/IdentifierType.h>
+#include <aidl/android/hardware/broadcastradio/Metadata.h>
+#include <aidl/android/hardware/broadcastradio/ProgramIdentifier.h>
+#include <aidl/android/hardware/broadcastradio/ProgramInfo.h>
+#include <aidl/android/hardware/broadcastradio/ProgramSelector.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+bool isValidV2(const ProgramIdentifier& id);
+bool isValidV2(const ProgramSelector& sel);
+std::optional<std::string> getMetadataStringV2(const ProgramInfo& info, const Metadata::Tag& tag);
+
+} // namespace utils
+
+} // namespace aidl::android::hardware::broadcastradio
diff --git a/broadcastradio/common/utilsaidl/Utils.cpp b/broadcastradio/common/utilsaidl/src/Utils.cpp
similarity index 95%
rename from broadcastradio/common/utilsaidl/Utils.cpp
rename to broadcastradio/common/utilsaidl/src/Utils.cpp
index de4f529..2875318 100644
--- a/broadcastradio/common/utilsaidl/Utils.cpp
+++ b/broadcastradio/common/utilsaidl/src/Utils.cpp
@@ -31,7 +31,6 @@
namespace {
using ::android::base::EqualsIgnoreCase;
-using ::std::string;
using ::std::vector;
const int64_t kValueForNotFoundIdentifier = 0;
@@ -207,7 +206,7 @@
uint64_t val = static_cast<uint64_t>(id.value);
bool valid = true;
- auto expect = [&valid](bool condition, const string& message) {
+ auto expect = [&valid](bool condition, const std::string& message) {
if (!condition) {
valid = false;
LOG(ERROR) << "identifier not valid, expected " << message;
@@ -278,9 +277,9 @@
case IdentifierType::SXM_CHANNEL:
expect(val < 1000u, "SXM channel < 1000");
break;
- case IdentifierType::VENDOR_START:
- case IdentifierType::VENDOR_END:
- // skip
+ default:
+ expect(id.type >= IdentifierType::VENDOR_START && id.type <= IdentifierType::VENDOR_END,
+ "Undefined identifier type");
break;
}
@@ -452,10 +451,10 @@
return metadataString;
}
-ProgramIdentifier makeHdRadioStationName(const string& name) {
+ProgramIdentifier makeHdRadioStationName(const std::string& name) {
constexpr size_t maxlen = 8;
- string shortName;
+ std::string shortName;
shortName.reserve(maxlen);
const auto& loc = std::locale::classic();
@@ -484,7 +483,7 @@
return static_cast<IdentifierType>(typeAsInt);
}
-bool parseArgInt(const string& s, int* out) {
+bool parseArgInt(const std::string& s, int* out) {
return ::android::base::ParseInt(s, out);
}
@@ -492,7 +491,7 @@
return ::android::base::ParseInt(s, out);
}
-bool parseArgBool(const string& s, bool* out) {
+bool parseArgBool(const std::string& s, bool* out) {
if (EqualsIgnoreCase(s, "true")) {
*out = true;
} else if (EqualsIgnoreCase(s, "false")) {
@@ -503,7 +502,7 @@
return true;
}
-bool parseArgDirection(const string& s, bool* out) {
+bool parseArgDirection(const std::string& s, bool* out) {
if (EqualsIgnoreCase(s, "up")) {
*out = true;
} else if (EqualsIgnoreCase(s, "down")) {
@@ -514,8 +513,8 @@
return true;
}
-bool parseArgIdentifierTypeArray(const string& s, vector<IdentifierType>* out) {
- for (const string& val : ::android::base::Split(s, ",")) {
+bool parseArgIdentifierTypeArray(const std::string& s, vector<IdentifierType>* out) {
+ for (const std::string& val : ::android::base::Split(s, ",")) {
int outInt;
if (!parseArgInt(val, &outInt)) {
return false;
@@ -526,8 +525,8 @@
}
bool parseProgramIdentifierList(const std::string& s, vector<ProgramIdentifier>* out) {
- for (const string& idStr : ::android::base::Split(s, ",")) {
- const vector<string> idStrPair = ::android::base::Split(idStr, ":");
+ for (const std::string& idStr : ::android::base::Split(s, ",")) {
+ const vector<std::string> idStrPair = ::android::base::Split(idStr, ":");
if (idStrPair.size() != 2) {
return false;
}
diff --git a/broadcastradio/common/utilsaidl/src/UtilsV2.cpp b/broadcastradio/common/utilsaidl/src/UtilsV2.cpp
new file mode 100644
index 0000000..ef739df
--- /dev/null
+++ b/broadcastradio/common/utilsaidl/src/UtilsV2.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "BcRadioAidlDef.utilsV2"
+
+#include "broadcastradio-utils-aidl/UtilsV2.h"
+
+#include <android-base/logging.h>
+#include <android-base/strings.h>
+
+namespace aidl::android::hardware::broadcastradio {
+
+namespace utils {
+
+bool isValidV2(const ProgramIdentifier& id) {
+ uint64_t val = static_cast<uint64_t>(id.value);
+ bool valid = true;
+
+ auto expect = [&valid](bool condition, const std::string& message) {
+ if (!condition) {
+ valid = false;
+ LOG(ERROR) << "identifier not valid, expected " << message;
+ }
+ };
+
+ switch (id.type) {
+ case IdentifierType::INVALID:
+ expect(false, "IdentifierType::INVALID");
+ break;
+ case IdentifierType::DAB_FREQUENCY_KHZ:
+ expect(val > 100000u, "f > 100MHz");
+ [[fallthrough]];
+ case IdentifierType::AMFM_FREQUENCY_KHZ:
+ case IdentifierType::DRMO_FREQUENCY_KHZ:
+ expect(val > 100u, "f > 100kHz");
+ expect(val < 10000000u, "f < 10GHz");
+ break;
+ case IdentifierType::RDS_PI:
+ expect(val != 0u, "RDS PI != 0");
+ expect(val <= 0xFFFFu, "16bit id");
+ break;
+ case IdentifierType::HD_STATION_ID_EXT: {
+ uint64_t stationId = val & 0xFFFFFFFF; // 32bit
+ val >>= 32;
+ uint64_t subchannel = val & 0xF; // 4bit
+ val >>= 4;
+ uint64_t freq = val & 0x3FFFF; // 18bit
+ expect(stationId != 0u, "HD station id != 0");
+ expect(subchannel < 8u, "HD subch < 8");
+ expect(freq > 100u, "f > 100kHz");
+ expect(freq < 10000000u, "f < 10GHz");
+ break;
+ }
+ case IdentifierType::HD_STATION_NAME: {
+ while (val > 0) {
+ char ch = static_cast<char>(val & 0xFF);
+ val >>= 8;
+ expect((ch >= '0' && ch <= '9') || (ch >= 'A' && ch <= 'Z'),
+ "HD_STATION_NAME does not match [A-Z0-9]+");
+ }
+ break;
+ }
+ case IdentifierType::DAB_SID_EXT: {
+ uint64_t sid = val & 0xFFFFFFFF; // 32bit
+ val >>= 32;
+ uint64_t ecc = val & 0xFF; // 8bit
+ expect(sid != 0u, "DAB SId != 0");
+ expect(ecc >= 0xA0u && ecc <= 0xF6u, "Invalid ECC, see ETSI TS 101 756 V2.1.1");
+ break;
+ }
+ case IdentifierType::DAB_ENSEMBLE:
+ expect(val != 0u, "DAB ensemble != 0");
+ expect(val <= 0xFFFFu, "16bit id");
+ break;
+ case IdentifierType::DAB_SCID:
+ expect(val > 0xFu, "12bit SCId (not 4bit SCIdS)");
+ expect(val <= 0xFFFu, "12bit id");
+ break;
+ case IdentifierType::DRMO_SERVICE_ID:
+ expect(val != 0u, "DRM SId != 0");
+ expect(val <= 0xFFFFFFu, "24bit id");
+ break;
+ case IdentifierType::SXM_SERVICE_ID:
+ expect(val != 0u, "SXM SId != 0");
+ expect(val <= 0xFFFFFFFFu, "32bit id");
+ break;
+ case IdentifierType::SXM_CHANNEL:
+ expect(val < 1000u, "SXM channel < 1000");
+ break;
+ case IdentifierType::HD_STATION_LOCATION: {
+ uint64_t latitudeBit = val & 0x1;
+ expect(latitudeBit == 1u, "Latitude comes first");
+ val >>= 27;
+ uint64_t latitudePad = val & 0x1Fu;
+ expect(latitudePad == 0u, "Latitude padding");
+ val >>= 5;
+ uint64_t longitudeBit = val & 0x1;
+ expect(longitudeBit == 1u, "Longitude comes next");
+ val >>= 27;
+ uint64_t longitudePad = val & 0x1Fu;
+ expect(longitudePad == 0u, "Latitude padding");
+ break;
+ }
+ default:
+ expect(id.type >= IdentifierType::VENDOR_START && id.type <= IdentifierType::VENDOR_END,
+ "Undefined identifier type");
+ break;
+ }
+
+ return valid;
+}
+
+bool isValidV2(const ProgramSelector& sel) {
+ if (sel.primaryId.type != IdentifierType::AMFM_FREQUENCY_KHZ &&
+ sel.primaryId.type != IdentifierType::RDS_PI &&
+ sel.primaryId.type != IdentifierType::HD_STATION_ID_EXT &&
+ sel.primaryId.type != IdentifierType::DAB_SID_EXT &&
+ sel.primaryId.type != IdentifierType::DRMO_SERVICE_ID &&
+ sel.primaryId.type != IdentifierType::SXM_SERVICE_ID &&
+ (sel.primaryId.type < IdentifierType::VENDOR_START ||
+ sel.primaryId.type > IdentifierType::VENDOR_END)) {
+ return false;
+ }
+ return isValidV2(sel.primaryId);
+}
+
+std::optional<std::string> getMetadataStringV2(const ProgramInfo& info, const Metadata::Tag& tag) {
+ auto isRdsPs = [tag](const Metadata& item) { return item.getTag() == tag; };
+
+ auto it = std::find_if(info.metadata.begin(), info.metadata.end(), isRdsPs);
+ if (it == info.metadata.end()) {
+ return std::nullopt;
+ }
+
+ std::string metadataString;
+ switch (it->getTag()) {
+ case Metadata::rdsPs:
+ metadataString = it->get<Metadata::rdsPs>();
+ break;
+ case Metadata::rdsPty:
+ metadataString = std::to_string(it->get<Metadata::rdsPty>());
+ break;
+ case Metadata::rbdsPty:
+ metadataString = std::to_string(it->get<Metadata::rbdsPty>());
+ break;
+ case Metadata::rdsRt:
+ metadataString = it->get<Metadata::rdsRt>();
+ break;
+ case Metadata::songTitle:
+ metadataString = it->get<Metadata::songTitle>();
+ break;
+ case Metadata::songArtist:
+ metadataString = it->get<Metadata::songArtist>();
+ break;
+ case Metadata::songAlbum:
+ metadataString = it->get<Metadata::songAlbum>();
+ break;
+ case Metadata::stationIcon:
+ metadataString = std::to_string(it->get<Metadata::stationIcon>());
+ break;
+ case Metadata::albumArt:
+ metadataString = std::to_string(it->get<Metadata::albumArt>());
+ break;
+ case Metadata::programName:
+ metadataString = it->get<Metadata::programName>();
+ break;
+ case Metadata::dabEnsembleName:
+ metadataString = it->get<Metadata::dabEnsembleName>();
+ break;
+ case Metadata::dabEnsembleNameShort:
+ metadataString = it->get<Metadata::dabEnsembleNameShort>();
+ break;
+ case Metadata::dabServiceName:
+ metadataString = it->get<Metadata::dabServiceName>();
+ break;
+ case Metadata::dabServiceNameShort:
+ metadataString = it->get<Metadata::dabServiceNameShort>();
+ break;
+ case Metadata::dabComponentName:
+ metadataString = it->get<Metadata::dabComponentName>();
+ break;
+ case Metadata::dabComponentNameShort:
+ metadataString = it->get<Metadata::dabComponentNameShort>();
+ break;
+ case Metadata::genre:
+ metadataString = it->get<Metadata::genre>();
+ break;
+ case Metadata::commentShortDescription:
+ metadataString = it->get<Metadata::commentShortDescription>();
+ break;
+ case Metadata::commentActualText:
+ metadataString = it->get<Metadata::commentActualText>();
+ break;
+ case Metadata::commercial:
+ metadataString = it->get<Metadata::commercial>();
+ break;
+ case Metadata::ufids: {
+ auto& ufids = it->get<Metadata::ufids>();
+ metadataString = "[";
+ for (const auto& ufid : ufids) {
+ metadataString += std::string(ufid) + ",";
+ }
+ if (ufids.empty()) {
+ metadataString += "]";
+ } else {
+ metadataString[metadataString.size() - 1] = ']';
+ }
+ } break;
+ case Metadata::hdStationNameShort:
+ metadataString = it->get<Metadata::hdStationNameShort>();
+ break;
+ case Metadata::hdStationNameLong:
+ metadataString = it->get<Metadata::hdStationNameLong>();
+ break;
+ case Metadata::hdSubChannelsAvailable:
+ metadataString = std::to_string(it->get<Metadata::hdSubChannelsAvailable>());
+ break;
+ default:
+ LOG(ERROR) << "Metadata " << it->toString() << " is not converted.";
+ return std::nullopt;
+ }
+ return metadataString;
+}
+
+} // namespace utils
+
+} // namespace aidl::android::hardware::broadcastradio
diff --git a/camera/device/default/ExternalCameraUtils.cpp b/camera/device/default/ExternalCameraUtils.cpp
index cfb95f2..30c216f 100644
--- a/camera/device/default/ExternalCameraUtils.cpp
+++ b/camera/device/default/ExternalCameraUtils.cpp
@@ -402,7 +402,10 @@
buffer_handle_t buf,
/*out*/ buffer_handle_t** outBufPtr) {
using ::aidl::android::hardware::camera::common::Status;
- if (buf == nullptr && bufId == BUFFER_ID_NO_BUFFER) {
+ // AIDL does not have null NativeHandles. It sends empty handles instead.
+ // We check for when the buf is empty instead of when buf is null.
+ bool isBufEmpty = buf == nullptr || (buf->numFds == 0 && buf->numInts == 0);
+ if (isBufEmpty && bufId == BUFFER_ID_NO_BUFFER) {
ALOGE("%s: bufferId %" PRIu64 " has null buffer handle!", __FUNCTION__, bufId);
return Status::ILLEGAL_ARGUMENT;
}
@@ -857,4 +860,4 @@
} // namespace device
} // namespace camera
} // namespace hardware
-} // namespace android
\ No newline at end of file
+} // namespace android
diff --git a/cas/aidl/default/Android.bp b/cas/aidl/default/Android.bp
index 34ecbf2..06e167c 100644
--- a/cas/aidl/default/Android.bp
+++ b/cas/aidl/default/Android.bp
@@ -80,7 +80,8 @@
cc_fuzz {
name: "android.hardware.cas-service_fuzzer",
- vendor: true,
+ // TODO(b/307611931): avoid fuzzing on vendor until hermiticity issue is fixed
+ // vendor: true,
defaults: ["service_fuzzer_defaults"],
srcs: ["fuzzer.cpp"],
diff --git a/compatibility_matrices/compatibility_matrix.9.xml b/compatibility_matrices/compatibility_matrix.9.xml
index 75915e3..60dbd99 100644
--- a/compatibility_matrices/compatibility_matrix.9.xml
+++ b/compatibility_matrices/compatibility_matrix.9.xml
@@ -157,6 +157,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.broadcastradio</name>
+ <version>1-2</version>
<interface>
<name>IBroadcastRadio</name>
<regex-instance>.*</regex-instance>
@@ -240,15 +241,6 @@
<instance>default</instance>
</interface>
</hal>
- <!-- Either the native or the HIDL mapper HAL must exist on the device -->
- <hal format="hidl" optional="true">
- <name>android.hardware.graphics.mapper</name>
- <version>4.0</version>
- <interface>
- <name>IMapper</name>
- <instance>default</instance>
- </interface>
- </hal>
<hal format="aidl" optional="true">
<name>android.hardware.health</name>
<version>1-2</version>
@@ -511,6 +503,14 @@
</interface>
</hal>
<hal format="aidl" optional="true" updatable-via-apex="true">
+ <name>android.hardware.security.authgraph</name>
+ <version>1</version>
+ <interface>
+ <name>IAuthGraphKeyExchange</name>
+ <instance>nonsecure</instance>
+ </interface>
+ </hal>
+ <hal format="aidl" optional="true" updatable-via-apex="true">
<name>android.hardware.security.secureclock</name>
<version>1</version>
<interface>
@@ -609,7 +609,7 @@
</hal>
<hal format="aidl" optional="true">
<name>android.hardware.usb</name>
- <version>1-2</version>
+ <version>1-3</version>
<interface>
<name>IUsb</name>
<instance>default</instance>
@@ -678,7 +678,7 @@
<instance>default</instance>
</interface>
</hal>
- <!-- Either the native or the HIDL mapper HAL must exist on the device -->
+ <!-- The native mapper HAL must exist on the device -->
<hal format="native" optional="true">
<name>mapper</name>
<version>5.0</version>
diff --git a/compatibility_matrices/exclude/fcm_exclude.cpp b/compatibility_matrices/exclude/fcm_exclude.cpp
index 2cb4ffa..46f0e03 100644
--- a/compatibility_matrices/exclude/fcm_exclude.cpp
+++ b/compatibility_matrices/exclude/fcm_exclude.cpp
@@ -128,6 +128,7 @@
"android.hardware.media.bufferpool2@",
"android.hardware.radio@",
"android.hardware.uwb.fira_android@",
+ "android.hardware.wifi.common@",
// Test packages are exempted.
"android.hardware.tests.",
diff --git a/gatekeeper/aidl/Android.bp b/gatekeeper/aidl/Android.bp
index b050f6a..169a7d5 100644
--- a/gatekeeper/aidl/Android.bp
+++ b/gatekeeper/aidl/Android.bp
@@ -25,6 +25,9 @@
cpp: {
enabled: false,
},
+ rust: {
+ enabled: true,
+ },
},
versions_with_info: [
{
diff --git a/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl b/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
index c66d9e8..69de750 100644
--- a/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
+++ b/gnss/aidl/android/hardware/gnss/GnssSignalType.aidl
@@ -46,27 +46,28 @@
double carrierFrequencyHz;
/**
- * GNSS signal code type "A" representing GALILEO E1A, GALILEO E6A, IRNSS L5A SPS, IRNSS SA SPS,
+ * GNSS signal code type "A" representing GALILEO E1A, GALILEO E6A, NavIC L5A SPS, NavIC SA SPS,
* GLONASS G1a L1OCd, GLONASS G2a L2CSI.
*/
const @utf8InCpp String CODE_TYPE_A = "A";
/**
- * GNSS signal code type "B" representing GALILEO E1B, GALILEO E6B, IRNSS L5B RS (D),
- * IRNSS SB RS (D), GLONASS G1a L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
+ * GNSS signal code type "B" representing GALILEO E1B, GALILEO E6B, NavIC L5B RS (D),
+ * NavIC SB RS (D), GLONASS G1a L1OCp, GLONASS G2a L2OCp, QZSS L1Sb.
*/
const @utf8InCpp String CODE_TYPE_B = "B";
/**
* GNSS signal code type "C" representing GPS L1 C/A, GPS L2 C/A, GLONASS G1 C/A,
- * GLONASS G2 C/A, GALILEO E1C, GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, IRNSS L5C RS (P),
- * IRNSS SC RS (P).
+ * GLONASS G2 C/A, GALILEO E1C, GALILEO E6C, SBAS L1 C/A, QZSS L1 C/A, NavIC L5C RS (P),
+ * NavIC SC RS (P).
*/
const @utf8InCpp String CODE_TYPE_C = "C";
/**
* GNSS signal code type "D" representing GPS L2 (L1(C/A) + (P2-P1) (semi-codeless)),
- * QZSS L5S(I), BDS B1C Data, BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data.
+ * QZSS L5S(I), BDS B1C Data, BDS B2a Data, BDS B2b Data, BDS B2 (B2a+B2b) Data, BDS B3a Data,
+ * NavIC L1 Data.
*/
const @utf8InCpp String CODE_TYPE_D = "D";
@@ -100,7 +101,7 @@
/**
* GNSS signal code type "P" representing GPS L1P, GPS L2P, GLONASS G1P, GLONASS G2P,
* BDS B1C Pilot, BDS B2a Pilot, BDS B2b Pilot, BDS B2 (B2a+B2b) Pilot, BDS B3a Pilot,
- * QZSS L5S(Q).
+ * QZSS L5S(Q), NavIC L1 Pilot.
*/
const @utf8InCpp String CODE_TYPE_P = "P";
@@ -127,7 +128,7 @@
* GALILEO E5a (I+Q), GALILEO E5b (I+Q), GALILEO E5a+b (I+Q), GALILEO E6 (B+C), SBAS L5 (I+Q),
* QZSS L1C (D+P), QZSS L2C (M+L), QZSS L5 (I+Q), QZSS L6 (D+P), BDS B1 (I+Q),
* BDS B1C Data+Pilot, BDS B2a Data+Pilot, BDS B2 (I+Q), BDS B2 (B2a+B2b) Data+Pilot,
- * BDS B3 (I+Q), IRNSS L5 (B+C), IRNSS S (B+C).
+ * BDS B3 (I+Q), NavIC L5 (B+C), NavIC S (B+C), NavIC L1 Data+Pilot.
*/
const @utf8InCpp String CODE_TYPE_X = "X";
diff --git a/gnss/aidl/default/Android.bp b/gnss/aidl/default/Android.bp
index 542796f..822e8fc 100644
--- a/gnss/aidl/default/Android.bp
+++ b/gnss/aidl/default/Android.bp
@@ -26,12 +26,7 @@
cc_binary {
name: "android.hardware.gnss-service.example",
relative_install_path: "hw",
- init_rc: [
- "gnss-default.rc",
- ],
- vintf_fragments: [
- "gnss-default.xml",
- ],
+ installable: false, // install APEX instead
vendor: true,
cflags: [
"-Wall",
@@ -73,3 +68,35 @@
"android.hardware.gnss@common-default-lib",
],
}
+
+prebuilt_etc {
+ name: "gnss-default.rc",
+ src: "gnss-default.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "gnss-default.xml",
+ src: "gnss-default.xml",
+ sub_dir: "vintf",
+ installable: false,
+}
+
+apex {
+ name: "com.android.hardware.gnss",
+ manifest: "apex_manifest.json",
+ file_contexts: "apex_file_contexts",
+ key: "com.android.hardware.key",
+ certificate: ":com.android.hardware.certificate",
+ updatable: false,
+ vendor: true,
+
+ binaries: [
+ "android.hardware.gnss-service.example",
+ ],
+ prebuilts: [
+ "gnss-default.rc",
+ "gnss-default.xml",
+ "android.hardware.location.gps.prebuilt.xml", // permission
+ ],
+}
diff --git a/gnss/aidl/default/Gnss.cpp b/gnss/aidl/default/Gnss.cpp
index f1b9cbf..c31f991 100644
--- a/gnss/aidl/default/Gnss.cpp
+++ b/gnss/aidl/default/Gnss.cpp
@@ -115,7 +115,9 @@
mGnssMeasurementInterface->setLocationEnabled(true);
this->reportGnssStatusValue(IGnssCallback::GnssStatusValue::SESSION_BEGIN);
mThread = std::thread([this]() {
- this->reportSvStatus();
+ if (!mGnssMeasurementEnabled || mMinIntervalMs <= mGnssMeasurementIntervalMs) {
+ this->reportSvStatus();
+ }
if (!mFirstFixReceived) {
std::this_thread::sleep_for(std::chrono::milliseconds(TTFF_MILLIS));
mFirstFixReceived = true;
@@ -124,7 +126,9 @@
if (!mIsActive) {
break;
}
- this->reportSvStatus();
+ if (!mGnssMeasurementEnabled || mMinIntervalMs <= mGnssMeasurementIntervalMs) {
+ this->reportSvStatus();
+ }
this->reportNmea();
auto currentLocation = getLocationFromHW();
@@ -386,4 +390,12 @@
return ndk::ScopedAStatus::ok();
}
+void Gnss::setGnssMeasurementEnabled(const bool enabled) {
+ mGnssMeasurementEnabled = enabled;
+}
+
+void Gnss::setGnssMeasurementInterval(const long intervalMs) {
+ mGnssMeasurementIntervalMs = intervalMs;
+}
+
} // namespace aidl::android::hardware::gnss
diff --git a/gnss/aidl/default/Gnss.h b/gnss/aidl/default/Gnss.h
index 00540cd..245d607 100644
--- a/gnss/aidl/default/Gnss.h
+++ b/gnss/aidl/default/Gnss.h
@@ -85,6 +85,8 @@
override;
void reportSvStatus() const;
+ void setGnssMeasurementEnabled(const bool enabled);
+ void setGnssMeasurementInterval(const long intervalMs);
std::shared_ptr<GnssConfiguration> mGnssConfiguration;
std::shared_ptr<GnssPowerIndication> mGnssPowerIndication;
std::shared_ptr<GnssMeasurementInterface> mGnssMeasurementInterface;
@@ -101,10 +103,12 @@
static std::shared_ptr<IGnssCallback> sGnssCallback;
std::atomic<long> mMinIntervalMs;
+ std::atomic<long> mGnssMeasurementIntervalMs;
std::atomic<bool> mIsActive;
std::atomic<bool> mIsSvStatusActive;
std::atomic<bool> mIsNmeaActive;
std::atomic<bool> mFirstFixReceived;
+ std::atomic<bool> mGnssMeasurementEnabled;
std::thread mThread;
::android::hardware::gnss::common::ThreadBlocker mThreadBlocker;
diff --git a/gnss/aidl/default/GnssMeasurementInterface.cpp b/gnss/aidl/default/GnssMeasurementInterface.cpp
index aab9e03..f324213 100644
--- a/gnss/aidl/default/GnssMeasurementInterface.cpp
+++ b/gnss/aidl/default/GnssMeasurementInterface.cpp
@@ -35,7 +35,9 @@
std::shared_ptr<IGnssMeasurementCallback> GnssMeasurementInterface::sCallback = nullptr;
GnssMeasurementInterface::GnssMeasurementInterface()
- : mIntervalMs(1000), mLocationIntervalMs(1000), mFutures(std::vector<std::future<void>>()) {}
+ : mIntervalMs(1000), mLocationIntervalMs(1000) {
+ mThreads.reserve(2);
+}
GnssMeasurementInterface::~GnssMeasurementInterface() {
waitForStoppingThreads();
@@ -74,6 +76,7 @@
stop();
}
mIntervalMs = std::max(options.intervalMs, 1000);
+ mGnss->setGnssMeasurementInterval(mIntervalMs);
start(options.enableCorrVecOutputs, options.enableFullTracking);
return ndk::ScopedAStatus::ok();
@@ -100,12 +103,13 @@
ALOGD("restarting since measurement has started");
stop();
}
- // Wait for stopping previous thread.
- waitForStoppingThreads();
mIsActive = true;
- mThreadBlocker.reset();
- mThread = std::thread([this, enableCorrVecOutputs, enableFullTracking]() {
+ mGnss->setGnssMeasurementEnabled(true);
+ mThreads.emplace_back(std::thread([this, enableCorrVecOutputs, enableFullTracking]() {
+ waitForStoppingThreads();
+ mThreadBlocker.reset();
+
int intervalMs;
do {
if (!mIsActive) {
@@ -127,22 +131,30 @@
auto measurement =
Utils::getMockMeasurement(enableCorrVecOutputs, enableFullTracking);
this->reportMeasurement(measurement);
- if (!mLocationEnabled) {
+ if (!mLocationEnabled || mLocationIntervalMs > mIntervalMs) {
mGnss->reportSvStatus();
}
}
intervalMs =
(mLocationEnabled) ? std::min(mLocationIntervalMs, mIntervalMs) : mIntervalMs;
} while (mIsActive && mThreadBlocker.wait_for(std::chrono::milliseconds(intervalMs)));
- });
+ }));
}
void GnssMeasurementInterface::stop() {
ALOGD("stop");
mIsActive = false;
+ mGnss->setGnssMeasurementEnabled(false);
mThreadBlocker.notify();
- if (mThread.joinable()) {
- mFutures.push_back(std::async(std::launch::async, [this] { mThread.join(); }));
+ for (auto iter = mThreads.begin(); iter != mThreads.end(); ++iter) {
+ if (iter->joinable()) {
+ mFutures.push_back(std::async(std::launch::async, [this, iter] {
+ iter->join();
+ mThreads.erase(iter);
+ }));
+ } else {
+ mThreads.erase(iter);
+ }
}
}
diff --git a/gnss/aidl/default/GnssMeasurementInterface.h b/gnss/aidl/default/GnssMeasurementInterface.h
index 926a4e7..ea07c9a 100644
--- a/gnss/aidl/default/GnssMeasurementInterface.h
+++ b/gnss/aidl/default/GnssMeasurementInterface.h
@@ -52,7 +52,7 @@
std::atomic<long> mLocationIntervalMs;
std::atomic<bool> mIsActive;
std::atomic<bool> mLocationEnabled;
- std::thread mThread;
+ std::vector<std::thread> mThreads;
std::vector<std::future<void>> mFutures;
::android::hardware::gnss::common::ThreadBlocker mThreadBlocker;
diff --git a/gnss/aidl/default/GnssNavigationMessageInterface.cpp b/gnss/aidl/default/GnssNavigationMessageInterface.cpp
index 75b9624..c262dc6 100644
--- a/gnss/aidl/default/GnssNavigationMessageInterface.cpp
+++ b/gnss/aidl/default/GnssNavigationMessageInterface.cpp
@@ -29,7 +29,9 @@
std::shared_ptr<IGnssNavigationMessageCallback> GnssNavigationMessageInterface::sCallback = nullptr;
-GnssNavigationMessageInterface::GnssNavigationMessageInterface() : mMinIntervalMillis(1000) {}
+GnssNavigationMessageInterface::GnssNavigationMessageInterface() : mMinIntervalMillis(1000) {
+ mThreads.reserve(2);
+}
GnssNavigationMessageInterface::~GnssNavigationMessageInterface() {
waitForStoppingThreads();
@@ -61,11 +63,11 @@
ALOGD("restarting since nav msg has started");
stop();
}
- // Wait for stopping previous thread.
- waitForStoppingThreads();
mIsActive = true;
- mThread = std::thread([this]() {
+ mThreads.emplace_back(std::thread([this]() {
+ waitForStoppingThreads();
+ mThreadBlocker.reset();
do {
if (!mIsActive) {
break;
@@ -81,15 +83,22 @@
this->reportMessage(message);
} while (mIsActive &&
mThreadBlocker.wait_for(std::chrono::milliseconds(mMinIntervalMillis)));
- });
+ }));
}
void GnssNavigationMessageInterface::stop() {
ALOGD("stop");
mIsActive = false;
mThreadBlocker.notify();
- if (mThread.joinable()) {
- mFutures.push_back(std::async(std::launch::async, [this] { mThread.join(); }));
+ for (auto iter = mThreads.begin(); iter != mThreads.end(); ++iter) {
+ if (iter->joinable()) {
+ mFutures.push_back(std::async(std::launch::async, [this, iter] {
+ iter->join();
+ mThreads.erase(iter);
+ }));
+ } else {
+ mThreads.erase(iter);
+ }
}
}
diff --git a/gnss/aidl/default/GnssNavigationMessageInterface.h b/gnss/aidl/default/GnssNavigationMessageInterface.h
index b335348..e9a7536 100644
--- a/gnss/aidl/default/GnssNavigationMessageInterface.h
+++ b/gnss/aidl/default/GnssNavigationMessageInterface.h
@@ -40,7 +40,7 @@
std::atomic<long> mMinIntervalMillis;
std::atomic<bool> mIsActive;
- std::thread mThread;
+ std::vector<std::thread> mThreads;
std::vector<std::future<void>> mFutures;
::android::hardware::gnss::common::ThreadBlocker mThreadBlocker;
diff --git a/gnss/aidl/default/apex_file_contexts b/gnss/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..83b01ed
--- /dev/null
+++ b/gnss/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)? u:object_r:vendor_file:s0
+/etc(/.*)? u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.gnss-service\.example u:object_r:hal_gnss_default_exec:s0
diff --git a/gnss/aidl/default/apex_manifest.json b/gnss/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..9b2db23
--- /dev/null
+++ b/gnss/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.hardware.gnss",
+ "version": 1
+}
diff --git a/gnss/aidl/default/gnss-default.rc b/gnss/aidl/default/gnss-default.rc
index fe179c3..d47b3a5 100644
--- a/gnss/aidl/default/gnss-default.rc
+++ b/gnss/aidl/default/gnss-default.rc
@@ -1,4 +1,4 @@
-service vendor.gnss-default /vendor/bin/hw/android.hardware.gnss-service.example
+service vendor.gnss-default /apex/com.android.hardware.gnss/bin/hw/android.hardware.gnss-service.example
class hal
user nobody
group nobody
diff --git a/gnss/aidl/vts/gnss_hal_test_cases.cpp b/gnss/aidl/vts/gnss_hal_test_cases.cpp
index aa8bdfd..9381a0a 100644
--- a/gnss/aidl/vts/gnss_hal_test_cases.cpp
+++ b/gnss/aidl/vts/gnss_hal_test_cases.cpp
@@ -1430,13 +1430,13 @@
startMeasurementWithInterval(intervals[i], iGnssMeasurement, callback);
std::vector<int> measurementDeltas;
- std::vector<int> svInfoListTimestampsDeltas;
+ std::vector<int> svInfoListDeltas;
collectMeasurementIntervals(callback, numEvents[i], /* timeoutSeconds= */ 10,
measurementDeltas);
if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
- collectSvInfoListTimestamps(numEvents[i], /* timeoutSeconds= */ 10,
- svInfoListTimestampsDeltas);
+ collectSvInfoListTimestamps(numEvents[i], /* timeoutSeconds= */ 10, svInfoListDeltas);
+ EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
}
status = iGnssMeasurement->close();
ASSERT_TRUE(status.isOk());
@@ -1444,8 +1444,7 @@
assertMeanAndStdev(intervals[i], measurementDeltas);
if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
- assertMeanAndStdev(intervals[i], svInfoListTimestampsDeltas);
- EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+ assertMeanAndStdev(intervals[i], svInfoListDeltas);
}
}
}
@@ -1477,13 +1476,25 @@
auto callback = sp<GnssMeasurementCallbackAidl>::make();
startMeasurementWithInterval(intervalMs, iGnssMeasurement, callback);
- std::vector<int> deltas;
- collectMeasurementIntervals(callback, /*numEvents=*/10, /*timeoutSeconds=*/10, deltas);
+ std::vector<int> measurementDeltas;
+ std::vector<int> svInfoListDeltas;
+
+ collectMeasurementIntervals(callback, /*numEvents=*/10, /*timeoutSeconds=*/10,
+ measurementDeltas);
+ if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+ collectSvInfoListTimestamps(/*numEvents=*/10, /* timeoutSeconds= */ 10,
+ svInfoListDeltas);
+ EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+ }
status = iGnssMeasurement->close();
ASSERT_TRUE(status.isOk());
- assertMeanAndStdev(locationIntervalMs, deltas);
+ assertMeanAndStdev(locationIntervalMs, measurementDeltas);
+ if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+ // Verify the SvStatus interval is 1s (not 2s)
+ assertMeanAndStdev(locationIntervalMs, svInfoListDeltas);
+ }
}
StopAndClearLocations();
}
@@ -1516,16 +1527,37 @@
// Start location and verify the measurements are received at 1Hz
StartAndCheckFirstLocation(locationIntervalMs, /* lowPowerMode= */ false);
- std::vector<int> deltas;
- collectMeasurementIntervals(callback, /*numEvents=*/10, kFirstMeasTimeoutSec, deltas);
- assertMeanAndStdev(locationIntervalMs, deltas);
+ std::vector<int> measurementDeltas;
+ std::vector<int> svInfoListDeltas;
+ collectMeasurementIntervals(callback, /*numEvents=*/10, kFirstMeasTimeoutSec,
+ measurementDeltas);
+ assertMeanAndStdev(locationIntervalMs, measurementDeltas);
+ if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+ collectSvInfoListTimestamps(/*numEvents=*/10, /* timeoutSeconds= */ 10,
+ svInfoListDeltas);
+ EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+ // Verify the SvStatus intervals are at 1s interval
+ assertMeanAndStdev(locationIntervalMs, svInfoListDeltas);
+ }
// Stop location request and verify the measurements are received at 2s intervals
StopAndClearLocations();
- callback->gnss_data_cbq_.reset();
- deltas.clear();
- collectMeasurementIntervals(callback, /*numEvents=*/5, kFirstMeasTimeoutSec, deltas);
- assertMeanAndStdev(intervalMs, deltas);
+ measurementDeltas.clear();
+ collectMeasurementIntervals(callback, /*numEvents=*/5, kFirstMeasTimeoutSec,
+ measurementDeltas);
+ assertMeanAndStdev(intervalMs, measurementDeltas);
+
+ if (aidl_gnss_hal_->getInterfaceVersion() >= 3) {
+ svInfoListDeltas.clear();
+ collectSvInfoListTimestamps(/*numEvents=*/5, /* timeoutSeconds= */ 10,
+ svInfoListDeltas);
+ EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+ // Verify the SvStatus intervals are at 2s interval
+ for (const int& delta : svInfoListDeltas) {
+ ALOGD("svInfoListDelta: %d", delta);
+ }
+ assertMeanAndStdev(intervalMs, svInfoListDeltas);
+ }
status = iGnssMeasurement->close();
ASSERT_TRUE(status.isOk());
@@ -1587,8 +1619,7 @@
* TestGnssMeasurementIsFullTracking
* 1. Start measurement with enableFullTracking=true. Verify the received measurements have
* isFullTracking=true.
- * 2. Start measurement with enableFullTracking = false. Verify the received measurements have
- * isFullTracking=false.
+ * 2. Start measurement with enableFullTracking = false.
* 3. Do step 1 again.
*/
TEST_P(GnssHalTest, TestGnssMeasurementIsFullTracking) {
@@ -1675,4 +1706,59 @@
ASSERT_TRUE(accumulatedDeltaRangeFound);
status = iGnssMeasurement->close();
ASSERT_TRUE(status.isOk());
-}
\ No newline at end of file
+}
+
+/*
+ * TestSvStatusIntervals:
+ * 1. start measurement and location with various intervals
+ * 2. verify the SvStatus are received at expected interval
+ */
+TEST_P(GnssHalTest, TestSvStatusIntervals) {
+ if (aidl_gnss_hal_->getInterfaceVersion() <= 2) {
+ return;
+ }
+ ALOGD("TestSvStatusIntervals");
+ sp<IGnssMeasurementInterface> iGnssMeasurement;
+ auto status = aidl_gnss_hal_->getExtensionGnssMeasurement(&iGnssMeasurement);
+ ASSERT_TRUE(status.isOk());
+ ASSERT_TRUE(iGnssMeasurement != nullptr);
+
+ std::vector<int> locationIntervals{1000, 2000, INT_MAX};
+ std::vector<int> measurementIntervals{1000, 2000, INT_MAX};
+
+ for (auto& locationIntervalMs : locationIntervals) {
+ for (auto& measurementIntervalMs : measurementIntervals) {
+ if (locationIntervalMs == INT_MAX && measurementIntervalMs == INT_MAX) {
+ continue;
+ }
+ auto measurementCallback = sp<GnssMeasurementCallbackAidl>::make();
+ // Start measurement
+ if (measurementIntervalMs < INT_MAX) {
+ startMeasurementWithInterval(measurementIntervalMs, iGnssMeasurement,
+ measurementCallback);
+ }
+ // Start location
+ if (locationIntervalMs < INT_MAX) {
+ StartAndCheckFirstLocation(locationIntervalMs, /* lowPowerMode= */ false);
+ }
+ ALOGD("location@%d(ms), measurement@%d(ms)", locationIntervalMs, measurementIntervalMs);
+ std::vector<int> svInfoListDeltas;
+ collectSvInfoListTimestamps(/*numEvents=*/5, /* timeoutSeconds= */ 10,
+ svInfoListDeltas);
+ EXPECT_TRUE(aidl_gnss_cb_->sv_info_list_cbq_.size() > 0);
+
+ int svStatusInterval = std::min(locationIntervalMs, measurementIntervalMs);
+ assertMeanAndStdev(svStatusInterval, svInfoListDeltas);
+
+ if (locationIntervalMs < INT_MAX) {
+ // Stop location request
+ StopAndClearLocations();
+ }
+ if (measurementIntervalMs < INT_MAX) {
+ // Stop measurement request
+ status = iGnssMeasurement->close();
+ ASSERT_TRUE(status.isOk());
+ }
+ }
+ }
+}
diff --git a/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp b/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
index bf074e8..55c2630 100644
--- a/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
+++ b/keymaster/4.0/support/fuzzer/keymaster4_utils_fuzzer.cpp
@@ -46,33 +46,42 @@
support::getOsVersion();
support::getOsPatchlevel();
- VerificationToken token;
- token.challenge = mFdp->ConsumeIntegral<uint64_t>();
- token.timestamp = mFdp->ConsumeIntegral<uint64_t>();
- token.securityLevel = mFdp->PickValueInArray(kSecurityLevel);
- size_t vectorSize = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxVectorSize);
- token.mac.resize(vectorSize);
- for (size_t n = 0; n < vectorSize; ++n) {
- token.mac[n] = n;
+ while (mFdp->remaining_bytes() > 0) {
+ auto keymaster_function = mFdp->PickValueInArray<const std::function<void()>>({
+ [&]() {
+ VerificationToken token;
+ token.challenge = mFdp->ConsumeIntegral<uint64_t>();
+ token.timestamp = mFdp->ConsumeIntegral<uint64_t>();
+ token.securityLevel = mFdp->PickValueInArray(kSecurityLevel);
+ size_t vectorSize = mFdp->ConsumeIntegralInRange<size_t>(0, kMaxVectorSize);
+ token.mac.resize(vectorSize);
+ for (size_t n = 0; n < vectorSize; ++n) {
+ token.mac[n] = mFdp->ConsumeIntegral<uint8_t>();
+ }
+ std::optional<std::vector<uint8_t>> serialized =
+ serializeVerificationToken(token);
+ if (serialized.has_value()) {
+ std::optional<VerificationToken> deserialized =
+ deserializeVerificationToken(serialized.value());
+ }
+ },
+ [&]() {
+ std::vector<uint8_t> dataVector;
+ size_t size = mFdp->ConsumeIntegralInRange<size_t>(0, sizeof(hw_auth_token_t));
+ dataVector = mFdp->ConsumeBytes<uint8_t>(size);
+ support::blob2hidlVec(dataVector.data(), dataVector.size());
+ support::blob2hidlVec(dataVector);
+ HardwareAuthToken authToken = support::hidlVec2AuthToken(dataVector);
+ hidl_vec<uint8_t> volatile hidlVector = support::authToken2HidlVec(authToken);
+ },
+ [&]() {
+ std::string str = mFdp->ConsumeRandomLengthString(kMaxCharacters);
+ support::blob2hidlVec(str);
+ },
+ });
+ keymaster_function();
}
- std::optional<std::vector<uint8_t>> serialized = serializeVerificationToken(token);
- if (serialized.has_value()) {
- std::optional<VerificationToken> deserialized =
- deserializeVerificationToken(serialized.value());
- }
-
- std::vector<uint8_t> dataVector;
- size_t size = mFdp->ConsumeIntegralInRange<size_t>(0, sizeof(hw_auth_token_t));
- dataVector = mFdp->ConsumeBytes<uint8_t>(size);
- support::blob2hidlVec(dataVector.data(), dataVector.size());
-
- support::blob2hidlVec(dataVector);
-
- std::string str = mFdp->ConsumeRandomLengthString(kMaxCharacters);
- support::blob2hidlVec(str);
-
- HardwareAuthToken authToken = support::hidlVec2AuthToken(dataVector);
- hidl_vec<uint8_t> volatile hidlVector = support::authToken2HidlVec(authToken);
+ return;
}
void KeyMaster4UtilsFuzzer::process(const uint8_t* data, size_t size) {
diff --git a/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl b/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
index c7d8a97..7d58340 100644
--- a/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
+++ b/media/c2/aidl/aidl_api/android.hardware.media.c2/current/android/hardware/media/c2/IComponent.aidl
@@ -49,8 +49,12 @@
long blockPoolId;
android.hardware.media.c2.IConfigurable configurable;
}
+ parcelable C2AidlGbAllocator {
+ android.hardware.media.c2.IGraphicBufferAllocator igba;
+ ParcelFileDescriptor waitableFd;
+ }
union BlockPoolAllocator {
int allocatorId;
- android.hardware.media.c2.IGraphicBufferAllocator igba;
+ android.hardware.media.c2.IComponent.C2AidlGbAllocator allocator;
}
}
diff --git a/media/c2/aidl/android/hardware/media/c2/IComponent.aidl b/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
index a7d94b1..fc923ab 100644
--- a/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
+++ b/media/c2/aidl/android/hardware/media/c2/IComponent.aidl
@@ -22,6 +22,8 @@
import android.hardware.media.c2.IConfigurable;
import android.hardware.media.c2.IGraphicBufferAllocator;
import android.hardware.media.c2.WorkBundle;
+import android.os.ParcelFileDescriptor;
+
/**
* Interface for an AIDL Codec2 component.
@@ -46,6 +48,18 @@
}
/**
+ * C2AIDL allocator interface along with a waitable fd.
+ *
+ * The interface is used from a specific type of C2BlockPool to allocate
+ * graphic blocks. the waitable fd is used to create a specific type of
+ * C2Fence which can be used for waiting until to allocate is not blocked.
+ */
+ parcelable C2AidlGbAllocator {
+ IGraphicBufferAllocator igba;
+ ParcelFileDescriptor waitableFd;
+ }
+
+ /**
* Allocator for C2BlockPool.
*
* C2BlockPool will use a C2Allocator which is specified by an id.
@@ -53,7 +67,7 @@
*/
union BlockPoolAllocator {
int allocatorId;
- IGraphicBufferAllocator igba;
+ C2AidlGbAllocator allocator;
}
/**
diff --git a/radio/aidl/aidl_api/android.hardware.radio.modem/current/android/hardware/radio/modem/IRadioModemIndication.aidl b/radio/aidl/aidl_api/android.hardware.radio.modem/current/android/hardware/radio/modem/IRadioModemIndication.aidl
index 20066f8..3c06877 100644
--- a/radio/aidl/aidl_api/android.hardware.radio.modem/current/android/hardware/radio/modem/IRadioModemIndication.aidl
+++ b/radio/aidl/aidl_api/android.hardware.radio.modem/current/android/hardware/radio/modem/IRadioModemIndication.aidl
@@ -40,4 +40,5 @@
oneway void radioCapabilityIndication(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.modem.RadioCapability rc);
oneway void radioStateChanged(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.modem.RadioState radioState);
oneway void rilConnected(in android.hardware.radio.RadioIndicationType type);
+ oneway void onImeiMappingChanged(in android.hardware.radio.RadioIndicationType type, in android.hardware.radio.modem.ImeiInfo imeiInfo);
}
diff --git a/radio/aidl/android/hardware/radio/modem/IRadioModemIndication.aidl b/radio/aidl/android/hardware/radio/modem/IRadioModemIndication.aidl
index 4b98277..ba3c510 100644
--- a/radio/aidl/android/hardware/radio/modem/IRadioModemIndication.aidl
+++ b/radio/aidl/android/hardware/radio/modem/IRadioModemIndication.aidl
@@ -20,6 +20,7 @@
import android.hardware.radio.modem.HardwareConfig;
import android.hardware.radio.modem.RadioCapability;
import android.hardware.radio.modem.RadioState;
+import android.hardware.radio.modem.ImeiInfo;
/**
* Interface declaring unsolicited radio indications for modem APIs.
@@ -76,4 +77,12 @@
* @param type Type of radio indication
*/
void rilConnected(in RadioIndicationType type);
+
+ /**
+ * Indicates when there is a change in the IMEI mapping.
+ *
+ * @param type Type of radio indication
+ * @param imeiInfo IMEI information
+ */
+ void onImeiMappingChanged(in RadioIndicationType type, in ImeiInfo imeiInfo);
}
diff --git a/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioIndication.h b/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioIndication.h
index f042456..e6f2516 100644
--- a/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioIndication.h
+++ b/radio/aidl/compat/libradiocompat/include/libradiocompat/RadioIndication.h
@@ -26,6 +26,7 @@
#include <aidl/android/hardware/radio/sim/IRadioSimIndication.h>
#include <aidl/android/hardware/radio/voice/IRadioVoiceIndication.h>
#include <android/hardware/radio/1.6/IRadioIndication.h>
+#include <aidl/android/hardware/radio/modem/ImeiInfo.h>
namespace android::hardware::radio::compat {
@@ -208,7 +209,8 @@
Return<void> simPhonebookRecordsReceived(
V1_0::RadioIndicationType type, V1_6::PbReceivedStatus status,
const hidl_vec<V1_6::PhonebookRecordInfo>& records) override;
-
+ Return<void> onImeiMappingChanged(V1_0::RadioIndicationType type,
+ ::aidl::android::hardware::radio::modem::ImeiInfo config);
public:
RadioIndication(std::shared_ptr<DriverContext> context);
diff --git a/radio/aidl/compat/libradiocompat/modem/RadioIndication-modem.cpp b/radio/aidl/compat/libradiocompat/modem/RadioIndication-modem.cpp
index 851c93b..990ccff 100644
--- a/radio/aidl/compat/libradiocompat/modem/RadioIndication-modem.cpp
+++ b/radio/aidl/compat/libradiocompat/modem/RadioIndication-modem.cpp
@@ -68,4 +68,11 @@
return {};
}
+Return<void> RadioIndication::onImeiMappingChanged(V1_0::RadioIndicationType type,
+ ::aidl::android::hardware::radio::modem::ImeiInfo imeiInfo) {
+ LOG_CALL << type;
+ modemCb()->onImeiMappingChanged(toAidl(type), imeiInfo);
+ return {};
+}
+
} // namespace android::hardware::radio::compat
diff --git a/radio/aidl/vts/radio_modem_indication.cpp b/radio/aidl/vts/radio_modem_indication.cpp
index 0bfcd66..9f63cb0 100644
--- a/radio/aidl/vts/radio_modem_indication.cpp
+++ b/radio/aidl/vts/radio_modem_indication.cpp
@@ -41,3 +41,8 @@
ndk::ScopedAStatus RadioModemIndication::rilConnected(RadioIndicationType /*type*/) {
return ndk::ScopedAStatus::ok();
}
+
+ndk::ScopedAStatus RadioModemIndication::onImeiMappingChanged(RadioIndicationType /*type*/,
+ const ::aidl::android::hardware::radio::modem::ImeiInfo& /*imeiInfo*/) {
+ return ndk::ScopedAStatus::ok();
+}
diff --git a/radio/aidl/vts/radio_modem_utils.h b/radio/aidl/vts/radio_modem_utils.h
index d47bdeb..aa99ea3 100644
--- a/radio/aidl/vts/radio_modem_utils.h
+++ b/radio/aidl/vts/radio_modem_utils.h
@@ -109,6 +109,9 @@
RadioState radioState) override;
virtual ndk::ScopedAStatus rilConnected(RadioIndicationType type) override;
+
+ virtual ndk::ScopedAStatus onImeiMappingChanged(RadioIndicationType type,
+ const ::aidl::android::hardware::radio::modem::ImeiInfo& imeiInfo) override;
};
// The main test class for Radio AIDL Modem.
diff --git a/rebootescrow/aidl/default/Android.bp b/rebootescrow/aidl/default/Android.bp
index 4409314..7f9b6d6 100644
--- a/rebootescrow/aidl/default/Android.bp
+++ b/rebootescrow/aidl/default/Android.bp
@@ -42,10 +42,10 @@
cc_binary {
name: "android.hardware.rebootescrow-service.default",
- init_rc: ["rebootescrow-default.rc"],
relative_install_path: "hw",
- vintf_fragments: ["rebootescrow-default.xml"],
vendor: true,
+ installable: false, // installed in APEX
+
srcs: [
"service.cpp",
],
@@ -53,12 +53,14 @@
"-Wall",
"-Werror",
],
+ stl: "c++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
- "android.hardware.rebootescrow-V1-ndk",
+ "liblog",
],
static_libs: [
+ "android.hardware.rebootescrow-V1-ndk",
+ "libbase",
"libhadamardutils",
"librebootescrowdefaultimpl",
],
@@ -97,3 +99,35 @@
],
test_suites: ["device-tests"],
}
+
+prebuilt_etc {
+ name: "rebootescrow-default.rc",
+ src: "rebootescrow-default.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "rebootescrow-default.xml",
+ src: "rebootescrow-default.xml",
+ sub_dir: "vintf",
+ installable: false,
+}
+
+apex {
+ name: "com.android.hardware.rebootescrow",
+ manifest: "apex_manifest.json",
+ file_contexts: "apex_file_contexts",
+ key: "com.android.hardware.key",
+ certificate: ":com.android.hardware.certificate",
+ vendor: true,
+ updatable: false,
+
+ binaries: [
+ "android.hardware.rebootescrow-service.default",
+ ],
+ prebuilts: [
+ "rebootescrow-default.rc",
+ "rebootescrow-default.xml",
+ "android.hardware.reboot_escrow.prebuilt.xml", // <feature>
+ ],
+}
diff --git a/rebootescrow/aidl/default/apex_file_contexts b/rebootescrow/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..aa84984
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)? u:object_r:vendor_file:s0
+/etc(/.*)? u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.rebootescrow-service\.default u:object_r:hal_rebootescrow_default_exec:s0
diff --git a/rebootescrow/aidl/default/apex_manifest.json b/rebootescrow/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..8be495b
--- /dev/null
+++ b/rebootescrow/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.hardware.rebootescrow",
+ "version": 1
+}
\ No newline at end of file
diff --git a/rebootescrow/aidl/default/rebootescrow-default.rc b/rebootescrow/aidl/default/rebootescrow-default.rc
index ad90465..024dd6d 100644
--- a/rebootescrow/aidl/default/rebootescrow-default.rc
+++ b/rebootescrow/aidl/default/rebootescrow-default.rc
@@ -1,4 +1,4 @@
-service vendor.rebootescrow-default /vendor/bin/hw/android.hardware.rebootescrow-service.default
+service vendor.rebootescrow-default /apex/com.android.hardware.rebootescrow/bin/hw/android.hardware.rebootescrow-service.default
interface aidl android.hardware.rebootescrow.IRebootEscrow/default
class hal
user system
diff --git a/secure_element/aidl/default/Android.bp b/secure_element/aidl/default/Android.bp
index d1bb393..b382822 100644
--- a/secure_element/aidl/default/Android.bp
+++ b/secure_element/aidl/default/Android.bp
@@ -11,14 +11,50 @@
name: "android.hardware.secure_element-service.example",
relative_install_path: "hw",
vendor: true,
- init_rc: ["secure_element.rc"],
- vintf_fragments: ["secure_element.xml"],
+ installable: false, // installed in APEX
+
+ stl: "c++_static",
shared_libs: [
- "libbase",
"libbinder_ndk",
+ "liblog",
+ ],
+ static_libs: [
"android.hardware.secure_element-V1-ndk",
+ "libbase",
],
srcs: [
"main.cpp",
],
}
+
+prebuilt_etc {
+ name: "secure_element.rc",
+ src: "secure_element.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "secure_element.xml",
+ src: "secure_element.xml",
+ sub_dir: "vintf",
+ installable: false,
+}
+
+apex {
+ name: "com.android.hardware.secure_element",
+ manifest: "apex_manifest.json",
+ file_contexts: "apex_file_contexts",
+ key: "com.android.hardware.key",
+ certificate: ":com.android.hardware.certificate",
+ vendor: true,
+ updatable: false,
+
+ binaries: [
+ "android.hardware.secure_element-service.example",
+ ],
+ prebuilts: [
+ "secure_element.rc",
+ "secure_element.xml",
+ "android.hardware.se.omapi.ese.prebuilt.xml", // <feature>
+ ],
+}
diff --git a/secure_element/aidl/default/apex_file_contexts b/secure_element/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..e9e811e
--- /dev/null
+++ b/secure_element/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)? u:object_r:vendor_file:s0
+/etc(/.*)? u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.secure_element-service\.example u:object_r:hal_secure_element_default_exec:s0
\ No newline at end of file
diff --git a/secure_element/aidl/default/apex_manifest.json b/secure_element/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..6e04c11
--- /dev/null
+++ b/secure_element/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.hardware.secure_element",
+ "version": 1
+}
\ No newline at end of file
diff --git a/secure_element/aidl/default/secure_element.rc b/secure_element/aidl/default/secure_element.rc
index 7d21666..b74b2ee 100644
--- a/secure_element/aidl/default/secure_element.rc
+++ b/secure_element/aidl/default/secure_element.rc
@@ -1,4 +1,4 @@
-service vendor.secure_element /vendor/bin/hw/android.hardware.secure_element-service.example
+service vendor.secure_element /apex/com.android.hardware.secure_element/bin/hw/android.hardware.secure_element-service.example
class hal
user nobody
group nobody
diff --git a/security/authgraph/aidl/Android.bp b/security/authgraph/aidl/Android.bp
new file mode 100644
index 0000000..d94f640
--- /dev/null
+++ b/security/authgraph/aidl/Android.bp
@@ -0,0 +1,88 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // 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"],
+}
+
+aidl_interface {
+ name: "android.hardware.security.authgraph",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/security/authgraph/*.aidl",
+ ],
+ stability: "vintf",
+ frozen: false,
+ backend: {
+ java: {
+ platform_apis: true,
+ },
+ ndk: {
+ apps_enabled: false,
+ },
+ rust: {
+ enabled: true,
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.virt",
+ ],
+ },
+ },
+}
+
+// cc_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this cc_defaults to avoid
+// managing dependency versions explicitly.
+cc_defaults {
+ name: "authgraph_use_latest_hal_aidl_ndk_static",
+ static_libs: [
+ "android.hardware.security.authgraph-V1-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "authgraph_use_latest_hal_aidl_ndk_shared",
+ shared_libs: [
+ "android.hardware.security.authgraph-V1-ndk",
+ ],
+}
+
+cc_defaults {
+ name: "authgraph_use_latest_hal_aidl_cpp_static",
+ static_libs: [
+ "android.hardware.security.authgraph-V1-cpp",
+ ],
+}
+
+cc_defaults {
+ name: "authgraph_use_latest_hal_aidl_cpp_shared",
+ shared_libs: [
+ "android.hardware.security.authgraph-V1-cpp",
+ ],
+}
+
+// A rust_defaults that includes the latest Authgraph AIDL library.
+// Modules that depend on Authgraph directly can include this rust_defaults to avoid
+// managing dependency versions explicitly.
+rust_defaults {
+ name: "authgraph_use_latest_hal_aidl_rust",
+ rustlibs: [
+ "android.hardware.security.authgraph-V1-rust",
+ ],
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl
new file mode 100644
index 0000000..dc86fbd
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Arc.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+/* @hide */
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Arc {
+ byte[] arc;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl
new file mode 100644
index 0000000..1a78b54
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Error.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+/* @hide */
+@Backing(type="int") @VintfStability
+enum Error {
+ OK = 0,
+ INVALID_PEER_NONCE = (-1) /* -1 */,
+ INVALID_PEER_KE_KEY = (-2) /* -2 */,
+ INVALID_IDENTITY = (-3) /* -3 */,
+ INVALID_CERT_CHAIN = (-4) /* -4 */,
+ INVALID_SIGNATURE = (-5) /* -5 */,
+ INVALID_KE_KEY = (-6) /* -6 */,
+ INVALID_PUB_KEY_IN_KEY = (-7) /* -7 */,
+ INVALID_PRIV_KEY_ARC_IN_KEY = (-8) /* -8 */,
+ INVALID_SHARED_KEY_ARCS = (-9) /* -9 */,
+ MEMORY_ALLOCATION_FAILED = (-10) /* -10 */,
+ INCOMPATIBLE_PROTOCOL_VERSION = (-11) /* -11 */,
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
new file mode 100644
index 0000000..2c56f33
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+/* @hide */
+@VintfStability
+interface IAuthGraphKeyExchange {
+ android.hardware.security.authgraph.SessionInitiationInfo create();
+ android.hardware.security.authgraph.KeInitResult init(in android.hardware.security.authgraph.PubKey peerPubKey, in android.hardware.security.authgraph.Identity peerId, in byte[] peerNonce, in int peerVersion);
+ android.hardware.security.authgraph.SessionInfo finish(in android.hardware.security.authgraph.PubKey peerPubKey, in android.hardware.security.authgraph.Identity peerId, in android.hardware.security.authgraph.SessionIdSignature peerSignature, in byte[] peerNonce, in int peerVersion, in android.hardware.security.authgraph.Key ownKey);
+ android.hardware.security.authgraph.Arc[2] authenticationComplete(in android.hardware.security.authgraph.SessionIdSignature peerSignature, in android.hardware.security.authgraph.Arc[2] sharedKeys);
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl
new file mode 100644
index 0000000..bd5453e
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Identity.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Identity {
+ byte[] identity;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl
new file mode 100644
index 0000000..8c91523
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/KeInitResult.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable KeInitResult {
+ android.hardware.security.authgraph.SessionInitiationInfo sessionInitiationInfo;
+ android.hardware.security.authgraph.SessionInfo sessionInfo;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl
new file mode 100644
index 0000000..5b4ebbf
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/Key.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable Key {
+ @nullable android.hardware.security.authgraph.PubKey pubKey;
+ @nullable android.hardware.security.authgraph.Arc arcFromPBK;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl
new file mode 100644
index 0000000..f070bfa
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PlainPubKey.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable PlainPubKey {
+ byte[] plainPubKey;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl
new file mode 100644
index 0000000..4c3376e
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/PubKey.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+union PubKey {
+ android.hardware.security.authgraph.PlainPubKey plainKey;
+ android.hardware.security.authgraph.SignedPubKey signedKey;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl
new file mode 100644
index 0000000..6dabc0a
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionIdSignature.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionIdSignature {
+ byte[] signature;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl
new file mode 100644
index 0000000..427962b
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInfo.aidl
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionInfo {
+ android.hardware.security.authgraph.Arc[2] sharedKeys;
+ byte[] sessionId;
+ android.hardware.security.authgraph.SessionIdSignature signature;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl
new file mode 100644
index 0000000..bf55e74
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SessionInitiationInfo.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SessionInitiationInfo {
+ android.hardware.security.authgraph.Key key;
+ android.hardware.security.authgraph.Identity identity;
+ byte[] nonce;
+ int version;
+}
diff --git a/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl
new file mode 100644
index 0000000..3dbaed8
--- /dev/null
+++ b/security/authgraph/aidl/aidl_api/android.hardware.security.authgraph/current/android/hardware/security/authgraph/SignedPubKey.aidl
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.security.authgraph;
+@RustDerive(Clone=true, Eq=true, PartialEq=true) @VintfStability
+parcelable SignedPubKey {
+ byte[] signedPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl
new file mode 100644
index 0000000..855ce5c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.aidl
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * This is the definition of the data format of an Arc.
+ * @hide
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Arc {
+ /**
+ * The messages exchanged between the domains in the AuthGraph protocol are called Arcs.
+ * An arc is simply AES-GCM. Encryption of a payload P with a key K and additional
+ * authentication data (AAD) D: (i.e. Arc = Enc(K, P, D)).
+ *
+ * Data is CBOR-encoded according to the `Arc` CDDL definition in Arc.cddl.
+ */
+ byte[] arc;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl
new file mode 100644
index 0000000..4c1b965
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Arc.cddl
@@ -0,0 +1,115 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+Arc = [ ; COSE_Encrypt0 [RFC9052 s5.2]
+ protected : bstr .cbor ArcProtectedHeaders,
+ unprotected : {
+ 5 : bstr .size 12 ; IV
+ },
+ ciphertext : bstr ; Enc(K, bstr .cbor Payload, encoded ArcEncStruct)
+]
+
+ArcProtectedHeaders = {
+ 1 : 3, ; Algorithm: AES-GCM mode w/ 256-bit key, 128-bit tag
+ ? -70001 : { + Permission }, ; One or more Permissions
+ ? -70002 : { + Limitation }, ; One or more Limitations
+ ? -70003 : int, ; Timestamp in milliseconds since some starting point (generally
+ ; the most recent device boot) which all of the applications within
+ ; the secure domain must agree upon
+ ? -70004 : bstr .size 16, ; Nonce used in key exchange methods
+ ? -70005 : PayloadType, ; Payload type, if needed to disambiguate, when processing an arc
+ ? -70006 : int, ; Version of the payload structure (if applicable)
+ ? -70007 : int, ; Sequence number (if needed to prevent replay attacks)
+ ? -70008 : Direction ; Direction of the encryption key (i.e. whether it is used to
+ ; encrypt incoming messages or outgoing messages)
+ ? -70009 : bool, ; "authentication_completed" - this is used during authenticated
+ ; key exchange indicate whether signature verification is done
+ ? -70010 : bstr .size 32 ; "session_id" computed during key exchange protocol
+}
+
+; Permissions indicate what an arc can be used with. Permissions are added to an arc during the
+; `create()` primitive operation and are propagated during `mint` and `snap` primitive operations.
+Permission = &(
+ -4770552 : IdentityEncoded, ; "source_id" - in the operations performed by a source, the
+ ; source adds its own identity to the permissions of an arc.
+ -4770553 : IdentityEncoded, ; "sink_id" - in the operations performed by a sink, the sink
+ ; adds its own identity to the permissions of an arc.
+ -4770555 : [ +IdentityEncoded ] ; "minting_allowed" - defines the set of TA identities
+ ; to whom the payload key is allowed to be minted.
+ -4770556 : bool ; "deleted_on_biometric_change" - A Boolean value that
+ ; indicates whether an auth key issued from a biometric TA is
+ ; invalidated on new biometric enrollment or removal of all
+ ; biometrics.
+)
+
+; Limitations indicate what restrictions are applied on the usage of an arc. Permissions are added
+; to an arc during the `create` primitive operation and are propagated during `snap` primitive
+; operation.
+Limitation = &(
+ -4770554 : bstr, ; "challenge" - is added to an arc that transfers an auth key to a channel
+ ; key, in order to ensure the freshness of the authentication.
+ ; A challenge is issued by a sink (e.g. Keymint TA, Biometric TAs).
+)
+
+; INCLUDE Identity.cddl for: Identity
+IdentityEncoded = bstr .cbor Identity
+
+Direction = &(
+ In: 1,
+ Out: 2,
+)
+
+PayloadType = &(
+ SecretKey: 1,
+ Arc: 2,
+ ; Any other payload types should also be defined here
+)
+
+Payload = &(
+ SecretKey,
+ Arc,
+ ; Any other payload formats should also be defined here
+)
+
+SecretKey = &( ; One of the payload types of an Arc is a secret key
+ SymmetricKey,
+ ECPrivateKey, ; Private key of a key pair generated for key exchange
+)
+
+ECPrivateKey = { ; COSE_Key [RFC9052 s7]
+ 1 : 2, ; Key type : EC2
+ 3 : -25, ; Algorithm: ECDH ES w/ HKDF 256 - generate key directly
+ ? 4 : [7], ; Key_ops: [derive key]
+ -1 : 1, ; Curve: P-256
+ ? -2 : bstr, ; x coordinate
+ ? -3 : bstr, ; y coordinate
+ -4 : bstr, ; private key (d)
+}
+
+SymmetricKey = { ; COSE_Key [RFC9052 s7] - For symmetric key encryption
+ 1 : 4, ; Key type : Symmetric
+ 3 : 3, ; Algorithm : AES-GCM mode w/ 256-bit key, 128-bit tag
+ 4 : [ 4 ], ; Key_ops: [decrypt]
+ -1 : bstr .size 32, ; Key value (k)
+}
+
+ArcEncStruct = [ ; COSE_Enc_structure [RFC9052 s5.3]
+ context : "Encrypt0",
+ protected : bstr .cbor ArcProtectedHeaders,
+ external_aad : bstr .size 0,
+]
+
+; INCLUDE generateCertificateRequestV2.cddl for: PubKeyEd25519, PubKeyECDSA256, PubKeyECDSA384
+; from hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl
new file mode 100644
index 0000000..a7dcbc6
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/DicePolicy.cddl
@@ -0,0 +1,33 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+DicePolicy = [
+ 1, ; dice policy version
+ + nodeConstraintList ; for each entry in dice chain
+]
+
+nodeConstraintList = [
+ * nodeConstraint
+]
+
+; We may add a hashConstraint item later
+nodeConstraint = exactMatchConstraint / geConstraint
+
+exactMatchConstraint = [1, keySpec, value]
+geConstraint = [2, keySpec, int]
+
+keySpec = [value+]
+
+value = bool / int / tstr / bstr
\ No newline at end of file
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl
new file mode 100644
index 0000000..1ad6054
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Error.aidl
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * AuthGraph error codes. Aidl will return these error codes as service specific errors in
+ * EX_SERVICE_SPECIFIC.
+ * @hide
+ */
+@VintfStability
+@Backing(type="int")
+enum Error {
+ /* Success */
+ OK = 0,
+ /* Invalid peer nonce for key exchange */
+ INVALID_PEER_NONCE = -1,
+ /* Invalid key exchange public key by the peer */
+ INVALID_PEER_KE_KEY = -2,
+ /* Invalid identity of the peer */
+ INVALID_IDENTITY = -3,
+ /* Invalid certificate chain in the identity of the peer */
+ INVALID_CERT_CHAIN = -4,
+ /* Invalid signature by the peer */
+ INVALID_SIGNATURE = -5,
+ /* Invalid key exchange key created by a particular party themselves to be used as a handle */
+ INVALID_KE_KEY = -6,
+ /* Invalid public key in the `Key` struct */
+ INVALID_PUB_KEY_IN_KEY = -7,
+ /* Invalid private key arc in the `Key` struct */
+ INVALID_PRIV_KEY_ARC_IN_KEY = -8,
+ /* Invalid shared key arcs */
+ INVALID_SHARED_KEY_ARCS = -9,
+ /* Memory allocation failed */
+ MEMORY_ALLOCATION_FAILED = -10,
+ /* The protocol version negotiated with the sink is incompatible */
+ INCOMPATIBLE_PROTOCOL_VERSION = -11,
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl
new file mode 100644
index 0000000..3de5617
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/ExplicitKeyDiceCertChain.cddl
@@ -0,0 +1,30 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+ExplicitKeyDiceCertChain = [
+ 1, ; version, hopefully will never change
+ DiceCertChainInitialPayload,
+ * DiceChainEntry
+]
+
+DiceCertChainInitialPayload = {
+ -4670552 : bstr .cbor PubKeyEd25519 /
+ bstr .cbor PubKeyECDSA256 /
+ bstr .cbor PubKeyECDSA384 ; subjectPublicKey
+}
+
+; INCLUDE generateCertificateRequestV2.cddl for: PubKeyEd25519, PubKeyECDSA256, PubKeyECDSA384,
+; DiceChainEntry
+; from hardware/interfaces/security/rkp/aidl/android/hardware/security/keymint/
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
new file mode 100644
index 0000000..6ceb09c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.aidl
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.Identity;
+import android.hardware.security.authgraph.KeInitResult;
+import android.hardware.security.authgraph.Key;
+import android.hardware.security.authgraph.PubKey;
+import android.hardware.security.authgraph.SessionIdSignature;
+import android.hardware.security.authgraph.SessionInfo;
+import android.hardware.security.authgraph.SessionInitiationInfo;
+
+/**
+ * AuthGraph interface definition for authenticated key exchange between two parties: P1 (source)
+ * and P2 (sink).
+ * Pre-requisites: each participant should have a:
+ * 1. Persistent identity - e.g. a signing key pair with a self signed certificate or a DICE
+ * certificate chain.
+ * 2. A symmetric encryption key kept in memory with per-boot life time of the participant
+ * (a.k.a per-boot key)
+ *
+ * ErrorCodes are defined in android.hardware.security.authgraph.ErrorCode.aidl.
+ * @hide
+ */
+@VintfStability
+interface IAuthGraphKeyExchange {
+ /**
+ * This method is invoked on P1 (source).
+ * Create an ephermeral EC key pair on NIST curve P-256 and a nonce (of 16 bytes) for
+ * key exchange.
+ *
+ * @return SessionInitiationInfo including the `Key` containing the public key of the created
+ * key pair and an arc from the per-boot key to the private key, the nonce, the persistent
+ * identity and the latest protocol version (i.e. AIDL version) supported.
+ *
+ * Note: The arc from the per-boot key to the private key in `Key` of the return type:
+ * `SessionInitiationInfo` serves two purposes:
+ * i. A mapping to correlate `create` and `finish` calls to P1 in a particular instance of the
+ * key exchange protocol.
+ * ii.A way to minimize the in-memory storage (P1 can include the nonce in the protected headers
+ * of the arc).
+ * However, P1 should maintain some form of in-memory record to be able to verify that the input
+ * `Key` sent to `finish` is from an unfinished instance of a key exchange protocol, to prevent
+ * any replay attacks in `finish`.
+ */
+ SessionInitiationInfo create();
+
+ /**
+ * This method is invoked on P2 (sink).
+ * Perform the following steps for key exchange:
+ * 0. If either `peerPubKey`, `peerId`, `peerNonce` is not in the expected format, return
+ * errors: INVALID_PEER_KE_KEY, INVALID_IDENTITY, INVALID_PEER_NONCE respectively.
+ * 1. Create an ephemeral EC key pair on NIST curve P-256.
+ * 2. Create a nonce (of 16 bytes).
+ * 3. Compute the diffie-hellman shared secret: Z.
+ * 4. Compute a salt = bstr .cbor [
+ * source_version: int, ; from input `peerVersion`
+ * sink_pub_key: bstr .cbor PlainPubKey, ; from step #1
+ * source_pub_key: bstr .cbor PlainPubKey, ; from input `peerPubKey`
+ * sink_nonce: bstr .size 16, ; from step #2
+ * source_nonce: bstr .size 16, ; from input `peerNonce`
+ * sink_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from own identity
+ * source_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from input `peerId`
+ * ]
+ * 5. Extract a cryptographic secret S from Z, using the salt from #4 above.
+ * 6. Derive two symmetric encryption keys of 256 bits with:
+ * i. b"KE_ENCRYPTION_KEY_SOURCE_TO_SINK" as context for the key used to encrypt incoming
+ * messages
+ * ii. b"KE_ENCRYPTION_KEY_SINK_TO_SOURCE" as context for the key used to encrypt outgoing
+ * messages
+ * 7. Create arcs from the per-boot key to each of the two shared keys from step #6 and
+ * mark authentication_complete = false in arcs' protected headers.
+ * 8. Derive a MAC key with b"KE_HMAC_KEY" as the context.
+ * 9. Compute session_id_input = bstr .cbor [
+ * sink_nonce: bstr .size 16,
+ * source_nonce: bstr .size 16,
+ * ],
+ * 10.Compute a session_id as a 256 bits HMAC over the session_id_input from step#9 with
+ * the key from step #8.
+ * 11.Create a signature over the session_id from step #10, using the signing key which is
+ * part of the party's identity.
+ *
+ * @param peerPubKey - the public key of the key pair created by the peer (P1) for key exchange
+ *
+ * @param peerId - the persistent identity of the peer
+ *
+ * @param peerNonce - nonce created by the peer
+ *
+ * @param peerVersion - an integer representing the latest protocol version (i.e. AIDL version)
+ * supported by the peer
+ *
+ * @return KeInitResult including the `Key` containing the public key of the created key pair,
+ * the nonce, the persistent identity, two shared key arcs from step #7, session id, signature
+ * over the session id and the negotiated protocol version. The negotiated protocol version
+ * should be less than or equal to the peer's version.
+ *
+ * Note: The two shared key arcs in the return type: `KeInitResult` serves two purposes:
+ * i. A mapping to correlate `init` and `authenticationComplete` calls to P2 in a particular
+ * instance of the key exchange protocol.
+ * ii.A way to minimize the in-memory storage of P2 allocated for key exchange.
+ * However, P2 should maintain some in-memory record to be able to verify that the input
+ * `sharedkeys` sent to `authenticationComplete` and to any subsequent AuthGraph protocol
+ * methods are valid shared keys agreed with the party identified by `peerId`, to prevent
+ * any replay attacks in `authenticationComplete` and in any subsequent AuthGraph protocol
+ * methods which use the shared keys to encrypt the secret messages.
+ */
+ KeInitResult init(
+ in PubKey peerPubKey, in Identity peerId, in byte[] peerNonce, in int peerVersion);
+
+ /**
+ * This method is invoked on P1 (source).
+ * Perform the following steps:
+ * 0. If either `peerPubKey`, `peerId`, `peerNonce` is not in the expected format, return
+ * errors: INVALID_PEER_KE_KEY, INVALID_IDENTITY, INVALID_PEER_NONCE respectively. If
+ * `peerVersion` is greater than the version advertised in `create`, return error:
+ * INCOMPATIBLE_PROTOCOL_VERSION.
+ * If `ownKey` is not in the in-memory records for unfinished instances of a key
+ * exchange protocol, return error: INVALID_KE_KEY. Similarly, if the public key or the
+ * arc containing the private key in `ownKey` is invalid, return INVALID_PUB_KEY_IN_KEY
+ * and INVALID_PRIV_KEY_ARC_IN_KEY respectively.
+ * 1. Compute the diffie-hellman shared secret: Z.
+ * 2. Compute a salt = bstr .cbor [
+ * source_version: int, ; the protocol version used in `create`
+ * sink_pub_key: bstr .cbor PlainPubKey, ; from input `peerPubKey`
+ * source_pub_key: bstr .cbor PlainPubKey, ; from the output of `create`
+ * sink_nonce: bstr .size 16, ; from input `peerNonce`
+ * source_nonce: bstr .size 16, ; from the output of `create`
+ * sink_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from input `peerId`
+ * source_cert_chain: bstr .cbor ExplicitKeyDiceCertChain, ; from own identity
+ * ]
+ * 3. Extract a cryptographic secret S from Z, using the salt from #2 above.
+ * 4. Derive two symmetric encryption keys of 256 bits with:
+ * i. b"KE_ENCRYPTION_KEY_SOURCE_TO_SINK" as context for the key used to encrypt outgoing
+ * messages
+ * ii. b"KE_ENCRYPTION_KEY_SINK_TO_SOURCE" as context for the key used to encrypt incoming
+ * messages
+ * 5. Derive a MAC key with b"KE_HMAC_KEY" as the context.
+ * 6. Compute session_id_input = bstr .cbor [
+ * sink_nonce: bstr .size 16,
+ * source_nonce: bstr .size 16,
+ * ],
+ * 7. Compute a session_id as a 256 bits HMAC over the session_id_input from step #6 with
+ * the key from step #5.
+ * 8. Verify the peer's signature over the session_id from step #7. If successful, proceed,
+ * otherwise, return error: INVALID_SIGNATURE.
+ * 9. Create arcs from the per-boot key to each of the two shared keys from step #4 and
+ * mark authentication_complete = true in arcs' protected headers.
+ * 10.Create a signature over the session_id from step #7, using the signing key which is
+ * part of the party's identity.
+ *
+ * @param peerPubKey - the public key of the key pair created by the peer (P2) for key exchange
+ *
+ * @param peerId - the persistent identity of the peer
+ *
+ * @param peerSignature - the signature created by the peer over the session id computed by the
+ * peer
+ *
+ * @param peerNonce - nonce created by the peer
+ *
+ * @param peerVersion - an integer representing the protocol version (i.e. AIDL version)
+ * negotiated with the peer
+ *
+ * @param ownKey - the key created by P1 (source) in `create()` for key exchange
+ *
+ * @return SessionInfo including the two shared key arcs from step #9, session id and the
+ * signature over the session id.
+ *
+ * Note: The two shared key arcs in the return type: `SessionInfo` serves two purposes:
+ * i. A mapping to correlate the key exchange protocol taken place with a particular peer and
+ * subsequent AuthGraph protocols execued with the same peer.
+ * ii.A way to minimize the in-memory storage for shared keys.
+ * However, P1 should maintain some in-memory record to be able to verify that the shared key
+ * arcs sent to any subsequent AuthGraph protocol methods are valid shared keys agreed with the
+ * party identified by `peerId`, to prevent any replay attacks.
+ */
+ SessionInfo finish(in PubKey peerPubKey, in Identity peerId,
+ in SessionIdSignature peerSignature, in byte[] peerNonce, in int peerVersion,
+ in Key ownKey);
+
+ /**
+ * This method is invoked on P2 (sink).
+ * Perform the following steps:
+ * 0. If input `sharedKeys` is invalid (i.e. they cannot be decrypted with P2's per-boot key
+ * or they are not in P2's in-memory records as valid shared keys agreed with the party
+ * identified by `peerId`), return error: INVALID_SHARED_KEY_ARCS.
+ * 1. Verify that both shared key arcs have the same session id and peer identity.
+ * 2. Verify the peer's signature over the session id attached to the shared key arcs'
+ * headers. If successful, proceed, otherwise, return error: INVALID_SIGNATURE.
+ * 3. Mark authentication_complete = true in the shared key arcs' headers
+ *
+ * @param peerSignature - the signature created by the peer over the session id computed by the
+ * peer
+ *
+ * @param sharedKeys - two shared key arcs created by P2 in `init`. P2 obtains from the arcs'
+ * protected headers, the session id and the peer's identity to verify the
+ * peer's signature over the session id.
+ *
+ * @return Arc[] - an array of two updated shared key arcs
+ */
+ Arc[2] authenticationComplete(in SessionIdSignature peerSignature, in Arc[2] sharedKeys);
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl
new file mode 100644
index 0000000..9e350e8
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * Persistent (versioned) identity of a participant of Authgraph key exchange.
+ * Identity consists of two main parts:
+ * 1. a certificate chain (e.g. a DICE certificate chain)
+ * 2. (optional) a policy specifying how to verify the certificate chain - if a policy is not
+ * provided, a simple byte-to-byte comparison of the certificate chain is assumed.
+ *
+ * During identity verification, the certificate chain of the identity attached to the access
+ * request is compared against the policy of the identity attached to the persistent resources.
+ *
+ * The usage of policy based identity verification in Authgraph is three-fold:
+ * 1. Retain access to persistent resources for the newer versions of the party who
+ * created them, even when parts of the certificate chain are updated in the new version.
+ * 2. Deny access to the new persistent resources for the older versions of the party
+ * who created the new persistent resources.
+ * 3. Trigger rotation of critical keys encrypted in persistent arcs created by the previous
+ * version of the party, by including an updated policy in the identity attached to the
+ * access request.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Identity {
+ /* Data is CBOR-encoded according to the `Identity` CDDL definition in Identity.cddl */
+ byte[] identity;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl
new file mode 100644
index 0000000..0419421
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Identity.cddl
@@ -0,0 +1,23 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+Identity = [
+ 1, ; Version
+ cert_chain: bstr .cbor ExplicitKeyDiceCertChain,
+ policy: bstr .cbor DicePolicy / nil,
+]
+
+; INCLUDE ExplicitKeyDiceCertChain.cddl for: ExplicitKeyDiceCertChain
+; INCLUDE DicePolicy.cddl for: DicePolicy
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl
new file mode 100644
index 0000000..b4ae451
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/KeInitResult.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.SessionInfo;
+import android.hardware.security.authgraph.SessionInitiationInfo;
+
+/**
+ * The return type for the init() step of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable KeInitResult {
+ /**
+ * Session initiation information.
+ */
+ SessionInitiationInfo sessionInitiationInfo;
+
+ /**
+ * Session information.
+ */
+ SessionInfo sessionInfo;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl
new file mode 100644
index 0000000..11fe174
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/Key.aidl
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.PubKey;
+
+/**
+ * The type that encapsulates a key. Key can be either a symmetric key or an asymmetric key.
+ * If it is an asymmetric key, it is used for key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable Key {
+ /**
+ * If the Key is an asymmetric key, public key should be present.
+ */
+ @nullable PubKey pubKey;
+
+ /**
+ * Arc from the per-boot key to the payload key. The payload key is either the symmetric key
+ * or the private key of an asymmetric key, based on the type of the key being created.
+ * This is marked as optional because there are instances where only the public key is returned,
+ * e.g. `init` method in the key exchange protocol.
+ */
+ @nullable Arc arcFromPBK;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl
new file mode 100644
index 0000000..5483ec5
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * One of the two enum variants of the enum type: `PubKey`. This represents the plain public key
+ * material encoded as a COSE_Key.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable PlainPubKey {
+ /* Data is CBOR-encoded according to the `PlainPubKey` CDDL definition in PlainPubKey.cddl */
+ byte[] plainPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl
new file mode 100644
index 0000000..34b316b
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PlainPubKey.cddl
@@ -0,0 +1,24 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+
+; P-256 public key for key exchange.
+PlainPubKey = [ ; COSE_Key [RFC9052 s7]
+ 1 : 2, ; Key type : EC2
+ 3 : -27, ; Algorithm : ECDH-SS + HKDF-256
+ -1 : 1, ; Curve: P256
+ -2 : bstr, ; X coordinate, big-endian
+ -3 : bstr ; Y coordinate, big-endian
+]
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl
new file mode 100644
index 0000000..8640871
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/PubKey.aidl
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.PlainPubKey;
+import android.hardware.security.authgraph.SignedPubKey;
+
+/**
+ * The enum type representing the public key of an asymmetric key pair.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+union PubKey {
+ /**
+ * Plain public key material encoded as a COSE_Key.
+ */
+ PlainPubKey plainKey;
+
+ /**
+ * Public key signed with the long term signing key of the party.
+ */
+ SignedPubKey signedKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl
new file mode 100644
index 0000000..2fa8b4c
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * Signature computed by a party over the session id during authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionIdSignature {
+ /* Data is CBOR-encoded according to the `SessionIdSignature` CDDL definition in
+ * SessionIdSignature.cddl */
+ byte[] signature;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl
new file mode 100644
index 0000000..038a0f0
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionIdSignature.cddl
@@ -0,0 +1,33 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+SessionIdSignature = [ ; COSE_Sign1 (untagged) [RFC9052 s4.2]
+ protected: bstr .cbor SessionIdSignatureProtected,
+ unprotected: {},
+ payload: nil, ; session ID payload to be transported separately
+ signature: bstr ; PureEd25519(privateKey, SessionIdSignatureSigStruct) /
+ ; ECDSA(privateKey, SessionIdSignatureSigStruct)
+]
+
+SessionIdSignatureProtected = {
+ 1 : AlgorithmEdDSA / AlgorithmES256,
+}
+
+SessionIdSignatureSigStruct = [ ; Sig_structure for SessionIdSignature [ RFC9052 s4.4]
+ context: "Signature1",
+ protected: bstr SessionIdSignatureProtected,
+ external_aad: bstr .size 0,
+ payload: bstr, ; session ID payload provided separately
+]
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl
new file mode 100644
index 0000000..ef49a1a
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInfo.aidl
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.SessionIdSignature;
+
+/**
+ * Session information returned as part of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionInfo {
+ /**
+ * The arcs that encrypt the two derived symmetric encryption keys (for two-way communication)
+ * from the party's per-boot key.
+ */
+ Arc[2] sharedKeys;
+
+ /**
+ * The value of the session id computed by the two parties during the authenticate key
+ * exchange. Apart from the usage of the session id by the two peers, session id is also useful
+ * to verify (by a third party) that the key exchange was successful.
+ */
+ byte[] sessionId;
+
+ /**
+ * The signature over the session id, created by the party who computed the session id.
+ *
+ * If there is one or more `DiceChainEntry` in the `ExplicitKeyDiceCertChain` of the party's
+ * identity, the signature is verified with the public key in the leaf of the chain of
+ * DiceChainEntries (i.e the public key in the last of the array of DiceChainEntries).
+ * Otherwise, the signature is verified with the `DiceCertChainInitialPayload`.
+ */
+ SessionIdSignature signature;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl
new file mode 100644
index 0000000..c630d91
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SessionInitiationInfo.aidl
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+import android.hardware.security.authgraph.Arc;
+import android.hardware.security.authgraph.Identity;
+import android.hardware.security.authgraph.Key;
+
+/**
+ * Session initiation information returned as part of authenticated key exchange.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SessionInitiationInfo {
+ /**
+ * An ephemeral EC key created for the ECDH process.
+ */
+ Key key;
+
+ /**
+ * The identity of the party who created the Diffie-Hellman key exchange key.
+ */
+ Identity identity;
+
+ /**
+ * Nonce value specific to this session. The nonce serves three purposes:
+ * 1. freshness of key exchange
+ * 2. creating a session id (a publicly known value related to the exchanged keys)
+ * 3. usage as salt into the HKDF-EXTRACT function during key derivation from the shared DH key
+ */
+ byte[] nonce;
+
+ /**
+ * The protocol version (i.e. AIDL version) - This is used to prevent version downgrade attacks
+ * as follows:
+ * 1. In `create`, the source advertises the latest protocol version supported by the source,
+ * which is given as input to the `init` call on the sink in the input parameter:
+ * `peerVersion`.
+ * 2. In `init`, the sink includes the `peerVersion` in the inputs to the derivation of the
+ * shared keys. Then the sink returns the latest protocol version supported by the sink,
+ * which is given as input to the `finish` call on the source in the input parameter:
+ * `peerVersion`.
+ * 3. In `finish`, the source first checks whether the sink's version is equal or less than the
+ * source's version and includes in the source's version in the inputs to the derivation of
+ * the shared keys.
+ * Analysis: if an attacker-in-the-middle wanted the two parties to use an older (vulnerable)
+ * version of the protocol, they can invoke `init` with a version that is lower than the version
+ * advertised by the source in `create`. However, since both parties include the source's
+ * version in the inputs to the derivation of the shared keys, the two parties won't end up with
+ * the same shared keys in the presence of such an attack. This is detected when checking the
+ * signature on the session id in `finish`, at which point the protocol aborts. Therefore,
+ * an attacker cannot successfully launch a version downgrade attack on this protocol.
+ */
+ int version;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl
new file mode 100644
index 0000000..72ee219
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.security.authgraph;
+
+/**
+ * One of the two enum variants of the enum type: `PubKey`. This represents the public key signed
+ * with the long term signing key of the party.
+ */
+@VintfStability
+@RustDerive(Clone=true, Eq=true, PartialEq=true)
+parcelable SignedPubKey {
+ /* Data is CBOR-encoded according to the `SignedPubKey` CDDL definition in SignedPubKey.cddl */
+ byte[] signedPubKey;
+}
diff --git a/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl
new file mode 100644
index 0000000..f23a492
--- /dev/null
+++ b/security/authgraph/aidl/android/hardware/security/authgraph/SignedPubKey.cddl
@@ -0,0 +1,41 @@
+;
+; Copyright (C) 2023 The Android Open Source Project
+;
+; Licensed under the Apache License, Version 2.0 (the "License");
+; you may not use this file except in compliance with the License.
+; You may obtain a copy of the License at
+;
+; http://www.apache.org/licenses/LICENSE-2.0
+;
+; Unless required by applicable law or agreed to in writing, software
+; distributed under the License is distributed on an "AS IS" BASIS,
+; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+; See the License for the specific language governing permissions and
+; limitations under the License.
+;
+SignedPubKey = [ ; COSE_Sign1 (untagged) [RFC9052 s4.2]
+ protected: bstr .cbor SignedPubKeyProtected,
+ unprotected: {},
+ payload: bstr .cbor PlainPubKey,
+ signature: bstr ; PureEd25519(privateKey, SignedPubKeySigStruct) /
+ ; ECDSA(privateKey, SignedPubKeySigStruct)
+]
+
+SignedPubKeyProtected = {
+ 1 : AlgorithmEdDSA / AlgorithmES256,
+ ? -70011 : Identity, ; the party who performs the signing operation adds its own
+ ; identity to the protected headers.
+}
+
+SignedPubKeySigStruct = [ ; Sig_structure for SignedPubKey [ RFC9052 s4.4]
+ context: "Signature1",
+ protected: bstr SignedPubKeyProtected,
+ external_aad: bstr .size 0,
+ payload: bstr .cbor PlainPubKey,
+]
+
+AlgorithmES256 = -7 ; [RFC9053 s2.1]
+AlgorithmEdDSA = -8 ; [RFC9053 s2.2]
+
+; INCLUDE PlainPubKey.cddl for: PlainPubKey
+; INCLUDE Identity.cddl for: Identity
\ No newline at end of file
diff --git a/security/authgraph/aidl/vts/functional/Android.bp b/security/authgraph/aidl/vts/functional/Android.bp
new file mode 100644
index 0000000..0e3480f
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/Android.bp
@@ -0,0 +1,81 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package {
+ // 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_test {
+ name: "VtsAidlAuthGraphSessionTest",
+ defaults: [
+ "VtsHalTargetTestDefaults",
+ "authgraph_use_latest_hal_aidl_ndk_static",
+ "use_libaidlvintf_gtest_helper_static",
+ ],
+ cflags: [
+ "-Wall",
+ "-Wextra",
+ ],
+ srcs: [
+ "AuthGraphSessionTest.cpp",
+ ],
+ shared_libs: [
+ "libbinder_ndk",
+ "libcrypto",
+ ],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+}
+
+rust_test {
+ name: "VtsAidlAuthGraphRoleTest",
+ srcs: ["role_test.rs"],
+ test_suites: [
+ "general-tests",
+ "vts",
+ ],
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libauthgraph_vts_test",
+ "libbinder_rs",
+ ],
+}
+
+rust_library {
+ name: "libauthgraph_vts_test",
+ crate_name: "authgraph_vts_test",
+ srcs: ["lib.rs"],
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libauthgraph_boringssl",
+ "libauthgraph_core",
+ "libauthgraph_hal",
+ "libauthgraph_nonsecure",
+ "libbinder_rs",
+ "libcoset",
+ ],
+}
diff --git a/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
new file mode 100644
index 0000000..d9dea77
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/AuthGraphSessionTest.cpp
@@ -0,0 +1,375 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#define LOG_TAG "authgraph_session_test"
+#include <android-base/logging.h>
+
+#include <aidl/Gtest.h>
+#include <aidl/Vintf.h>
+#include <aidl/android/hardware/security/authgraph/Error.h>
+#include <aidl/android/hardware/security/authgraph/IAuthGraphKeyExchange.h>
+#include <android/binder_manager.h>
+#include <binder/ProcessState.h>
+#include <gtest/gtest.h>
+#include <vector>
+
+namespace aidl::android::hardware::security::authgraph::test {
+using ::aidl::android::hardware::security::authgraph::Error;
+
+namespace {
+
+// Check that the signature in the encoded COSE_Sign1 data is correct, and that the payload matches.
+// TODO: maybe drop separate payload, and extract it from cose_sign1.payload (and return it).
+void CheckSignature(std::vector<uint8_t>& /*pub_cose_key*/, std::vector<uint8_t>& /*payload*/,
+ std::vector<uint8_t>& /*cose_sign1*/) {
+ // TODO: implement me
+}
+
+void CheckSignature(std::vector<uint8_t>& pub_cose_key, std::vector<uint8_t>& payload,
+ SessionIdSignature& signature) {
+ return CheckSignature(pub_cose_key, payload, signature.signature);
+}
+
+std::vector<uint8_t> SigningKeyFromIdentity(const Identity& identity) {
+ // TODO: This is a CBOR-encoded `Identity` which currently happens to be a COSE_Key with the
+ // pubkey This will change in future.
+ return identity.identity;
+}
+
+} // namespace
+
+class AuthGraphSessionTest : public ::testing::TestWithParam<std::string> {
+ public:
+ enum ErrorType { AIDL_ERROR, BINDER_ERROR };
+
+ union ErrorValue {
+ Error aidl_error;
+ int32_t binder_error;
+ };
+
+ struct ReturnedError {
+ ErrorType err_type;
+ ErrorValue err_val;
+
+ friend bool operator==(const ReturnedError& lhs, const ReturnedError& rhs) {
+ return lhs.err_type == rhs.err_type;
+ switch (lhs.err_type) {
+ case ErrorType::AIDL_ERROR:
+ return lhs.err_val.aidl_error == rhs.err_val.aidl_error;
+ case ErrorType::BINDER_ERROR:
+ return lhs.err_val.binder_error == rhs.err_val.binder_error;
+ }
+ }
+ };
+
+ const ReturnedError OK = {.err_type = ErrorType::AIDL_ERROR, .err_val.aidl_error = Error::OK};
+
+ ReturnedError GetReturnError(const ::ndk::ScopedAStatus& result) {
+ if (result.isOk()) {
+ return OK;
+ }
+ int32_t exception_code = result.getExceptionCode();
+ int32_t error_code = result.getServiceSpecificError();
+ if (exception_code == EX_SERVICE_SPECIFIC && error_code != 0) {
+ ReturnedError re = {.err_type = ErrorType::AIDL_ERROR,
+ .err_val.aidl_error = static_cast<Error>(error_code)};
+ return re;
+ }
+ ReturnedError re = {.err_type = ErrorType::BINDER_ERROR,
+ .err_val.binder_error = exception_code};
+ return re;
+ }
+
+ // Build the parameters for the VTS test by enumerating the available HAL instances
+ static std::vector<std::string> build_params() {
+ auto params = ::android::getAidlHalInstanceNames(IAuthGraphKeyExchange::descriptor);
+ return params;
+ }
+
+ void SetUp() override {
+ ASSERT_TRUE(AServiceManager_isDeclared(GetParam().c_str()))
+ << "No instance declared for " << GetParam();
+ ::ndk::SpAIBinder binder(AServiceManager_waitForService(GetParam().c_str()));
+ authNode_ = IAuthGraphKeyExchange::fromBinder(binder);
+ ASSERT_NE(authNode_, nullptr) << "Failed to get Binder reference for " << GetParam();
+ }
+
+ void TearDown() override {}
+
+ protected:
+ std::shared_ptr<IAuthGraphKeyExchange> authNode_;
+};
+
+TEST_P(AuthGraphSessionTest, Mainline) {
+ std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+ // Step 1: create an ephemeral ECDH key at the source.
+ SessionInitiationInfo source_init_info;
+ ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info)));
+ ASSERT_TRUE(source_init_info.key.pubKey.has_value());
+ ASSERT_TRUE(source_init_info.key.arcFromPBK.has_value());
+
+ // Step 2: pass the source's ECDH public key and other session info to the sink.
+ KeInitResult init_result;
+ ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info.key.pubKey.value(),
+ source_init_info.identity, source_init_info.nonce,
+ source_init_info.version, &init_result)));
+ SessionInitiationInfo sink_init_info = init_result.sessionInitiationInfo;
+ ASSERT_TRUE(sink_init_info.key.pubKey.has_value());
+ // The sink_init_info.arcFromPBK need not be populated, as the ephemeral key agreement
+ // key is no longer needed.
+
+ SessionInfo sink_info = init_result.sessionInfo;
+ ASSERT_EQ((int)sink_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+ ASSERT_GT((int)sink_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+ std::vector<uint8_t> sink_signing_key = SigningKeyFromIdentity(sink_init_info.identity);
+ CheckSignature(sink_signing_key, sink_info.sessionId, sink_info.signature);
+
+ // Step 3: pass the sink's ECDH public key and other session info to the source, so it can
+ // calculate the same pair of symmetric keys.
+ SessionInfo source_info;
+ ASSERT_EQ(OK, GetReturnError(source->finish(sink_init_info.key.pubKey.value(),
+ sink_init_info.identity, sink_info.signature,
+ sink_init_info.nonce, sink_init_info.version,
+ source_init_info.key, &source_info)));
+ ASSERT_EQ((int)source_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+ ASSERT_GT((int)source_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+ std::vector<uint8_t> source_signing_key = SigningKeyFromIdentity(source_init_info.identity);
+ CheckSignature(source_signing_key, source_info.sessionId, source_info.signature);
+
+ // Both ends should agree on the session ID.
+ ASSERT_EQ(source_info.sessionId, sink_info.sessionId);
+
+ // Step 4: pass the source's session ID info back to the sink, so it can check it and
+ // update the symmetric keys so they're marked as authentication complete.
+ std::array<Arc, 2> auth_complete_result;
+ ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+ source_info.signature, sink_info.sharedKeys, &auth_complete_result)));
+ ASSERT_EQ((int)auth_complete_result.size(), 2)
+ << "Expect two symmetric keys from authComplete()";
+ sink_info.sharedKeys = auth_complete_result;
+
+ // At this point the sink and source have agreed on the same pair of symmetric keys,
+ // encoded as `sink_info.sharedKeys` and `source_info.sharedKeys`.
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSink) {
+ std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> sink1 = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> sink2 = authNode_;
+
+ // Step 1: create ephemeral ECDH keys at the source.
+ SessionInitiationInfo source_init1_info;
+ ASSERT_EQ(OK, GetReturnError(source->create(&source_init1_info)));
+ ASSERT_TRUE(source_init1_info.key.pubKey.has_value());
+ ASSERT_TRUE(source_init1_info.key.arcFromPBK.has_value());
+ SessionInitiationInfo source_init2_info;
+ ASSERT_EQ(OK, GetReturnError(source->create(&source_init2_info)));
+ ASSERT_TRUE(source_init2_info.key.pubKey.has_value());
+ ASSERT_TRUE(source_init2_info.key.arcFromPBK.has_value());
+
+ // Step 2: pass the source's ECDH public keys and other session info to the sinks.
+ KeInitResult init1_result;
+ ASSERT_EQ(OK, GetReturnError(sink1->init(source_init1_info.key.pubKey.value(),
+ source_init1_info.identity, source_init1_info.nonce,
+ source_init1_info.version, &init1_result)));
+ SessionInitiationInfo sink1_init_info = init1_result.sessionInitiationInfo;
+ ASSERT_TRUE(sink1_init_info.key.pubKey.has_value());
+
+ SessionInfo sink1_info = init1_result.sessionInfo;
+ ASSERT_EQ((int)sink1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+ ASSERT_GT((int)sink1_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+ std::vector<uint8_t> sink1_signing_key = SigningKeyFromIdentity(sink1_init_info.identity);
+ CheckSignature(sink1_signing_key, sink1_info.sessionId, sink1_info.signature);
+ KeInitResult init2_result;
+ ASSERT_EQ(OK, GetReturnError(sink2->init(source_init2_info.key.pubKey.value(),
+ source_init2_info.identity, source_init2_info.nonce,
+ source_init2_info.version, &init2_result)));
+ SessionInitiationInfo sink2_init_info = init2_result.sessionInitiationInfo;
+ ASSERT_TRUE(sink2_init_info.key.pubKey.has_value());
+
+ SessionInfo sink2_info = init2_result.sessionInfo;
+ ASSERT_EQ((int)sink2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+ ASSERT_GT((int)sink2_info.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+ std::vector<uint8_t> sink2_signing_key = SigningKeyFromIdentity(sink2_init_info.identity);
+ CheckSignature(sink2_signing_key, sink2_info.sessionId, sink2_info.signature);
+
+ // Step 3: pass each sink's ECDH public key and other session info to the source, so it can
+ // calculate the same pair of symmetric keys.
+ SessionInfo source_info1;
+ ASSERT_EQ(OK, GetReturnError(source->finish(sink1_init_info.key.pubKey.value(),
+ sink1_init_info.identity, sink1_info.signature,
+ sink1_init_info.nonce, sink1_init_info.version,
+ source_init1_info.key, &source_info1)));
+ ASSERT_EQ((int)source_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+ ASSERT_GT((int)source_info1.sessionId.size(), 0) << "Expect non-empty session ID from source";
+ std::vector<uint8_t> source_signing_key1 = SigningKeyFromIdentity(source_init1_info.identity);
+ CheckSignature(source_signing_key1, source_info1.sessionId, source_info1.signature);
+ SessionInfo source_info2;
+ ASSERT_EQ(OK, GetReturnError(source->finish(sink2_init_info.key.pubKey.value(),
+ sink2_init_info.identity, sink2_info.signature,
+ sink2_init_info.nonce, sink2_init_info.version,
+ source_init2_info.key, &source_info2)));
+ ASSERT_EQ((int)source_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+ ASSERT_GT((int)source_info2.sessionId.size(), 0) << "Expect non-empty session ID from source";
+ std::vector<uint8_t> source_signing_key2 = SigningKeyFromIdentity(source_init2_info.identity);
+ CheckSignature(source_signing_key2, source_info2.sessionId, source_info2.signature);
+
+ // Both ends should agree on the session ID.
+ ASSERT_EQ(source_info1.sessionId, sink1_info.sessionId);
+ ASSERT_EQ(source_info2.sessionId, sink2_info.sessionId);
+
+ // Step 4: pass the source's session ID info back to the sink, so it can check it and
+ // update the symmetric keys so they're marked as authentication complete.
+ std::array<Arc, 2> auth_complete_result1;
+ ASSERT_EQ(OK, GetReturnError(sink1->authenticationComplete(
+ source_info1.signature, sink1_info.sharedKeys, &auth_complete_result1)));
+ ASSERT_EQ((int)auth_complete_result1.size(), 2)
+ << "Expect two symmetric keys from authComplete()";
+ sink1_info.sharedKeys = auth_complete_result1;
+ std::array<Arc, 2> auth_complete_result2;
+ ASSERT_EQ(OK, GetReturnError(sink2->authenticationComplete(
+ source_info2.signature, sink2_info.sharedKeys, &auth_complete_result2)));
+ ASSERT_EQ((int)auth_complete_result2.size(), 2)
+ << "Expect two symmetric keys from authComplete()";
+ sink2_info.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, ParallelSource) {
+ std::shared_ptr<IAuthGraphKeyExchange> source1 = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> source2 = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+ // Step 1: create an ephemeral ECDH key at each of the sources.
+ SessionInitiationInfo source1_init_info;
+ ASSERT_EQ(OK, GetReturnError(source1->create(&source1_init_info)));
+ ASSERT_TRUE(source1_init_info.key.pubKey.has_value());
+ ASSERT_TRUE(source1_init_info.key.arcFromPBK.has_value());
+ SessionInitiationInfo source2_init_info;
+ ASSERT_EQ(OK, GetReturnError(source1->create(&source2_init_info)));
+ ASSERT_TRUE(source2_init_info.key.pubKey.has_value());
+ ASSERT_TRUE(source2_init_info.key.arcFromPBK.has_value());
+
+ // Step 2: pass each source's ECDH public key and other session info to the sink.
+ KeInitResult init1_result;
+ ASSERT_EQ(OK, GetReturnError(sink->init(source1_init_info.key.pubKey.value(),
+ source1_init_info.identity, source1_init_info.nonce,
+ source1_init_info.version, &init1_result)));
+ SessionInitiationInfo sink_init1_info = init1_result.sessionInitiationInfo;
+ ASSERT_TRUE(sink_init1_info.key.pubKey.has_value());
+
+ SessionInfo sink_info1 = init1_result.sessionInfo;
+ ASSERT_EQ((int)sink_info1.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+ ASSERT_GT((int)sink_info1.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+ std::vector<uint8_t> sink_signing_key1 = SigningKeyFromIdentity(sink_init1_info.identity);
+ CheckSignature(sink_signing_key1, sink_info1.sessionId, sink_info1.signature);
+
+ KeInitResult init2_result;
+ ASSERT_EQ(OK, GetReturnError(sink->init(source2_init_info.key.pubKey.value(),
+ source2_init_info.identity, source2_init_info.nonce,
+ source2_init_info.version, &init2_result)));
+ SessionInitiationInfo sink_init2_info = init2_result.sessionInitiationInfo;
+ ASSERT_TRUE(sink_init2_info.key.pubKey.has_value());
+
+ SessionInfo sink_info2 = init2_result.sessionInfo;
+ ASSERT_EQ((int)sink_info2.sharedKeys.size(), 2) << "Expect two symmetric keys from init()";
+ ASSERT_GT((int)sink_info2.sessionId.size(), 0) << "Expect non-empty session ID from sink";
+ std::vector<uint8_t> sink_signing_key2 = SigningKeyFromIdentity(sink_init2_info.identity);
+ CheckSignature(sink_signing_key2, sink_info2.sessionId, sink_info2.signature);
+
+ // Step 3: pass the sink's ECDH public keys and other session info to the each of the sources.
+ SessionInfo source1_info;
+ ASSERT_EQ(OK, GetReturnError(source1->finish(sink_init1_info.key.pubKey.value(),
+ sink_init1_info.identity, sink_info1.signature,
+ sink_init1_info.nonce, sink_init1_info.version,
+ source1_init_info.key, &source1_info)));
+ ASSERT_EQ((int)source1_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+ ASSERT_GT((int)source1_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+ std::vector<uint8_t> source1_signing_key = SigningKeyFromIdentity(source1_init_info.identity);
+ CheckSignature(source1_signing_key, source1_info.sessionId, source1_info.signature);
+
+ SessionInfo source2_info;
+ ASSERT_EQ(OK, GetReturnError(source2->finish(sink_init2_info.key.pubKey.value(),
+ sink_init2_info.identity, sink_info2.signature,
+ sink_init2_info.nonce, sink_init2_info.version,
+ source2_init_info.key, &source2_info)));
+ ASSERT_EQ((int)source2_info.sharedKeys.size(), 2) << "Expect two symmetric keys from finsh()";
+ ASSERT_GT((int)source2_info.sessionId.size(), 0) << "Expect non-empty session ID from source";
+ std::vector<uint8_t> source2_signing_key = SigningKeyFromIdentity(source2_init_info.identity);
+ CheckSignature(source2_signing_key, source2_info.sessionId, source2_info.signature);
+
+ // Both ends should agree on the session ID.
+ ASSERT_EQ(source1_info.sessionId, sink_info1.sessionId);
+ ASSERT_EQ(source2_info.sessionId, sink_info2.sessionId);
+
+ // Step 4: pass the each source's session ID info back to the sink, so it can check it and
+ // update the symmetric keys so they're marked as authentication complete.
+ std::array<Arc, 2> auth_complete_result1;
+ ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+ source1_info.signature, sink_info1.sharedKeys, &auth_complete_result1)));
+ ASSERT_EQ((int)auth_complete_result1.size(), 2)
+ << "Expect two symmetric keys from authComplete()";
+ sink_info1.sharedKeys = auth_complete_result1;
+ std::array<Arc, 2> auth_complete_result2;
+ ASSERT_EQ(OK, GetReturnError(sink->authenticationComplete(
+ source2_info.signature, sink_info2.sharedKeys, &auth_complete_result2)));
+ ASSERT_EQ((int)auth_complete_result2.size(), 2)
+ << "Expect two symmetric keys from authComplete()";
+ sink_info2.sharedKeys = auth_complete_result2;
+}
+
+TEST_P(AuthGraphSessionTest, FreshNonces) {
+ std::shared_ptr<IAuthGraphKeyExchange> source = authNode_;
+ std::shared_ptr<IAuthGraphKeyExchange> sink = authNode_;
+
+ SessionInitiationInfo source_init_info1;
+ ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info1)));
+ SessionInitiationInfo source_init_info2;
+ ASSERT_EQ(OK, GetReturnError(source->create(&source_init_info2)));
+
+ // Two calls to create() should result in the same identity but different nonce values.
+ ASSERT_EQ(source_init_info1.identity, source_init_info2.identity);
+ ASSERT_NE(source_init_info1.nonce, source_init_info2.nonce);
+ ASSERT_NE(source_init_info1.key.pubKey, source_init_info2.key.pubKey);
+ ASSERT_NE(source_init_info1.key.arcFromPBK, source_init_info2.key.arcFromPBK);
+
+ KeInitResult init_result1;
+ ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info1.key.pubKey.value(),
+ source_init_info1.identity, source_init_info1.nonce,
+ source_init_info1.version, &init_result1)));
+ KeInitResult init_result2;
+ ASSERT_EQ(OK, GetReturnError(sink->init(source_init_info2.key.pubKey.value(),
+ source_init_info2.identity, source_init_info2.nonce,
+ source_init_info2.version, &init_result2)));
+
+ // Two calls to init() should result in the same identity buf different nonces and session IDs.
+ ASSERT_EQ(init_result1.sessionInitiationInfo.identity,
+ init_result2.sessionInitiationInfo.identity);
+ ASSERT_NE(init_result1.sessionInitiationInfo.nonce, init_result2.sessionInitiationInfo.nonce);
+ ASSERT_NE(init_result1.sessionInfo.sessionId, init_result2.sessionInfo.sessionId);
+}
+
+INSTANTIATE_TEST_SUITE_P(PerInstance, AuthGraphSessionTest,
+ testing::ValuesIn(AuthGraphSessionTest::build_params()),
+ ::android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AuthGraphSessionTest);
+
+} // namespace aidl::android::hardware::security::authgraph::test
+
+int main(int argc, char** argv) {
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/security/authgraph/aidl/vts/functional/lib.rs b/security/authgraph/aidl/vts/functional/lib.rs
new file mode 100644
index 0000000..7b9b2b9
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/lib.rs
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! VTS test library for AuthGraph functionality.
+//!
+//! This test code is bundled as a library, not as `[cfg(test)]`, to allow it to be
+//! re-used inside the (Rust) VTS tests of components that use AuthGraph.
+
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+ Error::Error, IAuthGraphKeyExchange::IAuthGraphKeyExchange, Identity::Identity,
+ PlainPubKey::PlainPubKey, PubKey::PubKey, SessionIdSignature::SessionIdSignature,
+};
+use authgraph_boringssl as boring;
+use authgraph_core::keyexchange as ke;
+use authgraph_core::{arc, key, traits};
+use authgraph_nonsecure::StdClock;
+use coset::CborSerializable;
+
+pub mod sink;
+pub mod source;
+
+/// Return a collection of AuthGraph trait implementations suitable for testing.
+pub fn test_impls() -> traits::TraitImpl {
+ // Note that the local implementation is using a clock with a potentially different epoch than
+ // the implementation under test.
+ boring::trait_impls(
+ Box::<boring::test_device::AgDevice>::default(),
+ Some(Box::new(StdClock::default())),
+ )
+}
+
+fn build_plain_pub_key(pub_key: &Option<Vec<u8>>) -> PubKey {
+ PubKey::PlainKey(PlainPubKey {
+ plainPubKey: pub_key.clone().unwrap(),
+ })
+}
+
+fn extract_plain_pub_key(pub_key: &Option<PubKey>) -> &PlainPubKey {
+ match pub_key {
+ Some(PubKey::PlainKey(pub_key)) => pub_key,
+ Some(PubKey::SignedKey(_)) => panic!("expect unsigned public key"),
+ None => panic!("expect pubKey to be populated"),
+ }
+}
+
+fn verification_key_from_identity(impls: &traits::TraitImpl, identity: &[u8]) -> key::EcVerifyKey {
+ let identity = key::Identity::from_slice(identity).expect("invalid identity CBOR");
+ impls
+ .device
+ .process_peer_cert_chain(&identity.cert_chain, &*impls.ecdsa)
+ .expect("failed to extract signing key")
+}
+
+fn vec_to_identity(data: &[u8]) -> Identity {
+ Identity {
+ identity: data.to_vec(),
+ }
+}
+
+fn vec_to_signature(data: &[u8]) -> SessionIdSignature {
+ SessionIdSignature {
+ signature: data.to_vec(),
+ }
+}
+
+/// Decrypt a pair of AES-256 keys encrypted with the AuthGraph PBK.
+pub fn decipher_aes_keys(imp: &traits::TraitImpl, arc: &[Vec<u8>; 2]) -> [key::AesKey; 2] {
+ [
+ decipher_aes_key(imp, &arc[0]),
+ decipher_aes_key(imp, &arc[1]),
+ ]
+}
+
+/// Decrypt an AES-256 key encrypted with the AuthGraph PBK.
+pub fn decipher_aes_key(imp: &traits::TraitImpl, arc: &[u8]) -> key::AesKey {
+ let pbk = imp.device.get_per_boot_key().expect("no PBK available");
+ let arc::ArcContent {
+ payload,
+ protected_headers: _,
+ unprotected_headers: _,
+ } = arc::decipher_arc(&pbk, arc, &*imp.aes_gcm).expect("failed to decrypt arc");
+ assert_eq!(payload.0.len(), 32);
+ let mut key = key::AesKey([0; 32]);
+ key.0.copy_from_slice(&payload.0);
+ assert_ne!(key.0, [0; 32], "agreed AES-256 key should be non-zero");
+ key
+}
diff --git a/security/authgraph/aidl/vts/functional/role_test.rs b/security/authgraph/aidl/vts/functional/role_test.rs
new file mode 100644
index 0000000..e95361a
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/role_test.rs
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Tests of individual AuthGraph role (source or sink) functionality.
+
+#![cfg(test)]
+
+use authgraph_vts_test as vts;
+use android_hardware_security_authgraph::aidl::android::hardware::security::authgraph::{
+ IAuthGraphKeyExchange::IAuthGraphKeyExchange,
+};
+
+const AUTH_GRAPH_NONSECURE: &str =
+ "android.hardware.security.authgraph.IAuthGraphKeyExchange/nonsecure";
+
+/// Retrieve the /nonsecure instance of AuthGraph, which supports both sink and source roles.
+fn get_nonsecure() -> Option<binder::Strong<dyn IAuthGraphKeyExchange>> {
+ binder::get_interface(AUTH_GRAPH_NONSECURE).ok()
+}
+
+/// Macro to require availability of a /nonsecure instance of AuthGraph.
+///
+/// Note that this macro triggers `return` if not found.
+macro_rules! require_nonsecure {
+ {} => {
+ match get_nonsecure() {
+ Some(v) => v,
+ None => {
+ eprintln!("Skipping test as no /nonsecure impl found");
+ return;
+ }
+ }
+ }
+}
+
+#[test]
+fn test_nonsecure_source_mainline() {
+ let mut impls = vts::test_impls();
+ vts::source::test_mainline(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_source_corrupt_sig() {
+ let mut impls = vts::test_impls();
+ vts::source::test_corrupt_sig(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_source_corrupt_keys() {
+ let mut impls = vts::test_impls();
+ vts::source::test_corrupt_key(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_mainline() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_mainline(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_corrupt_sig() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_corrupt_sig(&mut impls, require_nonsecure!());
+}
+#[test]
+fn test_nonsecure_sink_corrupt_keys() {
+ let mut impls = vts::test_impls();
+ vts::sink::test_corrupt_keys(&mut impls, require_nonsecure!());
+}
diff --git a/security/authgraph/aidl/vts/functional/sink.rs b/security/authgraph/aidl/vts/functional/sink.rs
new file mode 100644
index 0000000..5c81593
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/sink.rs
@@ -0,0 +1,214 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! VTS tests for sinks
+use super::*;
+use authgraph_core::traits;
+
+/// Run AuthGraph tests against the provided sink, using a local test source implementation.
+pub fn test(impls: &mut traits::TraitImpl, sink: binder::Strong<dyn IAuthGraphKeyExchange>) {
+ test_mainline(impls, sink.clone());
+ test_corrupt_sig(impls, sink.clone());
+ test_corrupt_keys(impls, sink);
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink and local implementation.
+/// Return the agreed AES keys in plaintext.
+pub fn test_mainline(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) -> [key::AesKey; 2] {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key =
+ verification_key_from_identity(&impls, &sink_init_info.identity.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.sessionId,
+ &sink_info.signature.signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let source_verification_key =
+ verification_key_from_identity(&impls, &source_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &source_verification_key,
+ &source_info.session_id,
+ &source_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Both ends should agree on the session ID.
+ assert_eq!(source_info.session_id, sink_info.sessionId);
+
+ // Step 4: pass the (local) source's session ID signature back to the sink, so it can check it
+ // and update the symmetric keys so they're marked as authentication complete.
+ let _sink_arcs = sink
+ .authenticationComplete(
+ &vec_to_signature(&source_info.session_id_signature),
+ &sink_info.sharedKeys,
+ )
+ .expect("failed to authenticationComplete() with remote sink");
+
+ // Decrypt and return the session keys.
+ decipher_aes_keys(&impls, &source_info.shared_keys)
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
+/// session ID signature.
+pub fn test_corrupt_sig(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // Build a corrupted version of the (local) source's session ID signature.
+ let mut corrupt_signature = source_info.session_id_signature.clone();
+ let sig_len = corrupt_signature.len();
+ corrupt_signature[sig_len - 1] ^= 0x01;
+
+ // Step 4: pass the (local) source's **invalid** session ID signature back to the sink,
+ // which should reject it.
+ let result =
+ sink.authenticationComplete(&vec_to_signature(&corrupt_signature), &sink_info.sharedKeys);
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
+ );
+}
+
+/// Perform mainline AuthGraph key exchange with the provided sink, but provide an invalid
+/// Arc for the sink's key.
+pub fn test_corrupt_keys(
+ impls: &mut traits::TraitImpl,
+ sink: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (local) source.
+ let source_init_info = ke::create(impls).expect("failed to create() with local impl");
+
+ // Step 2: pass the source's ECDH public key and other session info to the (remote) sink.
+ let init_result = sink
+ .init(
+ &build_plain_pub_key(&source_init_info.ke_key.pub_key),
+ &vec_to_identity(&source_init_info.identity),
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with remote impl");
+ let sink_init_info = init_result.sessionInitiationInfo;
+ let sink_pub_key = extract_plain_pub_key(&sink_init_info.key.pubKey);
+
+ let sink_info = init_result.sessionInfo;
+ assert!(!sink_info.sessionId.is_empty());
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (local) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = ke::finish(
+ impls,
+ &sink_pub_key.plainPubKey,
+ &sink_init_info.identity.identity,
+ &sink_info.signature.signature,
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ source_init_info.ke_key,
+ )
+ .expect("failed to finish() with local impl");
+ assert!(!source_info.session_id.is_empty());
+
+ // Deliberately corrupt the sink's shared key Arcs before returning them
+ let mut corrupt_keys = sink_info.sharedKeys.clone();
+ let len0 = corrupt_keys[0].arc.len();
+ let len1 = corrupt_keys[1].arc.len();
+ corrupt_keys[0].arc[len0 - 1] ^= 0x01;
+ corrupt_keys[1].arc[len1 - 1] ^= 0x01;
+
+ // Step 4: pass the (local) source's session ID signature back to the sink, but with corrupted
+ // keys, which should be rejected.
+ let result = sink.authenticationComplete(
+ &vec_to_signature(&source_info.session_id_signature),
+ &corrupt_keys,
+ );
+ let err = result.expect_err("expect failure with corrupt keys");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SHARED_KEY_ARCS.0, None)
+ );
+}
diff --git a/security/authgraph/aidl/vts/functional/source.rs b/security/authgraph/aidl/vts/functional/source.rs
new file mode 100644
index 0000000..9aaaaee
--- /dev/null
+++ b/security/authgraph/aidl/vts/functional/source.rs
@@ -0,0 +1,244 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! VTS tests for sources
+use super::*;
+use authgraph_core::traits;
+
+/// Run AuthGraph tests against the provided source, using a local test sink implementation.
+pub fn test(impls: &mut traits::TraitImpl, source: binder::Strong<dyn IAuthGraphKeyExchange>) {
+ test_mainline(impls, source.clone());
+ test_corrupt_sig(impls, source.clone());
+ test_corrupt_key(impls, source);
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source.
+/// Return the agreed AES keys in plaintext.
+pub fn test_mainline(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) -> [key::AesKey; 2] {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key = verification_key_from_identity(&impls, &sink_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.session_id,
+ &sink_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
+ // can calculate the same pair of symmetric keys.
+ let source_info = source
+ .finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&sink_info.session_id_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &source_init_info.key,
+ )
+ .expect("failed to finish() with remote impl");
+ assert!(!source_info.sessionId.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let source_verification_key =
+ verification_key_from_identity(&impls, &source_init_info.identity.identity);
+ ke::verify_signature_on_session_id(
+ &source_verification_key,
+ &source_info.sessionId,
+ &source_info.signature.signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Both ends should agree on the session ID.
+ assert_eq!(source_info.sessionId, sink_info.session_id);
+
+ // Step 4: pass the (remote) source's session ID signature back to the sink, so it can check it
+ // and update the symmetric keys so they're marked as authentication complete.
+ let sink_arcs = ke::authentication_complete(
+ impls,
+ &source_info.signature.signature,
+ sink_info.shared_keys,
+ )
+ .expect("failed to authenticationComplete() with local sink");
+
+ // Decrypt and return the session keys.
+ decipher_aes_keys(&impls, &sink_arcs)
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source, but provide an invalid session
+/// ID signature.
+pub fn test_corrupt_sig(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // Deliberately corrupt the sink's session ID signature.
+ let mut corrupt_signature = sink_info.session_id_signature.clone();
+ let sig_len = corrupt_signature.len();
+ corrupt_signature[sig_len - 1] ^= 0x01;
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, so it
+ // can calculate the same pair of symmetric keys.
+ let result = source.finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&corrupt_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &source_init_info.key,
+ );
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_SIGNATURE.0, None)
+ );
+}
+
+/// Perform mainline AuthGraph key exchange with the provided source, but give it back
+/// a corrupted key.
+pub fn test_corrupt_key(
+ impls: &mut traits::TraitImpl,
+ source: binder::Strong<dyn IAuthGraphKeyExchange>,
+) {
+ // Step 1: create an ephemeral ECDH key at the (remote) source.
+ let source_init_info = source
+ .create()
+ .expect("failed to create() with remote impl");
+ assert!(source_init_info.key.pubKey.is_some());
+ assert!(source_init_info.key.arcFromPBK.is_some());
+ let source_pub_key = extract_plain_pub_key(&source_init_info.key.pubKey);
+
+ // Step 2: pass the source's ECDH public key and other session info to the (local) sink.
+ let init_result = ke::init(
+ impls,
+ &source_pub_key.plainPubKey,
+ &source_init_info.identity.identity,
+ &source_init_info.nonce,
+ source_init_info.version,
+ )
+ .expect("failed to init() with local impl");
+ let sink_init_info = init_result.session_init_info;
+ let sink_pub_key = sink_init_info
+ .ke_key
+ .pub_key
+ .expect("expect pub_key to be populated");
+
+ let sink_info = init_result.session_info;
+ assert!(!sink_info.session_id.is_empty());
+
+ // The AuthGraph core library will verify the session ID signature, but do it here too.
+ let sink_verification_key = verification_key_from_identity(&impls, &sink_init_info.identity);
+ ke::verify_signature_on_session_id(
+ &sink_verification_key,
+ &sink_info.session_id,
+ &sink_info.session_id_signature,
+ &*impls.ecdsa,
+ )
+ .expect("failed verification of signed session ID");
+
+ // Deliberately corrupt the source's encrypted key.
+ let mut corrupt_key = source_init_info.key.clone();
+ match &mut corrupt_key.arcFromPBK {
+ Some(a) => {
+ let len = a.arc.len();
+ a.arc[len - 1] ^= 0x01;
+ }
+ None => panic!("no arc data"),
+ }
+
+ // Step 3: pass the sink's ECDH public key and other session info to the (remote) source, but
+ // give it back a corrupted version of its own key.
+ let result = source.finish(
+ &PubKey::PlainKey(PlainPubKey {
+ plainPubKey: sink_pub_key,
+ }),
+ &Identity {
+ identity: sink_init_info.identity,
+ },
+ &vec_to_signature(&sink_info.session_id_signature),
+ &sink_init_info.nonce,
+ sink_init_info.version,
+ &corrupt_key,
+ );
+
+ let err = result.expect_err("expect failure with corrupt signature");
+ assert_eq!(
+ err,
+ binder::Status::new_service_specific_error(Error::INVALID_PRIV_KEY_ARC_IN_KEY.0, None)
+ );
+}
diff --git a/security/authgraph/default/Android.bp b/security/authgraph/default/Android.bp
new file mode 100644
index 0000000..c481075
--- /dev/null
+++ b/security/authgraph/default/Android.bp
@@ -0,0 +1,82 @@
+//
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+//
+package {
+ // 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"],
+}
+
+rust_library {
+ name: "libauthgraph_nonsecure",
+ crate_name: "authgraph_nonsecure",
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ vendor_available: true,
+ rustlibs: [
+ "libandroid_logger",
+ "libauthgraph_boringssl",
+ "libauthgraph_core",
+ "libauthgraph_hal",
+ "libbinder_rs",
+ "liblibc",
+ "liblog_rust",
+ ],
+ srcs: ["src/lib.rs"],
+
+}
+
+rust_binary {
+ name: "android.hardware.security.authgraph-service.nonsecure",
+ relative_install_path: "hw",
+ vendor: true,
+ init_rc: ["authgraph.rc"],
+ vintf_fragments: ["authgraph.xml"],
+ defaults: [
+ "authgraph_use_latest_hal_aidl_rust",
+ ],
+ rustlibs: [
+ "libandroid_logger",
+ "libauthgraph_hal",
+ "libauthgraph_nonsecure",
+ "libbinder_rs",
+ "liblibc",
+ "liblog_rust",
+ ],
+ srcs: [
+ "src/main.rs",
+ ],
+}
+
+rust_fuzz {
+ name: "android.hardware.authgraph-service.nonsecure_fuzzer",
+ rustlibs: [
+ "libauthgraph_hal",
+ "libauthgraph_nonsecure",
+ "libbinder_random_parcel_rs",
+ "libbinder_rs",
+ ],
+ srcs: ["src/fuzzer.rs"],
+ fuzz_config: {
+ cc: [
+ "drysdale@google.com",
+ "hasinitg@google.com",
+ ],
+ },
+}
diff --git a/security/authgraph/default/authgraph.rc b/security/authgraph/default/authgraph.rc
new file mode 100644
index 0000000..0222994
--- /dev/null
+++ b/security/authgraph/default/authgraph.rc
@@ -0,0 +1,5 @@
+service vendor.authgraph /vendor/bin/hw/android.hardware.security.authgraph-service.nonsecure
+ interface aidl android.hardware.security.authgraph.IAuthGraph/nonsecure
+ class hal
+ user nobody
+ group nobody
diff --git a/security/authgraph/default/authgraph.xml b/security/authgraph/default/authgraph.xml
new file mode 100644
index 0000000..9529a0a
--- /dev/null
+++ b/security/authgraph/default/authgraph.xml
@@ -0,0 +1,10 @@
+<manifest version="1.0" type="device">
+ <hal format="aidl">
+ <name>android.hardware.security.authgraph</name>
+ <version>1</version>
+ <interface>
+ <name>IAuthGraphKeyExchange</name>
+ <instance>nonsecure</instance>
+ </interface>
+ </hal>
+</manifest>
diff --git a/security/authgraph/default/src/fuzzer.rs b/security/authgraph/default/src/fuzzer.rs
new file mode 100644
index 0000000..6a9cfdd
--- /dev/null
+++ b/security/authgraph/default/src/fuzzer.rs
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#![allow(missing_docs)]
+#![no_main]
+extern crate libfuzzer_sys;
+
+use authgraph_hal::service::AuthGraphService;
+use authgraph_nonsecure::LocalTa;
+use binder_random_parcel_rs::fuzz_service;
+use libfuzzer_sys::fuzz_target;
+use std::sync::{Arc, Mutex};
+
+fuzz_target!(|data: &[u8]| {
+ let local_ta = LocalTa::new();
+ let service = AuthGraphService::new_as_binder(Arc::new(Mutex::new(local_ta)));
+ fuzz_service(&mut service.as_binder(), data);
+});
diff --git a/security/authgraph/default/src/lib.rs b/security/authgraph/default/src/lib.rs
new file mode 100644
index 0000000..4cd0cb7
--- /dev/null
+++ b/security/authgraph/default/src/lib.rs
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Common functionality for non-secure/testing instance of AuthGraph.
+
+use authgraph_boringssl as boring;
+use authgraph_core::{
+ key::MillisecondsSinceEpoch,
+ ta::{AuthGraphTa, Role},
+ traits,
+};
+use authgraph_hal::channel::SerializedChannel;
+use std::sync::{Arc, Mutex};
+use std::time::Instant;
+
+/// Monotonic clock with an epoch that starts at the point of construction.
+/// (This makes it unsuitable for use outside of testing, because the epoch
+/// will not match that of any other component.)
+pub struct StdClock(Instant);
+
+impl Default for StdClock {
+ fn default() -> Self {
+ Self(Instant::now())
+ }
+}
+
+impl traits::MonotonicClock for StdClock {
+ fn now(&self) -> MillisecondsSinceEpoch {
+ let millis: i64 = self
+ .0
+ .elapsed()
+ .as_millis()
+ .try_into()
+ .expect("failed to fit timestamp in i64");
+ MillisecondsSinceEpoch(millis)
+ }
+}
+
+/// Implementation of the AuthGraph TA that runs locally in-process (and which is therefore
+/// insecure).
+pub struct LocalTa {
+ ta: Arc<Mutex<AuthGraphTa>>,
+}
+
+impl LocalTa {
+ /// Create a new instance.
+ pub fn new() -> Self {
+ Self {
+ ta: Arc::new(Mutex::new(AuthGraphTa::new(
+ boring::trait_impls(
+ Box::<boring::test_device::AgDevice>::default(),
+ Some(Box::new(StdClock::default())),
+ ),
+ Role::Both,
+ ))),
+ }
+ }
+}
+
+/// Pretend to be a serialized channel to the TA, but actually just directly invoke the TA with
+/// incoming requests.
+impl SerializedChannel for LocalTa {
+ const MAX_SIZE: usize = usize::MAX;
+
+ fn execute(&mut self, req_data: &[u8]) -> binder::Result<Vec<u8>> {
+ Ok(self.ta.lock().unwrap().process(req_data))
+ }
+}
diff --git a/security/authgraph/default/src/main.rs b/security/authgraph/default/src/main.rs
new file mode 100644
index 0000000..873eb4e
--- /dev/null
+++ b/security/authgraph/default/src/main.rs
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+//! Default implementation of the AuthGraph key exchange HAL.
+//!
+//! This implementation of the HAL is only intended to allow testing and policy compliance. A real
+//! implementation of the AuthGraph HAL would be implemented in a secure environment, and would not
+//! be independently registered with service manager (a secure component that uses AuthGraph would
+//! expose an entrypoint that allowed retrieval of the specific IAuthGraphKeyExchange instance that
+//! is correlated with the component).
+
+use authgraph_hal::service;
+use authgraph_nonsecure::LocalTa;
+use log::{error, info};
+use std::sync::{Arc, Mutex};
+
+static SERVICE_NAME: &str = "android.hardware.security.authgraph.IAuthGraphKeyExchange";
+static SERVICE_INSTANCE: &str = "nonsecure";
+
+/// Local error type for failures in the HAL service.
+#[derive(Debug, Clone)]
+struct HalServiceError(String);
+
+impl From<String> for HalServiceError {
+ fn from(s: String) -> Self {
+ Self(s)
+ }
+}
+
+fn main() {
+ if let Err(e) = inner_main() {
+ panic!("HAL service failed: {:?}", e);
+ }
+}
+
+fn inner_main() -> Result<(), HalServiceError> {
+ // Initialize Android logging.
+ android_logger::init_once(
+ android_logger::Config::default()
+ .with_tag("authgraph-hal-nonsecure")
+ .with_min_level(log::Level::Info)
+ .with_log_id(android_logger::LogId::System),
+ );
+ // Redirect panic messages to logcat.
+ std::panic::set_hook(Box::new(|panic_info| {
+ error!("{}", panic_info);
+ }));
+
+ info!("Insecure AuthGraph key exchange HAL service is starting.");
+
+ info!("Starting thread pool now.");
+ binder::ProcessState::start_thread_pool();
+
+ // Register the service
+ let local_ta = LocalTa::new();
+ let service = service::AuthGraphService::new_as_binder(Arc::new(Mutex::new(local_ta)));
+ let service_name = format!("{}/{}", SERVICE_NAME, SERVICE_INSTANCE);
+ binder::add_service(&service_name, service.as_binder()).map_err(|e| {
+ format!(
+ "Failed to register service {} because of {:?}.",
+ service_name, e
+ )
+ })?;
+
+ info!("Successfully registered AuthGraph HAL services.");
+ binder::ProcessState::join_thread_pool();
+ info!("AuthGraph HAL service is terminating."); // should not reach here
+ Ok(())
+}
diff --git a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
index 36f0106..aa7bf28 100644
--- a/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
+++ b/security/keymint/aidl/android/hardware/security/keymint/Tag.aidl
@@ -643,6 +643,8 @@
* Tag::ATTESTATION_CHALLENGE is used to deliver a "challenge" value to the attested key
* generation/import methods, which must place the value in the KeyDescription SEQUENCE of the
* attestation extension.
+ * The challenge value may be up to 128 bytes. If the caller provides a bigger challenge,
+ * INVALID_INPUT_LENGTH error should be returned.
*
* Must never appear in KeyCharacteristics.
*/
diff --git a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
index 456aee7..0b15d12 100644
--- a/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
+++ b/sensors/aidl/vts/VtsAidlHalSensorsTargetTest.cpp
@@ -41,6 +41,7 @@
using aidl::android::hardware::sensors::SensorInfo;
using aidl::android::hardware::sensors::SensorStatus;
using aidl::android::hardware::sensors::SensorType;
+using aidl::android::hardware::sensors::AdditionalInfo;
using android::ProcessState;
using std::chrono::duration_cast;
@@ -629,6 +630,15 @@
Event additionalInfoEvent;
additionalInfoEvent.sensorType = SensorType::ADDITIONAL_INFO;
additionalInfoEvent.timestamp = android::elapsedRealtimeNano();
+ AdditionalInfo info;
+ info.type = AdditionalInfo::AdditionalInfoType::AINFO_BEGIN;
+ info.serial = 1;
+ AdditionalInfo::AdditionalInfoPayload::Int32Values infoData;
+ for (int32_t i = 0; i < 14; i++) {
+ infoData.values[i] = i;
+ }
+ info.payload.set<AdditionalInfo::AdditionalInfoPayload::Tag::dataInt32>(infoData);
+ additionalInfoEvent.payload.set<Event::EventPayload::Tag::additional>(info);
Event injectedEvent;
injectedEvent.timestamp = android::elapsedRealtimeNano();
diff --git a/tv/tuner/aidl/default/Android.bp b/tv/tuner/aidl/default/Android.bp
index 65fa821..ed97d9c 100644
--- a/tv/tuner/aidl/default/Android.bp
+++ b/tv/tuner/aidl/default/Android.bp
@@ -23,6 +23,7 @@
"TimeFilter.cpp",
"Tuner.cpp",
"service.cpp",
+ "dtv_plugin.cpp",
],
static_libs: [
"libaidlcommonsupport",
diff --git a/tv/tuner/aidl/default/Demux.cpp b/tv/tuner/aidl/default/Demux.cpp
index 11e7131..34e3442 100644
--- a/tv/tuner/aidl/default/Demux.cpp
+++ b/tv/tuner/aidl/default/Demux.cpp
@@ -20,7 +20,9 @@
#include <aidl/android/hardware/tv/tuner/DemuxQueueNotifyBits.h>
#include <aidl/android/hardware/tv/tuner/Result.h>
+#include <fmq/AidlMessageQueue.h>
#include <utils/Log.h>
+#include <thread>
#include "Demux.h"
namespace aidl {
@@ -29,6 +31,15 @@
namespace tv {
namespace tuner {
+using ::aidl::android::hardware::common::fmq::MQDescriptor;
+using ::aidl::android::hardware::common::fmq::SynchronizedReadWrite;
+using ::android::AidlMessageQueue;
+using ::android::hardware::EventFlag;
+
+using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;
+
#define WAIT_TIMEOUT 3000000000
Demux::Demux(int32_t demuxId, uint32_t filterTypes) {
@@ -45,6 +56,111 @@
close();
}
+::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
+ const std::shared_ptr<IDvrCallback>& in_cb,
+ std::shared_ptr<IDvr>* _aidl_return) {
+ ALOGV("%s", __FUNCTION__);
+
+ if (in_cb == nullptr) {
+ ALOGW("[Demux] DVR callback can't be null");
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+
+ set<int64_t>::iterator it;
+ switch (in_type) {
+ case DvrType::PLAYBACK:
+ mDvrPlayback = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
+ this->ref<Demux>());
+ if (!mDvrPlayback->createDvrMQ()) {
+ ALOGE("[Demux] cannot create dvr message queue");
+ mDvrPlayback = nullptr;
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::UNKNOWN_ERROR));
+ }
+
+ for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
+ if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
+ ALOGE("[Demux] Can't get filter info for DVR playback");
+ mDvrPlayback = nullptr;
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::UNKNOWN_ERROR));
+ }
+ }
+
+ ALOGI("Playback normal case");
+
+ *_aidl_return = mDvrPlayback;
+ return ::ndk::ScopedAStatus::ok();
+ case DvrType::RECORD:
+ mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
+ this->ref<Demux>());
+ if (!mDvrRecord->createDvrMQ()) {
+ mDvrRecord = nullptr;
+ *_aidl_return = mDvrRecord;
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::UNKNOWN_ERROR));
+ }
+
+ *_aidl_return = mDvrRecord;
+ return ::ndk::ScopedAStatus::ok();
+ default:
+ *_aidl_return = nullptr;
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+}
+
+void Demux::readIptvThreadLoop(dtv_plugin* interface, dtv_streamer* streamer, void* buf,
+ size_t buf_size, int timeout_ms, int buffer_timeout) {
+ Timer *timer, *fullBufferTimer;
+ while (mDemuxIptvReadThreadRunning) {
+ if (mIsIptvDvrFMQFull && fullBufferTimer->get_elapsed_time_ms() > buffer_timeout) {
+ ALOGE("DVR FMQ has not been flushed within timeout of %d ms", buffer_timeout);
+ delete fullBufferTimer;
+ break;
+ }
+ timer = new Timer();
+ ssize_t bytes_read = interface->read_stream(streamer, buf, buf_size, timeout_ms);
+ if (bytes_read == 0) {
+ double elapsed_time = timer->get_elapsed_time_ms();
+ if (elapsed_time > timeout_ms) {
+ ALOGE("[Demux] timeout reached - elapsed_time: %f, timeout: %d", elapsed_time,
+ timeout_ms);
+ }
+ ALOGE("[Demux] Cannot read data from the socket");
+ delete timer;
+ break;
+ }
+
+ delete timer;
+ ALOGI("Number of bytes read: %zd", bytes_read);
+ int result = mDvrPlayback->writePlaybackFMQ(buf, bytes_read);
+
+ switch (result) {
+ case DVR_WRITE_FAILURE_REASON_FMQ_FULL:
+ if (!mIsIptvDvrFMQFull) {
+ mIsIptvDvrFMQFull = true;
+ fullBufferTimer = new Timer();
+ }
+ ALOGI("Waiting for client to flush DVR FMQ.");
+ break;
+ case DVR_WRITE_FAILURE_REASON_UNKNOWN:
+ ALOGE("Failed to write data into DVR FMQ for unknown reason");
+ break;
+ case DVR_WRITE_SUCCESS:
+ ALOGI("Wrote %zd bytes to DVR FMQ", bytes_read);
+ break;
+ default:
+ ALOGI("Invalid DVR Status");
+ }
+ }
+ mDemuxIptvReadThreadRunning = false;
+}
+
::ndk::ScopedAStatus Demux::setFrontendDataSource(int32_t in_frontendId) {
ALOGV("%s", __FUNCTION__);
@@ -52,7 +168,6 @@
return ::ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int32_t>(Result::NOT_INITIALIZED));
}
-
mFrontend = mTuner->getFrontendById(in_frontendId);
if (mFrontend == nullptr) {
return ::ndk::ScopedAStatus::fromServiceSpecificError(
@@ -61,6 +176,58 @@
mTuner->setFrontendAsDemuxSource(in_frontendId, mDemuxId);
+ // if mFrontend is an IPTV frontend, create streamer to read TS data from socket
+ if (mFrontend->getFrontendType() == FrontendType::IPTV) {
+ // create a DVR instance on the demux
+ shared_ptr<IDvr> iptvDvr;
+
+ std::shared_ptr<IDvrCallback> dvrPlaybackCallback =
+ ::ndk::SharedRefBase::make<DvrPlaybackCallback>();
+
+ ::ndk::ScopedAStatus status =
+ openDvr(DvrType::PLAYBACK, IPTV_BUFFER_SIZE, dvrPlaybackCallback, &iptvDvr);
+ if (status.isOk()) {
+ ALOGI("DVR instance created");
+ }
+
+ // get plugin interface from frontend
+ dtv_plugin* interface = mFrontend->getIptvPluginInterface();
+ if (interface == nullptr) {
+ ALOGE("[Demux] getIptvPluginInterface(): plugin interface is null");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_STATE));
+ }
+ ALOGI("[Demux] getIptvPluginInterface(): plugin interface is not null");
+
+ // get streamer object from Frontend instance
+ dtv_streamer* streamer = mFrontend->getIptvPluginStreamer();
+ if (streamer == nullptr) {
+ ALOGE("[Demux] getIptvPluginStreamer(): streamer is null");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_STATE));
+ }
+ ALOGI("[Demux] getIptvPluginStreamer(): streamer is not null");
+
+ // get transport description from frontend
+ string transport_desc = mFrontend->getIptvTransportDescription();
+ ALOGI("[Demux] getIptvTransportDescription(): transport_desc: %s", transport_desc.c_str());
+
+ // call read_stream on the socket to populate the buffer with TS data
+ // while thread is alive, keep reading data
+ int timeout_ms = 20;
+ int buffer_timeout = 10000; // 10s
+ void* buf = malloc(sizeof(char) * IPTV_BUFFER_SIZE);
+ if (buf == nullptr) ALOGI("malloc buf failed");
+ ALOGI("[ INFO ] Allocated buffer of size %d", IPTV_BUFFER_SIZE);
+ ALOGI("Getting FMQ from DVR instance to write socket data");
+ mDemuxIptvReadThreadRunning = true;
+ mDemuxIptvReadThread = std::thread(&Demux::readIptvThreadLoop, this, interface, streamer,
+ buf, IPTV_BUFFER_SIZE, timeout_ms, buffer_timeout);
+ if (mDemuxIptvReadThread.joinable()) {
+ mDemuxIptvReadThread.join();
+ }
+ free(buf);
+ }
return ::ndk::ScopedAStatus::ok();
}
@@ -193,61 +360,6 @@
return ::ndk::ScopedAStatus::ok();
}
-::ndk::ScopedAStatus Demux::openDvr(DvrType in_type, int32_t in_bufferSize,
- const std::shared_ptr<IDvrCallback>& in_cb,
- std::shared_ptr<IDvr>* _aidl_return) {
- ALOGV("%s", __FUNCTION__);
-
- if (in_cb == nullptr) {
- ALOGW("[Demux] DVR callback can't be null");
- *_aidl_return = nullptr;
- return ::ndk::ScopedAStatus::fromServiceSpecificError(
- static_cast<int32_t>(Result::INVALID_ARGUMENT));
- }
-
- set<int64_t>::iterator it;
- switch (in_type) {
- case DvrType::PLAYBACK:
- mDvrPlayback = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
- this->ref<Demux>());
- if (!mDvrPlayback->createDvrMQ()) {
- mDvrPlayback = nullptr;
- *_aidl_return = mDvrPlayback;
- return ::ndk::ScopedAStatus::fromServiceSpecificError(
- static_cast<int32_t>(Result::UNKNOWN_ERROR));
- }
-
- for (it = mPlaybackFilterIds.begin(); it != mPlaybackFilterIds.end(); it++) {
- if (!mDvrPlayback->addPlaybackFilter(*it, mFilters[*it])) {
- ALOGE("[Demux] Can't get filter info for DVR playback");
- mDvrPlayback = nullptr;
- *_aidl_return = mDvrPlayback;
- return ::ndk::ScopedAStatus::fromServiceSpecificError(
- static_cast<int32_t>(Result::UNKNOWN_ERROR));
- }
- }
-
- *_aidl_return = mDvrPlayback;
- return ::ndk::ScopedAStatus::ok();
- case DvrType::RECORD:
- mDvrRecord = ndk::SharedRefBase::make<Dvr>(in_type, in_bufferSize, in_cb,
- this->ref<Demux>());
- if (!mDvrRecord->createDvrMQ()) {
- mDvrRecord = nullptr;
- *_aidl_return = mDvrRecord;
- return ::ndk::ScopedAStatus::fromServiceSpecificError(
- static_cast<int32_t>(Result::UNKNOWN_ERROR));
- }
-
- *_aidl_return = mDvrRecord;
- return ::ndk::ScopedAStatus::ok();
- default:
- *_aidl_return = nullptr;
- return ::ndk::ScopedAStatus::fromServiceSpecificError(
- static_cast<int32_t>(Result::INVALID_ARGUMENT));
- }
-}
-
::ndk::ScopedAStatus Demux::connectCiCam(int32_t in_ciCamId) {
ALOGV("%s", __FUNCTION__);
diff --git a/tv/tuner/aidl/default/Demux.h b/tv/tuner/aidl/default/Demux.h
index 7d7aee4..a23063f 100644
--- a/tv/tuner/aidl/default/Demux.h
+++ b/tv/tuner/aidl/default/Demux.h
@@ -17,6 +17,7 @@
#pragma once
#include <aidl/android/hardware/tv/tuner/BnDemux.h>
+#include <aidl/android/hardware/tv/tuner/BnDvrCallback.h>
#include <fmq/AidlMessageQueue.h>
#include <math.h>
@@ -28,7 +29,9 @@
#include "Filter.h"
#include "Frontend.h"
#include "TimeFilter.h"
+#include "Timer.h"
#include "Tuner.h"
+#include "dtv_plugin.h"
using namespace std;
@@ -44,6 +47,8 @@
using ::android::hardware::EventFlag;
using FilterMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+using AidlMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+using AidlMQDesc = MQDescriptor<int8_t, SynchronizedReadWrite>;
class Dvr;
class Filter;
@@ -51,6 +56,19 @@
class TimeFilter;
class Tuner;
+class DvrPlaybackCallback : public BnDvrCallback {
+ public:
+ virtual ::ndk::ScopedAStatus onPlaybackStatus(PlaybackStatus status) override {
+ ALOGD("demux.h: playback status %d", status);
+ return ndk::ScopedAStatus::ok();
+ }
+
+ virtual ::ndk::ScopedAStatus onRecordStatus(RecordStatus status) override {
+ ALOGD("Record Status %hhd", status);
+ return ndk::ScopedAStatus::ok();
+ }
+};
+
class Demux : public BnDemux {
public:
Demux(int32_t demuxId, uint32_t filterTypes);
@@ -85,6 +103,8 @@
void setIsRecording(bool isRecording);
bool isRecording();
void startFrontendInputLoop();
+ void readIptvThreadLoop(dtv_plugin* interface, dtv_streamer* streamer, void* buf, size_t size,
+ int timeout_ms, int buffer_timeout);
/**
* A dispatcher to read and dispatch input data to all the started filters.
@@ -167,11 +187,16 @@
// Thread handlers
std::thread mFrontendInputThread;
+ std::thread mDemuxIptvReadThread;
+
+ // track whether the DVR FMQ for IPTV Playback is full
+ bool mIsIptvDvrFMQFull = false;
/**
* If a specific filter's writing loop is still running
*/
std::atomic<bool> mFrontendInputThreadRunning;
+ std::atomic<bool> mDemuxIptvReadThreadRunning;
std::atomic<bool> mKeepFetchingDataFromFrontend;
/**
diff --git a/tv/tuner/aidl/default/Dvr.cpp b/tv/tuner/aidl/default/Dvr.cpp
index c046ae3..f997b15 100644
--- a/tv/tuner/aidl/default/Dvr.cpp
+++ b/tv/tuner/aidl/default/Dvr.cpp
@@ -236,6 +236,20 @@
ALOGD("[Dvr] playback thread ended.");
}
+void Dvr::maySendIptvPlaybackStatusCallback() {
+ lock_guard<mutex> lock(mPlaybackStatusLock);
+ int availableToRead = mDvrMQ->availableToRead();
+ int availableToWrite = mDvrMQ->availableToWrite();
+
+ PlaybackStatus newStatus = checkPlaybackStatusChange(availableToWrite, availableToRead,
+ IPTV_PLAYBACK_STATUS_THRESHOLD_HIGH,
+ IPTV_PLAYBACK_STATUS_THRESHOLD_LOW);
+ if (mPlaybackStatus != newStatus) {
+ mCallback->onPlaybackStatus(newStatus);
+ mPlaybackStatus = newStatus;
+ }
+}
+
void Dvr::maySendPlaybackStatusCallback() {
lock_guard<mutex> lock(mPlaybackStatusLock);
int availableToRead = mDvrMQ->availableToRead();
@@ -443,6 +457,24 @@
return true;
}
+int Dvr::writePlaybackFMQ(void* buf, size_t size) {
+ lock_guard<mutex> lock(mWriteLock);
+ ALOGI("Playback status: %d", mPlaybackStatus);
+ if (mPlaybackStatus == PlaybackStatus::SPACE_FULL) {
+ ALOGW("[Dvr] stops writing and wait for the client side flushing.");
+ return DVR_WRITE_FAILURE_REASON_FMQ_FULL;
+ }
+ ALOGI("availableToWrite before: %zu", mDvrMQ->availableToWrite());
+ if (mDvrMQ->write((int8_t*)buf, size)) {
+ mDvrEventFlag->wake(static_cast<uint32_t>(DemuxQueueNotifyBits::DATA_READY));
+ ALOGI("availableToWrite: %zu", mDvrMQ->availableToWrite());
+ maySendIptvPlaybackStatusCallback();
+ return DVR_WRITE_SUCCESS;
+ }
+ maySendIptvPlaybackStatusCallback();
+ return DVR_WRITE_FAILURE_REASON_UNKNOWN;
+}
+
bool Dvr::writeRecordFMQ(const vector<int8_t>& data) {
lock_guard<mutex> lock(mWriteLock);
if (mRecordStatus == RecordStatus::OVERFLOW) {
diff --git a/tv/tuner/aidl/default/Dvr.h b/tv/tuner/aidl/default/Dvr.h
index 293c533..4af187b 100644
--- a/tv/tuner/aidl/default/Dvr.h
+++ b/tv/tuner/aidl/default/Dvr.h
@@ -43,6 +43,19 @@
using DvrMQ = AidlMessageQueue<int8_t, SynchronizedReadWrite>;
+const int DVR_WRITE_SUCCESS = 0;
+const int DVR_WRITE_FAILURE_REASON_FMQ_FULL = 1;
+const int DVR_WRITE_FAILURE_REASON_UNKNOWN = 2;
+
+const int TS_SIZE = 188;
+const int IPTV_BUFFER_SIZE = TS_SIZE * 7 * 8; // defined in service_streamer_udp in cbs v3 project
+
+// Thresholds are defined to indicate how full the buffers are.
+const double HIGH_THRESHOLD_PERCENT = 0.90;
+const double LOW_THRESHOLD_PERCENT = 0.15;
+const int IPTV_PLAYBACK_STATUS_THRESHOLD_HIGH = IPTV_BUFFER_SIZE * HIGH_THRESHOLD_PERCENT;
+const int IPTV_PLAYBACK_STATUS_THRESHOLD_LOW = IPTV_BUFFER_SIZE * LOW_THRESHOLD_PERCENT;
+
struct MediaEsMetaData {
bool isAudio;
int startIndex;
@@ -80,6 +93,7 @@
* Return false is any of the above processes fails.
*/
bool createDvrMQ();
+ int writePlaybackFMQ(void* buf, size_t size);
bool writeRecordFMQ(const std::vector<int8_t>& data);
bool addPlaybackFilter(int64_t filterId, std::shared_ptr<IFilter> filter);
bool removePlaybackFilter(int64_t filterId);
@@ -102,6 +116,7 @@
bool readDataFromMQ();
void getMetaDataValue(int& index, int8_t* dataOutputBuffer, int& value);
void maySendPlaybackStatusCallback();
+ void maySendIptvPlaybackStatusCallback();
void maySendRecordStatusCallback();
PlaybackStatus checkPlaybackStatusChange(uint32_t availableToWrite, uint32_t availableToRead,
int64_t highThreshold, int64_t lowThreshold);
diff --git a/tv/tuner/aidl/default/Frontend.cpp b/tv/tuner/aidl/default/Frontend.cpp
index cd072bf..6bdbac5 100644
--- a/tv/tuner/aidl/default/Frontend.cpp
+++ b/tv/tuner/aidl/default/Frontend.cpp
@@ -213,20 +213,82 @@
return ::ndk::ScopedAStatus::ok();
}
-::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& /* in_settings */) {
- ALOGV("%s", __FUNCTION__);
+void Frontend::readTuneByte(dtv_streamer* streamer, void* buf, size_t buf_size, int timeout_ms) {
+ ssize_t bytes_read = mIptvPluginInterface->read_stream(streamer, buf, buf_size, timeout_ms);
+ if (bytes_read == 0) {
+ ALOGI("[ ERROR ] Tune byte couldn't be read.");
+ return;
+ }
+ mCallback->onEvent(FrontendEventType::LOCKED);
+ mIsLocked = true;
+}
+
+::ndk::ScopedAStatus Frontend::tune(const FrontendSettings& in_settings) {
if (mCallback == nullptr) {
- ALOGW("[ WARN ] Frontend callback is not set when tune");
+ ALOGW("[ WARN ] Frontend callback is not set for tunin0g");
return ::ndk::ScopedAStatus::fromServiceSpecificError(
static_cast<int32_t>(Result::INVALID_STATE));
}
if (mType != FrontendType::IPTV) {
mTuner->frontendStartTune(mId);
- }
+ mCallback->onEvent(FrontendEventType::LOCKED);
+ mIsLocked = true;
+ } else {
+ // This is a reference implementation for IPTV. It uses an additional socket buffer.
+ // Vendors can use hardware memory directly to make the implementation more performant.
+ ALOGI("[ INFO ] Frontend type is set to IPTV, tag = %d id=%d", in_settings.getTag(),
+ mId);
- mCallback->onEvent(FrontendEventType::LOCKED);
- mIsLocked = true;
+ // load udp plugin for reading TS data
+ const char* path = "/vendor/lib/iptv_udp_plugin.so";
+ DtvPlugin* plugin = new DtvPlugin(path);
+ if (!plugin) {
+ ALOGE("Failed to create DtvPlugin, plugin_path is invalid");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+ bool plugin_loaded = plugin->load();
+ if (!plugin_loaded) {
+ ALOGE("Failed to load plugin");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+ mIptvPluginInterface = plugin->interface();
+
+ // validate content_url format
+ std::string content_url = in_settings.get<FrontendSettings::Tag::iptv>()->contentUrl;
+ std::string transport_desc = "{ \"uri\": \"" + content_url + "\"}";
+ ALOGI("[ INFO ] transport_desc: %s", transport_desc.c_str());
+ bool is_transport_desc_valid = plugin->validate(transport_desc.c_str());
+ if (!is_transport_desc_valid) { // not of format protocol://ip:port
+ ALOGE("[ INFO ] transport_desc is not valid");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+ mIptvTransportDescription = transport_desc;
+
+ // create a streamer and open it for reading data
+ dtv_streamer* streamer = mIptvPluginInterface->create_streamer();
+ mIptvPluginStreamer = streamer;
+ int open_fd = mIptvPluginInterface->open_stream(streamer, transport_desc.c_str());
+ if (open_fd < 0) {
+ ALOGE("[ INFO ] could not open stream");
+ return ::ndk::ScopedAStatus::fromServiceSpecificError(
+ static_cast<int32_t>(Result::INVALID_ARGUMENT));
+ }
+ ALOGI("[ INFO ] open_stream successful, open_fd=%d", open_fd);
+
+ size_t buf_size = 1;
+ int timeout_ms = 2000;
+ void* buf = malloc(sizeof(char) * buf_size);
+ if (buf == nullptr) ALOGI("malloc buf failed [TUNE]");
+ ALOGI("[ INFO ] [Tune] Allocated buffer of size %zu", buf_size);
+ mIptvFrontendTuneThread =
+ std::thread(&Frontend::readTuneByte, this, streamer, buf, buf_size, timeout_ms);
+ if (mIptvFrontendTuneThread.joinable()) mIptvFrontendTuneThread.join();
+ free(buf);
+ }
return ::ndk::ScopedAStatus::ok();
}
@@ -1002,6 +1064,18 @@
return mId;
}
+dtv_plugin* Frontend::getIptvPluginInterface() {
+ return mIptvPluginInterface;
+}
+
+string Frontend::getIptvTransportDescription() {
+ return mIptvTransportDescription;
+}
+
+dtv_streamer* Frontend::getIptvPluginStreamer() {
+ return mIptvPluginStreamer;
+}
+
bool Frontend::supportsSatellite() {
return mType == FrontendType::DVBS || mType == FrontendType::ISDBS ||
mType == FrontendType::ISDBS3;
diff --git a/tv/tuner/aidl/default/Frontend.h b/tv/tuner/aidl/default/Frontend.h
index 85bd636..17a1aee 100644
--- a/tv/tuner/aidl/default/Frontend.h
+++ b/tv/tuner/aidl/default/Frontend.h
@@ -21,6 +21,7 @@
#include <iostream>
#include <thread>
#include "Tuner.h"
+#include "dtv_plugin.h"
using namespace std;
@@ -60,6 +61,10 @@
FrontendType getFrontendType();
int32_t getFrontendId();
string getSourceFile();
+ dtv_plugin* getIptvPluginInterface();
+ string getIptvTransportDescription();
+ dtv_streamer* getIptvPluginStreamer();
+ void readTuneByte(dtv_streamer* streamer, void* buf, size_t size, int timeout_ms);
bool isLocked();
void getFrontendInfo(FrontendInfo* _aidl_return);
void setTunerService(std::shared_ptr<Tuner> tuner);
@@ -81,6 +86,10 @@
std::ifstream mFrontendData;
FrontendCapabilities mFrontendCaps;
vector<FrontendStatusType> mFrontendStatusCaps;
+ dtv_plugin* mIptvPluginInterface;
+ string mIptvTransportDescription;
+ dtv_streamer* mIptvPluginStreamer;
+ std::thread mIptvFrontendTuneThread;
};
} // namespace tuner
diff --git a/tv/tuner/aidl/default/Timer.h b/tv/tuner/aidl/default/Timer.h
new file mode 100644
index 0000000..c6327cb
--- /dev/null
+++ b/tv/tuner/aidl/default/Timer.h
@@ -0,0 +1,17 @@
+#include <chrono>
+using namespace std::chrono;
+class Timer {
+ public:
+ Timer() { start_time = steady_clock::now(); }
+
+ ~Timer() { stop_time = steady_clock::now(); }
+
+ double get_elapsed_time_ms() {
+ auto current_time = std::chrono::steady_clock::now();
+ return duration_cast<milliseconds>(current_time - start_time).count();
+ }
+
+ private:
+ time_point<steady_clock> start_time;
+ time_point<steady_clock> stop_time;
+};
\ No newline at end of file
diff --git a/tv/tuner/aidl/default/dtv_plugin.cpp b/tv/tuner/aidl/default/dtv_plugin.cpp
new file mode 100644
index 0000000..4e73ee5
--- /dev/null
+++ b/tv/tuner/aidl/default/dtv_plugin.cpp
@@ -0,0 +1,130 @@
+#include "dtv_plugin.h"
+#include <dlfcn.h>
+#include <libgen.h>
+#include <utils/Log.h>
+
+DtvPlugin::DtvPlugin(const char* plugin_path) {
+ path_ = plugin_path;
+ basename_ = basename(path_);
+ module_ = NULL;
+ interface_ = NULL;
+ loaded_ = false;
+}
+
+DtvPlugin::~DtvPlugin() {
+ if (module_ != NULL) {
+ if (dlclose(module_)) ALOGE("DtvPlugin: Failed to close plugin '%s'", basename_);
+ }
+}
+
+bool DtvPlugin::load() {
+ ALOGI("Loading plugin '%s' from path '%s'", basename_, path_);
+
+ module_ = dlopen(path_, RTLD_LAZY);
+ if (module_ == NULL) {
+ ALOGE("DtvPlugin::Load::Failed to load plugin '%s'", basename_);
+ ALOGE("dlopen error: %s", dlerror());
+ return false;
+ }
+
+ interface_ = (dtv_plugin*)dlsym(module_, "plugin_entry");
+
+ if (interface_ == NULL) {
+ ALOGE("plugin_entry is NULL.");
+ goto error;
+ }
+
+ if (!interface_->get_transport_types || !interface_->get_streamer_count ||
+ !interface_->validate || !interface_->create_streamer || !interface_->destroy_streamer ||
+ !interface_->open_stream || !interface_->close_stream || !interface_->read_stream) {
+ ALOGW("Plugin: missing one or more callbacks");
+ goto error;
+ }
+
+ loaded_ = true;
+
+ return true;
+
+error:
+ if (dlclose(module_)) ALOGE("Failed to close plugin '%s'", basename_);
+
+ return false;
+}
+
+int DtvPlugin::getStreamerCount() {
+ if (!loaded_) {
+ ALOGE("DtvPlugin::GetStreamerCount: Plugin '%s' not loaded!", basename_);
+ return 0;
+ }
+
+ return interface_->get_streamer_count();
+}
+
+bool DtvPlugin::isTransportTypeSupported(const char* transport_type) {
+ const char** transport;
+
+ if (!loaded_) {
+ ALOGE("Plugin '%s' not loaded!", basename_);
+ return false;
+ }
+
+ transport = interface_->get_transport_types();
+ if (transport == NULL) return false;
+
+ while (*transport) {
+ if (strcmp(transport_type, *transport) == 0) return true;
+ transport++;
+ }
+
+ return false;
+}
+
+bool DtvPlugin::validate(const char* transport_desc) {
+ if (!loaded_) {
+ ALOGE("Plugin '%s' is not loaded!", basename_);
+ return false;
+ }
+
+ return interface_->validate(transport_desc);
+}
+
+bool DtvPlugin::getProperty(const char* key, void* value, int* size) {
+ if (!loaded_) {
+ ALOGE("Plugin '%s' is not loaded!", basename_);
+ return false;
+ }
+
+ if (!interface_->get_property) return false;
+
+ *size = interface_->get_property(NULL, key, value, *size);
+
+ return *size < 0 ? false : true;
+}
+
+bool DtvPlugin::setProperty(const char* key, const void* value, int size) {
+ int ret;
+
+ if (!loaded_) {
+ ALOGE("Plugin '%s': not loaded!", basename_);
+ return false;
+ }
+
+ if (!interface_->set_property) return false;
+
+ ret = interface_->set_property(NULL, key, value, size);
+
+ return ret < 0 ? false : true;
+}
+
+struct dtv_plugin* DtvPlugin::interface() {
+ if (!loaded_) {
+ ALOGE("Plugin '%s' is not loaded!", basename_);
+ return NULL;
+ }
+
+ return interface_;
+}
+
+const char* DtvPlugin::pluginBasename() {
+ return basename_;
+}
diff --git a/tv/tuner/aidl/default/dtv_plugin.h b/tv/tuner/aidl/default/dtv_plugin.h
new file mode 100644
index 0000000..0ee5489
--- /dev/null
+++ b/tv/tuner/aidl/default/dtv_plugin.h
@@ -0,0 +1,31 @@
+#ifndef LIVE_DTV_PLUGIN_H_
+#define LIVE_DTV_PLUGIN_H_
+
+#include <fstream>
+#include "dtv_plugin_api.h"
+
+class DtvPlugin {
+ public:
+ DtvPlugin(const char* plugin_path);
+ ~DtvPlugin();
+
+ bool load();
+ int getStreamerCount();
+ bool validate(const char* transport_desc);
+ bool isTransportTypeSupported(const char* transport_type);
+ // /* plugin-wide properties */
+ bool getProperty(const char* key, void* value, int* size);
+ bool setProperty(const char* key, const void* value, int size);
+
+ struct dtv_plugin* interface();
+ const char* pluginBasename();
+
+ protected:
+ const char* path_;
+ char* basename_;
+ void* module_;
+ struct dtv_plugin* interface_;
+ bool loaded_;
+};
+
+#endif // LIVE_DTV_PLUGIN_H_
diff --git a/tv/tuner/aidl/default/dtv_plugin_api.h b/tv/tuner/aidl/default/dtv_plugin_api.h
new file mode 100644
index 0000000..8fe7c1d
--- /dev/null
+++ b/tv/tuner/aidl/default/dtv_plugin_api.h
@@ -0,0 +1,137 @@
+#ifndef LIVE_DTV_PLUGIN_API_H_
+#define LIVE_DTV_PLUGIN_API_H_
+
+#include <stdint.h>
+
+struct dtv_streamer;
+
+struct dtv_plugin {
+ uint32_t version;
+
+ /**
+ * get_transport_types() - Retrieve a list of supported transport types.
+ *
+ * Return: A NULL-terminated list of supported transport types.
+ */
+ const char** (*get_transport_types)(void);
+
+ /**
+ * get_streamer_count() - Get number of streamers that can be created.
+ *
+ * Return: The number of streamers that can be created.
+ */
+ int (*get_streamer_count)(void);
+
+ /**
+ * validate() - Check if transport description is valid.
+ * @transport_desc: NULL-terminated transport description in json format.
+ *
+ * Return: 1 if valid, 0 otherwise.
+ */
+ int (*validate)(const char* transport_desc);
+
+ /**
+ * create_streamer() - Create a streamer object.
+ *
+ * Return: A pointer to a new streamer object.
+ */
+ struct dtv_streamer* (*create_streamer)(void);
+
+ /**
+ * destroy_streamer() - Free a streamer object and all associated resources.
+ * @st: Pointer to a streamer object
+ */
+ void (*destroy_streamer)(struct dtv_streamer* streamer);
+
+ /**
+ * set_property() - Set a key/value pair property.
+ * @streamer: Pointer to a streamer object (may be NULL for plugin-wide properties).
+ * @key: NULL-terminated property name.
+ * @value: Property value.
+ * @size: Property value size.
+ *
+ * Return: 0 if success, -1 otherwise.
+ */
+ int (*set_property)(struct dtv_streamer* streamer, const char* key, const void* value,
+ size_t size);
+
+ /**
+ * get_property() - Get a property's value.
+ * @streamer: Pointer to a streamer (may be NULL for plugin-wide properties).
+ * @key: NULL-terminated property name.
+ * @value: Property value.
+ * @size: Property value size.
+ *
+ * Return: >= 0 if success, -1 otherwise.
+ *
+ * If size is 0, get_property will return the size needed to hold the value.
+ */
+ int (*get_property)(struct dtv_streamer* streamer, const char* key, void* value, size_t size);
+
+ /**
+ * add_pid() - Add a TS filter on a given pid.
+ * @streamer: The streamer that outputs the TS.
+ * @pid: The pid to add to the TS output.
+ *
+ * Return: 0 if success, -1 otherwise.
+ *
+ * This function is optional but can be useful if a hardware remux is
+ * available.
+ */
+ int (*add_pid)(struct dtv_streamer* streamer, int pid);
+
+ /**
+ * remove_pid() - Remove a TS filter on a given pid.
+ * @streamer: The streamer that outputs the TS.
+ * @pid: The pid to remove from the TS output.
+ *
+ * Return: 0 if success, -1 otherwise.
+ *
+ * This function is optional.
+ */
+ int (*remove_pid)(struct dtv_streamer* streamer, int pid);
+
+ /**
+ * open_stream() - Open a stream from a transport description.
+ * @streamer: The streamer which will handle the stream.
+ * @transport_desc: NULL-terminated transport description in json format.
+ *
+ * The streamer will allocate the resources and make the appropriate
+ * connections to handle this transport.
+ * This function returns a file descriptor that can be polled for events.
+ *
+ * Return: A file descriptor if success, -1 otherwise.
+ */
+ int (*open_stream)(struct dtv_streamer* streamer, const char* transport_desc);
+
+ /**
+ * close_stream() - Release an open stream.
+ * @streamer: The streamer from which the stream should be released.
+ */
+ void (*close_stream)(struct dtv_streamer* streamer);
+
+ /**
+ * read_stream() - Read stream data.
+ * @streamer: The streamer to read from.
+ * @buf: The destination buffer.
+ * @count: The number of bytes to read.
+ * @timeout_ms: Timeout in ms.
+ *
+ * Return: The number of bytes read, -1 if error.
+ */
+ ssize_t (*read_stream)(struct dtv_streamer* streamer, void* buf, size_t count, int timeout_ms);
+};
+
+struct dtv_plugin_event {
+ int id;
+ char data[0];
+};
+
+enum {
+ DTV_PLUGIN_EVENT_SIGNAL_LOST = 1,
+ DTV_PLUGIN_EVENT_SIGNAL_READY,
+};
+
+#define PROPERTY_STATISTICS "statistics"
+
+#endif // LIVE_DTV_PLUGIN_API_H_
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.cpp b/tv/tuner/aidl/vts/functional/FilterTests.cpp
index 53afef7..533d0e6 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.cpp
+++ b/tv/tuner/aidl/vts/functional/FilterTests.cpp
@@ -305,13 +305,18 @@
ndk::ScopedAStatus status;
status = mFilters[filterId]->configureMonitorEvent(monitorEventTypes);
+ return AssertionResult(status.isOk());
+}
+
+AssertionResult FilterTests::testMonitorEvent(uint64_t filterId, uint32_t monitorEventTypes) {
+ EXPECT_TRUE(mFilterCallbacks[filterId]) << "Test with getNewlyOpenedFilterId first.";
if (monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::SCRAMBLING_STATUS)) {
mFilterCallbacks[filterId]->testFilterScramblingEvent();
}
if (monitorEventTypes & static_cast<int32_t>(DemuxFilterMonitorEventType::IP_CID_CHANGE)) {
mFilterCallbacks[filterId]->testFilterIpCidEvent();
}
- return AssertionResult(status.isOk());
+ return AssertionResult(true);
}
AssertionResult FilterTests::startIdTest(int64_t filterId) {
diff --git a/tv/tuner/aidl/vts/functional/FilterTests.h b/tv/tuner/aidl/vts/functional/FilterTests.h
index f579441..f57093e 100644
--- a/tv/tuner/aidl/vts/functional/FilterTests.h
+++ b/tv/tuner/aidl/vts/functional/FilterTests.h
@@ -124,6 +124,7 @@
AssertionResult configAvFilterStreamType(AvStreamType type, int64_t filterId);
AssertionResult configIpFilterCid(int32_t ipCid, int64_t filterId);
AssertionResult configureMonitorEvent(int64_t filterId, int32_t monitorEventTypes);
+ AssertionResult testMonitorEvent(uint64_t filterId, uint32_t monitorEventTypes);
AssertionResult getFilterMQDescriptor(int64_t filterId, bool getMqDesc);
AssertionResult startFilter(int64_t filterId);
AssertionResult stopFilter(int64_t filterId);
diff --git a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
index 9db82c8..3664b6c 100644
--- a/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
+++ b/tv/tuner/aidl/vts/functional/VtsHalTvTunerTargetTest.cpp
@@ -60,6 +60,11 @@
}
ASSERT_TRUE(mFilterTests.getFilterMQDescriptor(filterId, filterConf.getMqDesc));
ASSERT_TRUE(mFilterTests.startFilter(filterId));
+ ASSERT_TRUE(mFrontendTests.tuneFrontend(frontendConf, true /*testWithDemux*/));
+ if (filterConf.monitorEventTypes > 0) {
+ ASSERT_TRUE(mFilterTests.testMonitorEvent(filterId, filterConf.monitorEventTypes));
+ }
+ ASSERT_TRUE(mFrontendTests.stopTuneFrontend(true /*testWithDemux*/));
ASSERT_TRUE(mFilterTests.stopFilter(filterId));
ASSERT_TRUE(mFilterTests.closeFilter(filterId));
ASSERT_TRUE(mDemuxTests.closeDemux());
diff --git a/usb/aidl/Android.bp b/usb/aidl/Android.bp
index b82f6d5..b61576d 100644
--- a/usb/aidl/Android.bp
+++ b/usb/aidl/Android.bp
@@ -45,6 +45,6 @@
},
],
- frozen: true,
+ frozen: false,
}
diff --git a/usb/aidl/aidl_api/android.hardware.usb/current/android/hardware/usb/ComplianceWarning.aidl b/usb/aidl/aidl_api/android.hardware.usb/current/android/hardware/usb/ComplianceWarning.aidl
index 8b67070..c7c9103 100644
--- a/usb/aidl/aidl_api/android.hardware.usb/current/android/hardware/usb/ComplianceWarning.aidl
+++ b/usb/aidl/aidl_api/android.hardware.usb/current/android/hardware/usb/ComplianceWarning.aidl
@@ -38,4 +38,9 @@
DEBUG_ACCESSORY = 2,
BC_1_2 = 3,
MISSING_RP = 4,
+ INPUT_POWER_LIMITED = 5,
+ MISSING_DATA_LINES = 6,
+ ENUMERATION_FAIL = 7,
+ FLAKY_CONNECTION = 8,
+ UNRELIABLE_IO = 9,
}
diff --git a/usb/aidl/android/hardware/usb/ComplianceWarning.aidl b/usb/aidl/android/hardware/usb/ComplianceWarning.aidl
index 4c18a31..bf79399 100644
--- a/usb/aidl/android/hardware/usb/ComplianceWarning.aidl
+++ b/usb/aidl/android/hardware/usb/ComplianceWarning.aidl
@@ -56,4 +56,29 @@
* Type-C Cable and Connector Specification.
*/
MISSING_RP = 4,
+ /**
+ * Used to indicate the charging setups on the USB ports are unable to
+ * deliver negotiated power.
+ */
+ INPUT_POWER_LIMITED = 5,
+ /**
+ * Used to indicate the cable/connector on the USB ports are missing
+ * the required wires on the data pins to make data transfer.
+ */
+ MISSING_DATA_LINES = 6,
+ /**
+ * Used to indicate enumeration failures on the USB ports, potentially due to
+ * signal integrity issues or other causes.
+ */
+ ENUMERATION_FAIL = 7,
+ /**
+ * Used to indicate unexpected data disconnection on the USB ports,
+ * potentially due to signal integrity issues or other causes.
+ */
+ FLAKY_CONNECTION = 8,
+ /**
+ * Used to indicate unreliable or slow data transfer on the USB ports,
+ * potentially due to signal integrity issues or other causes.
+ */
+ UNRELIABLE_IO = 9,
}
diff --git a/usb/aidl/default/Android.bp b/usb/aidl/default/Android.bp
index 2c6ed07..ee8e479 100644
--- a/usb/aidl/default/Android.bp
+++ b/usb/aidl/default/Android.bp
@@ -34,7 +34,7 @@
"Usb.cpp",
],
shared_libs: [
- "android.hardware.usb-V2-ndk",
+ "android.hardware.usb-V3-ndk",
"libbase",
"libbinder_ndk",
"libcutils",
diff --git a/usb/aidl/default/android.hardware.usb-service.example.xml b/usb/aidl/default/android.hardware.usb-service.example.xml
index c3f07f5..7ac2067 100644
--- a/usb/aidl/default/android.hardware.usb-service.example.xml
+++ b/usb/aidl/default/android.hardware.usb-service.example.xml
@@ -1,7 +1,7 @@
<manifest version="1.0" type="device">
<hal format="aidl">
<name>android.hardware.usb</name>
- <version>2</version>
+ <version>3</version>
<interface>
<name>IUsb</name>
<instance>default</instance>
diff --git a/usb/aidl/vts/Android.bp b/usb/aidl/vts/Android.bp
index d0e0eec..cf9299e 100644
--- a/usb/aidl/vts/Android.bp
+++ b/usb/aidl/vts/Android.bp
@@ -34,7 +34,7 @@
"libbinder_ndk",
],
static_libs: [
- "android.hardware.usb-V2-ndk",
+ "android.hardware.usb-V3-ndk",
],
test_suites: [
"general-tests",
diff --git a/usb/aidl/vts/VtsAidlUsbTargetTest.cpp b/usb/aidl/vts/VtsAidlUsbTargetTest.cpp
index e9aa65b..7b7269d 100644
--- a/usb/aidl/vts/VtsAidlUsbTargetTest.cpp
+++ b/usb/aidl/vts/VtsAidlUsbTargetTest.cpp
@@ -654,11 +654,18 @@
EXPECT_EQ(2, usb_last_cookie);
EXPECT_EQ(transactionId, last_transactionId);
- // Current compliance values range from [1, 4]
if (usb_last_port_status.supportsComplianceWarnings) {
for (auto warning : usb_last_port_status.complianceWarnings) {
EXPECT_TRUE((int)warning >= (int)ComplianceWarning::OTHER);
- EXPECT_TRUE((int)warning <= (int)ComplianceWarning::MISSING_RP);
+ /*
+ * Version 2 compliance values range from [1, 4]
+ * Version 3 compliance values range from [1, 9]
+ */
+ if (usb_version < 3) {
+ EXPECT_TRUE((int)warning <= (int)ComplianceWarning::MISSING_RP);
+ } else {
+ EXPECT_TRUE((int)warning <= (int)ComplianceWarning::UNRELIABLE_IO);
+ }
}
}
diff --git a/wifi/aidl/default/wifi.cpp b/wifi/aidl/default/wifi.cpp
index 255266b..12017b6 100644
--- a/wifi/aidl/default/wifi.cpp
+++ b/wifi/aidl/default/wifi.cpp
@@ -18,14 +18,151 @@
#include <android-base/file.h>
#include <android-base/logging.h>
+#include <cutils/properties.h>
+#include <sys/stat.h>
+#include <sys/sysmacros.h>
#include "aidl_return_util.h"
#include "aidl_sync_util.h"
#include "wifi_status_util.h"
namespace {
+using android::base::unique_fd;
+
// Starting Chip ID, will be assigned to primary chip
static constexpr int32_t kPrimaryChipId = 0;
+constexpr char kCpioMagic[] = "070701";
+constexpr char kTombstoneFolderPath[] = "/data/vendor/tombstones/wifi/";
+
+// Helper function for |cpioArchiveFilesInDir|
+bool cpioWriteHeader(int out_fd, struct stat& st, const char* file_name, size_t file_name_len) {
+ const int buf_size = 32 * 1024;
+ std::array<char, buf_size> read_buf;
+ ssize_t llen = snprintf(
+ read_buf.data(), buf_size, "%s%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X",
+ kCpioMagic, static_cast<int>(st.st_ino), st.st_mode, st.st_uid, st.st_gid,
+ static_cast<int>(st.st_nlink), static_cast<int>(st.st_mtime),
+ static_cast<int>(st.st_size), major(st.st_dev), minor(st.st_dev), major(st.st_rdev),
+ minor(st.st_rdev), static_cast<uint32_t>(file_name_len), 0);
+ if (write(out_fd, read_buf.data(), llen < buf_size ? llen : buf_size - 1) == -1) {
+ PLOG(ERROR) << "Error writing cpio header to file " << file_name;
+ return false;
+ }
+ if (write(out_fd, file_name, file_name_len) == -1) {
+ PLOG(ERROR) << "Error writing filename to file " << file_name;
+ return false;
+ }
+
+ // NUL Pad header up to 4 multiple bytes.
+ llen = (llen + file_name_len) % 4;
+ if (llen != 0) {
+ const uint32_t zero = 0;
+ if (write(out_fd, &zero, 4 - llen) == -1) {
+ PLOG(ERROR) << "Error padding 0s to file " << file_name;
+ return false;
+ }
+ }
+ return true;
+}
+
+// Helper function for |cpioArchiveFilesInDir|
+size_t cpioWriteFileContent(int fd_read, int out_fd, struct stat& st) {
+ // writing content of file
+ std::array<char, 32 * 1024> read_buf;
+ ssize_t llen = st.st_size;
+ size_t n_error = 0;
+ while (llen > 0) {
+ ssize_t bytes_read = read(fd_read, read_buf.data(), read_buf.size());
+ if (bytes_read == -1) {
+ PLOG(ERROR) << "Error reading file";
+ return ++n_error;
+ }
+ llen -= bytes_read;
+ if (write(out_fd, read_buf.data(), bytes_read) == -1) {
+ PLOG(ERROR) << "Error writing data to file";
+ return ++n_error;
+ }
+ if (bytes_read == 0) { // this should never happen, but just in case
+ // to unstuck from while loop
+ PLOG(ERROR) << "Unexpected read result";
+ n_error++;
+ break;
+ }
+ }
+ llen = st.st_size % 4;
+ if (llen != 0) {
+ const uint32_t zero = 0;
+ if (write(out_fd, &zero, 4 - llen) == -1) {
+ PLOG(ERROR) << "Error padding 0s to file";
+ return ++n_error;
+ }
+ }
+ return n_error;
+}
+
+// Helper function for |cpioArchiveFilesInDir|
+bool cpioWriteFileTrailer(int out_fd) {
+ const int buf_size = 4096;
+ std::array<char, buf_size> read_buf;
+ read_buf.fill(0);
+ ssize_t llen = snprintf(read_buf.data(), 4096, "070701%040X%056X%08XTRAILER!!!", 1, 0x0b, 0);
+ if (write(out_fd, read_buf.data(), (llen < buf_size ? llen : buf_size - 1) + 4) == -1) {
+ PLOG(ERROR) << "Error writing trailing bytes";
+ return false;
+ }
+ return true;
+}
+
+// Archives all files in |input_dir| and writes result into |out_fd|
+// Logic obtained from //external/toybox/toys/posix/cpio.c "Output cpio archive"
+// portion
+size_t cpioArchiveFilesInDir(int out_fd, const char* input_dir) {
+ struct dirent* dp;
+ size_t n_error = 0;
+ std::unique_ptr<DIR, decltype(&closedir)> dir_dump(opendir(input_dir), closedir);
+ if (!dir_dump) {
+ PLOG(ERROR) << "Failed to open directory";
+ return ++n_error;
+ }
+ while ((dp = readdir(dir_dump.get()))) {
+ if (dp->d_type != DT_REG) {
+ continue;
+ }
+ std::string cur_file_name(dp->d_name);
+ struct stat st;
+ const std::string cur_file_path = kTombstoneFolderPath + cur_file_name;
+ if (stat(cur_file_path.c_str(), &st) == -1) {
+ PLOG(ERROR) << "Failed to get file stat for " << cur_file_path;
+ n_error++;
+ continue;
+ }
+ const int fd_read = open(cur_file_path.c_str(), O_RDONLY);
+ if (fd_read == -1) {
+ PLOG(ERROR) << "Failed to open file " << cur_file_path;
+ n_error++;
+ continue;
+ }
+ std::string file_name_with_last_modified_time =
+ cur_file_name + "-" + std::to_string(st.st_mtime);
+ // string.size() does not include the null terminator. The cpio FreeBSD
+ // file header expects the null character to be included in the length.
+ const size_t file_name_len = file_name_with_last_modified_time.size() + 1;
+ unique_fd file_auto_closer(fd_read);
+ if (!cpioWriteHeader(out_fd, st, file_name_with_last_modified_time.c_str(),
+ file_name_len)) {
+ return ++n_error;
+ }
+ size_t write_error = cpioWriteFileContent(fd_read, out_fd, st);
+ if (write_error) {
+ return n_error + write_error;
+ }
+ }
+ if (!cpioWriteFileTrailer(out_fd)) {
+ return ++n_error;
+ }
+ return n_error;
+}
+
} // namespace
namespace aidl {
@@ -83,16 +220,18 @@
binder_status_t Wifi::dump(int fd, const char** args, uint32_t numArgs) {
const auto lock = acquireGlobalLock();
LOG(INFO) << "-----------Debug was called----------------";
- if (chips_.size() == 0) {
- LOG(INFO) << "No chips to display.";
- return STATUS_OK;
+ if (chips_.size() != 0) {
+ for (std::shared_ptr<WifiChip> chip : chips_) {
+ if (!chip.get()) continue;
+ chip->dump(fd, args, numArgs);
+ }
}
-
- for (std::shared_ptr<WifiChip> chip : chips_) {
- if (!chip.get()) continue;
- chip->dump(fd, args, numArgs);
+ uint32_t n_error = cpioArchiveFilesInDir(fd, kTombstoneFolderPath);
+ if (n_error != 0) {
+ LOG(ERROR) << n_error << " errors occurred in cpio function";
}
::android::base::WriteStringToFd("\n", fd);
+ fsync(fd);
return STATUS_OK;
}
diff --git a/wifi/aidl/default/wifi_chip.cpp b/wifi/aidl/default/wifi_chip.cpp
index 8265e5b..41b386c 100644
--- a/wifi/aidl/default/wifi_chip.cpp
+++ b/wifi/aidl/default/wifi_chip.cpp
@@ -32,13 +32,8 @@
#define P2P_MGMT_DEVICE_PREFIX "p2p-dev-"
namespace {
-using aidl::android::hardware::wifi::IfaceType;
-using aidl::android::hardware::wifi::IWifiChip;
-using CoexRestriction = aidl::android::hardware::wifi::IWifiChip::CoexRestriction;
-using ChannelCategoryMask = aidl::android::hardware::wifi::IWifiChip::ChannelCategoryMask;
using android::base::unique_fd;
-constexpr char kCpioMagic[] = "070701";
constexpr size_t kMaxBufferSizeBytes = 1024 * 1024 * 3;
constexpr uint32_t kMaxRingBufferFileAgeSeconds = 60 * 60 * 10;
constexpr uint32_t kMaxRingBufferFileNum = 20;
@@ -215,135 +210,6 @@
return success;
}
-// Helper function for |cpioArchiveFilesInDir|
-bool cpioWriteHeader(int out_fd, struct stat& st, const char* file_name, size_t file_name_len) {
- const int buf_size = 32 * 1024;
- std::array<char, buf_size> read_buf;
- ssize_t llen = snprintf(
- read_buf.data(), buf_size, "%s%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X%08X",
- kCpioMagic, static_cast<int>(st.st_ino), st.st_mode, st.st_uid, st.st_gid,
- static_cast<int>(st.st_nlink), static_cast<int>(st.st_mtime),
- static_cast<int>(st.st_size), major(st.st_dev), minor(st.st_dev), major(st.st_rdev),
- minor(st.st_rdev), static_cast<uint32_t>(file_name_len), 0);
- if (write(out_fd, read_buf.data(), llen < buf_size ? llen : buf_size - 1) == -1) {
- PLOG(ERROR) << "Error writing cpio header to file " << file_name;
- return false;
- }
- if (write(out_fd, file_name, file_name_len) == -1) {
- PLOG(ERROR) << "Error writing filename to file " << file_name;
- return false;
- }
-
- // NUL Pad header up to 4 multiple bytes.
- llen = (llen + file_name_len) % 4;
- if (llen != 0) {
- const uint32_t zero = 0;
- if (write(out_fd, &zero, 4 - llen) == -1) {
- PLOG(ERROR) << "Error padding 0s to file " << file_name;
- return false;
- }
- }
- return true;
-}
-
-// Helper function for |cpioArchiveFilesInDir|
-size_t cpioWriteFileContent(int fd_read, int out_fd, struct stat& st) {
- // writing content of file
- std::array<char, 32 * 1024> read_buf;
- ssize_t llen = st.st_size;
- size_t n_error = 0;
- while (llen > 0) {
- ssize_t bytes_read = read(fd_read, read_buf.data(), read_buf.size());
- if (bytes_read == -1) {
- PLOG(ERROR) << "Error reading file";
- return ++n_error;
- }
- llen -= bytes_read;
- if (write(out_fd, read_buf.data(), bytes_read) == -1) {
- PLOG(ERROR) << "Error writing data to file";
- return ++n_error;
- }
- if (bytes_read == 0) { // this should never happen, but just in case
- // to unstuck from while loop
- PLOG(ERROR) << "Unexpected read result";
- n_error++;
- break;
- }
- }
- llen = st.st_size % 4;
- if (llen != 0) {
- const uint32_t zero = 0;
- if (write(out_fd, &zero, 4 - llen) == -1) {
- PLOG(ERROR) << "Error padding 0s to file";
- return ++n_error;
- }
- }
- return n_error;
-}
-
-// Helper function for |cpioArchiveFilesInDir|
-bool cpioWriteFileTrailer(int out_fd) {
- const int buf_size = 4096;
- std::array<char, buf_size> read_buf;
- read_buf.fill(0);
- ssize_t llen = snprintf(read_buf.data(), 4096, "070701%040X%056X%08XTRAILER!!!", 1, 0x0b, 0);
- if (write(out_fd, read_buf.data(), (llen < buf_size ? llen : buf_size - 1) + 4) == -1) {
- PLOG(ERROR) << "Error writing trailing bytes";
- return false;
- }
- return true;
-}
-
-// Archives all files in |input_dir| and writes result into |out_fd|
-// Logic obtained from //external/toybox/toys/posix/cpio.c "Output cpio archive"
-// portion
-size_t cpioArchiveFilesInDir(int out_fd, const char* input_dir) {
- struct dirent* dp;
- size_t n_error = 0;
- std::unique_ptr<DIR, decltype(&closedir)> dir_dump(opendir(input_dir), closedir);
- if (!dir_dump) {
- PLOG(ERROR) << "Failed to open directory";
- return ++n_error;
- }
- while ((dp = readdir(dir_dump.get()))) {
- if (dp->d_type != DT_REG) {
- continue;
- }
- std::string cur_file_name(dp->d_name);
- struct stat st;
- const std::string cur_file_path = kTombstoneFolderPath + cur_file_name;
- if (stat(cur_file_path.c_str(), &st) == -1) {
- PLOG(ERROR) << "Failed to get file stat for " << cur_file_path;
- n_error++;
- continue;
- }
- const int fd_read = open(cur_file_path.c_str(), O_RDONLY);
- if (fd_read == -1) {
- PLOG(ERROR) << "Failed to open file " << cur_file_path;
- n_error++;
- continue;
- }
- std::string file_name_with_last_modified_time =
- cur_file_name + "-" + std::to_string(st.st_mtime);
- // string.size() does not include the null terminator. The cpio FreeBSD
- // file header expects the null character to be included in the length.
- const size_t file_name_len = file_name_with_last_modified_time.size() + 1;
- unique_fd file_auto_closer(fd_read);
- if (!cpioWriteHeader(out_fd, st, file_name_with_last_modified_time.c_str(),
- file_name_len)) {
- return ++n_error;
- }
- size_t write_error = cpioWriteFileContent(fd_read, out_fd, st);
- if (write_error) {
- return n_error + write_error;
- }
- }
- if (!cpioWriteFileTrailer(out_fd)) {
- return ++n_error;
- }
- return n_error;
-}
-
// Helper function to create a non-const char*.
std::vector<char> makeCharVec(const std::string& str) {
std::vector<char> vec(str.size() + 1);
@@ -651,7 +517,7 @@
&WifiChip::setLatencyModeInternal, in_mode);
}
-binder_status_t WifiChip::dump(int fd, const char**, uint32_t) {
+binder_status_t WifiChip::dump(int fd __unused, const char**, uint32_t) {
{
std::unique_lock<std::mutex> lk(lock_t);
for (const auto& item : ringbuffer_map_) {
@@ -664,11 +530,6 @@
if (!writeRingbufferFilesInternal()) {
LOG(ERROR) << "Error writing files to flash";
}
- uint32_t n_error = cpioArchiveFilesInDir(fd, kTombstoneFolderPath);
- if (n_error != 0) {
- LOG(ERROR) << n_error << " errors occurred in cpio function";
- }
- fsync(fd);
return STATUS_OK;
}
diff --git a/wifi/common/aidl/Android.bp b/wifi/common/aidl/Android.bp
new file mode 100644
index 0000000..1913451
--- /dev/null
+++ b/wifi/common/aidl/Android.bp
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+ // 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"],
+}
+
+aidl_interface {
+ name: "android.hardware.wifi.common",
+ vendor_available: true,
+ srcs: [
+ "android/hardware/wifi/common/*.aidl",
+ ],
+ headers: [
+ "PersistableBundle_aidl",
+ ],
+ stability: "vintf",
+ backend: {
+ java: {
+ sdk_version: "module_current",
+ apex_available: [
+ "//apex_available:platform",
+ "com.android.wifi",
+ ],
+ min_sdk_version: "30",
+ },
+ cpp: {
+ enabled: false,
+ },
+ },
+}
diff --git a/wifi/common/aidl/aidl_api/android.hardware.wifi.common/current/android/hardware/wifi/common/OuiKeyedData.aidl b/wifi/common/aidl/aidl_api/android.hardware.wifi.common/current/android/hardware/wifi/common/OuiKeyedData.aidl
new file mode 100644
index 0000000..640a1f6
--- /dev/null
+++ b/wifi/common/aidl/aidl_api/android.hardware.wifi.common/current/android/hardware/wifi/common/OuiKeyedData.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// 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.wifi.common;
+@VintfStability
+parcelable OuiKeyedData {
+ int oui;
+ android.os.PersistableBundle vendorData;
+}
diff --git a/wifi/common/aidl/android/hardware/wifi/common/OuiKeyedData.aidl b/wifi/common/aidl/android/hardware/wifi/common/OuiKeyedData.aidl
new file mode 100644
index 0000000..4e6a99f
--- /dev/null
+++ b/wifi/common/aidl/android/hardware/wifi/common/OuiKeyedData.aidl
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.wifi.common;
+
+import android.os.PersistableBundle;
+
+/**
+ * Data for OUI-based configuration.
+ */
+@VintfStability
+parcelable OuiKeyedData {
+ /**
+ * OUI : 24-bit organizationally unique identifier to identify the vendor/OEM.
+ * See https://standards-oui.ieee.org/ for more information.
+ */
+ int oui;
+ /**
+ * Vendor data. Expected fields should be defined by the vendor.
+ */
+ PersistableBundle vendorData;
+}
diff --git a/wifi/netlinkinterceptor/aidl/default/Android.bp b/wifi/netlinkinterceptor/aidl/default/Android.bp
index 5227e51..c3a0c03 100644
--- a/wifi/netlinkinterceptor/aidl/default/Android.bp
+++ b/wifi/netlinkinterceptor/aidl/default/Android.bp
@@ -25,8 +25,6 @@
cc_binary {
name: "android.hardware.net.nlinterceptor-service.default",
- init_rc: ["nlinterceptor-default.rc"],
- vintf_fragments: ["nlinterceptor-default.xml"],
vendor: true,
relative_install_path: "hw",
defaults: ["nlinterceptor@defaults"],
@@ -45,4 +43,35 @@
"service.cpp",
"util.cpp",
],
+ installable: false, // installed in APEX
+}
+
+apex {
+ name: "com.android.hardware.net.nlinterceptor",
+ vendor: true,
+ manifest: "apex_manifest.json",
+ file_contexts: "apex_file_contexts",
+ key: "com.android.hardware.key",
+ certificate: ":com.android.hardware.certificate",
+ updatable: false,
+ binaries: [
+ "android.hardware.net.nlinterceptor-service.default",
+ ],
+ prebuilts: [
+ "nlinterceptor.rc",
+ "nlinterceptor.xml",
+ ],
+}
+
+prebuilt_etc {
+ name: "nlinterceptor.rc",
+ src: "nlinterceptor.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "nlinterceptor.xml",
+ src: "nlinterceptor.xml",
+ sub_dir: "vintf",
+ installable: false,
}
diff --git a/wifi/netlinkinterceptor/aidl/default/apex_file_contexts b/wifi/netlinkinterceptor/aidl/default/apex_file_contexts
new file mode 100644
index 0000000..6ee544c
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/apex_file_contexts
@@ -0,0 +1,3 @@
+(/.*)? u:object_r:vendor_file:s0
+/etc(/.*)? u:object_r:vendor_configs_file:s0
+/bin/hw/android\.hardware\.net\.nlinterceptor-service\.default u:object_r:hal_nlinterceptor_default_exec:s0
diff --git a/wifi/netlinkinterceptor/aidl/default/apex_manifest.json b/wifi/netlinkinterceptor/aidl/default/apex_manifest.json
new file mode 100644
index 0000000..4ffeac5
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/apex_manifest.json
@@ -0,0 +1,4 @@
+{
+ "name": "com.android.hardware.net.nlinterceptor",
+ "version": 1
+}
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc b/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc
deleted file mode 100644
index 353cb27..0000000
--- a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.rc
+++ /dev/null
@@ -1,4 +0,0 @@
-service nlinterceptor /vendor/bin/hw/android.hardware.net.nlinterceptor-service.default
- class hal
- user root
- group system inet
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc
new file mode 100644
index 0000000..ec9baa9
--- /dev/null
+++ b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.rc
@@ -0,0 +1,4 @@
+service nlinterceptor /apex/com.android.hardware.net.nlinterceptor/bin/hw/android.hardware.net.nlinterceptor-service.default
+ class hal
+ user root
+ group system inet
diff --git a/wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml b/wifi/netlinkinterceptor/aidl/default/nlinterceptor.xml
similarity index 100%
rename from wifi/netlinkinterceptor/aidl/default/nlinterceptor-default.xml
rename to wifi/netlinkinterceptor/aidl/default/nlinterceptor.xml