From 10c6fe2b8fa9c216d22de63bf3a92a5e1bc28ac4 Mon Sep 17 00:00:00 2001 From: Mikhail Naganov Date: Fri, 30 Sep 2022 23:49:17 +0000 Subject: [PATCH] audio: Add IBluetooth core interface This interface corresponds to BT-specific functions of the IPrimaryDevice in the HIDL HAL. Bug: 205884982 Test: atest VtsHalAudioCoreTargetTest Change-Id: I5f52d65e06f42e1a5fb2e51d27b79e94fe423eaa --- audio/aidl/Android.bp | 1 + .../hardware/audio/core/IBluetooth.aidl | 61 +++++++++ .../android/hardware/audio/core/IModule.aidl | 1 + .../hardware/audio/core/IBluetooth.aidl | 127 ++++++++++++++++++ .../android/hardware/audio/core/IModule.aidl | 15 +++ audio/aidl/default/Android.bp | 1 + audio/aidl/default/Bluetooth.cpp | 82 +++++++++++ audio/aidl/default/Module.cpp | 13 ++ .../default/include/core-impl/Bluetooth.h | 35 +++++ audio/aidl/default/include/core-impl/Module.h | 7 +- .../vts/VtsHalAudioCoreModuleTargetTest.cpp | 111 +++++++++++++++ 11 files changed, 452 insertions(+), 2 deletions(-) create mode 100644 audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetooth.aidl create mode 100644 audio/aidl/android/hardware/audio/core/IBluetooth.aidl create mode 100644 audio/aidl/default/Bluetooth.cpp create mode 100644 audio/aidl/default/include/core-impl/Bluetooth.h diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp index ef51361200..e69306eb32 100644 --- a/audio/aidl/Android.bp +++ b/audio/aidl/Android.bp @@ -112,6 +112,7 @@ aidl_interface { "android/hardware/audio/core/AudioMode.aidl", "android/hardware/audio/core/AudioPatch.aidl", "android/hardware/audio/core/AudioRoute.aidl", + "android/hardware/audio/core/IBluetooth.aidl", "android/hardware/audio/core/IConfig.aidl", "android/hardware/audio/core/IModule.aidl", "android/hardware/audio/core/IStreamCallback.aidl", diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetooth.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetooth.aidl new file mode 100644 index 0000000000..289c0c238e --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetooth.aidl @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/////////////////////////////////////////////////////////////////////////////// +// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE. // +/////////////////////////////////////////////////////////////////////////////// + +// This file is a snapshot of an AIDL file. Do not edit it manually. There are +// two cases: +// 1). this is a frozen version file - do not edit this in any case. +// 2). this is a 'current' file. If you make a backwards compatible change to +// the interface (from the latest frozen version), the build system will +// prompt you to update this file with `m -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.audio.core; +@VintfStability +interface IBluetooth { + android.hardware.audio.core.IBluetooth.ScoConfig setScoConfig(in android.hardware.audio.core.IBluetooth.ScoConfig config); + android.hardware.audio.core.IBluetooth.HfpConfig setHfpConfig(in android.hardware.audio.core.IBluetooth.HfpConfig config); + @JavaDerive(equals=true, toString=true) @VintfStability + parcelable ScoConfig { + @nullable android.media.audio.common.Boolean isEnabled; + @nullable android.media.audio.common.Boolean isNrecEnabled; + android.hardware.audio.core.IBluetooth.ScoConfig.Mode mode = android.hardware.audio.core.IBluetooth.ScoConfig.Mode.UNSPECIFIED; + @nullable @utf8InCpp String debugName; + @VintfStability + enum Mode { + UNSPECIFIED = 0, + SCO = 1, + SCO_WB = 2, + SCO_SWB = 3, + } + } + @JavaDerive(equals=true, toString=true) @VintfStability + parcelable HfpConfig { + @nullable android.media.audio.common.Boolean isEnabled; + @nullable android.media.audio.common.Int sampleRate; + @nullable android.media.audio.common.Float volume; + const int VOLUME_MIN = 0; + const int VOLUME_MAX = 1; + } +} diff --git a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl index a029d43609..4486b661b2 100644 --- a/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IModule.aidl @@ -36,6 +36,7 @@ package android.hardware.audio.core; interface IModule { void setModuleDebug(in android.hardware.audio.core.ModuleDebug debug); @nullable android.hardware.audio.core.ITelephony getTelephony(); + @nullable android.hardware.audio.core.IBluetooth getBluetooth(); android.media.audio.common.AudioPort connectExternalDevice(in android.media.audio.common.AudioPort templateIdAndAdditionalData); void disconnectExternalDevice(int portId); android.hardware.audio.core.AudioPatch[] getAudioPatches(); diff --git a/audio/aidl/android/hardware/audio/core/IBluetooth.aidl b/audio/aidl/android/hardware/audio/core/IBluetooth.aidl new file mode 100644 index 0000000000..21ac8e29a5 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IBluetooth.aidl @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.hardware.audio.core; + +import android.media.audio.common.Boolean; +import android.media.audio.common.Float; +import android.media.audio.common.Int; + +/** + * An instance of IBluetooth manages settings for the Hands-Free Profile (HFP) + * and the SCO Link. This interface is optional to implement and provide by the + * vendor. It needs to be provided only if the device actually supports BT SCO + * or HFP. + */ +@VintfStability +interface IBluetooth { + @JavaDerive(equals=true, toString=true) + @VintfStability + parcelable ScoConfig { + /** + * Whether BT SCO is enabled. The client might need to disable it + * when another profile (for example, A2DP) is activated. + */ + @nullable Boolean isEnabled; + /** + * Whether BT SCO Noise Reduction and Echo Cancellation are enabled. + */ + @nullable Boolean isNrecEnabled; + @VintfStability enum Mode { UNSPECIFIED, SCO, SCO_WB, SCO_SWB } + /** + * If set, specifies the SCO mode to use: + * regular, wide band (WB), or super wide band (SWB). + */ + Mode mode = Mode.UNSPECIFIED; + /** + * The name of the BT SCO headset used for debugging purposes. Can be empty. + */ + @nullable @utf8InCpp String debugName; + } + + /** + * Set the configuration of Bluetooth SCO. + * + * In the provided parcelable, the client sets zero, one or more parameters + * which have to be updated on the HAL side. The parameters that are left + * unset must retain their current values. It is allowed to change + * parameters while the SCO profile is disabled (isEnabled.value == false). + * + * In the returned parcelable, all parameter fields known to the HAL module + * must be populated to their current values. If the SCO profile is + * currently disabled (isEnabled.value == false), the parameters must + * reflect the last values that were in use. + * + * The client can pass an uninitialized parcelable in order to retrieve the + * current configuration. + * + * @return The current configuration (after update). All fields known to + * the HAL must be populated. + * @param config The configuration to set. Any number of fields may be left + * uninitialized. + * @throws EX_UNSUPPORTED_OPERATION If BT SCO is not supported. + * @throws EX_ILLEGAL_ARGUMENT If the requested combination of parameter + * values is invalid. + */ + ScoConfig setScoConfig(in ScoConfig config); + + @JavaDerive(equals=true, toString=true) + @VintfStability + parcelable HfpConfig { + /** + * Whether BT HFP is enabled. + */ + @nullable Boolean isEnabled; + /** + * The sample rate of BT HFP, in Hertz. Must be a positive number. + */ + @nullable Int sampleRate; + + const int VOLUME_MIN = 0; + const int VOLUME_MAX = 1; + /** + * The output volume of BT HFP. 1.0f means unity gain, 0.0f is muted, + * see VOLUME_* constants; + */ + @nullable Float volume; + } + + /** + * Set the configuration of Bluetooth HFP. + * + * In the provided parcelable, the client sets zero, one or more parameters + * which have to be updated on the HAL side. The parameters that are left + * unset must retain their current values. It is allowed to change + * parameters while the HFP profile is disabled (isEnabled.value == false). + * + * In the returned parcelable, all parameter fields known to the HAL module + * must be populated to their current values. If the HFP profile is + * currently disabled (isEnabled.value == false), the parameters must + * reflect the last values that were in use. + * + * The client can pass an uninitialized parcelable in order to retrieve the + * current configuration. + * + * @return The current configuration (after update). All fields known to + * the HAL must be populated. + * @param config The configuration to set. Any number of fields may be left + * uninitialized. + * @throws EX_UNSUPPORTED_OPERATION If BT HFP is not supported. + * @throws EX_ILLEGAL_ARGUMENT If the requested combination of parameter + * values is invalid. + */ + HfpConfig setHfpConfig(in HfpConfig config); +} diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl index 4779f1f3da..7bc1b9cba5 100644 --- a/audio/aidl/android/hardware/audio/core/IModule.aidl +++ b/audio/aidl/android/hardware/audio/core/IModule.aidl @@ -21,6 +21,7 @@ import android.hardware.audio.common.SourceMetadata; import android.hardware.audio.core.AudioMode; import android.hardware.audio.core.AudioPatch; import android.hardware.audio.core.AudioRoute; +import android.hardware.audio.core.IBluetooth; import android.hardware.audio.core.IStreamCallback; import android.hardware.audio.core.IStreamIn; import android.hardware.audio.core.IStreamOut; @@ -85,6 +86,20 @@ interface IModule { */ @nullable ITelephony getTelephony(); + /** + * Retrieve the interface to control Bluetooth SCO and HFP. + * + * If the HAL module supports either the SCO Link or Hands-Free Profile + * functionality (or both) for Bluetooth, it must return an instance of the + * IBluetooth interface. The same instance must be returned during the + * lifetime of the HAL module. If the HAL module does not support BT SCO and + * HFP, a null must be returned, without throwing any errors. + * + * @return An instance of the IBluetooth interface implementation. + * @throws EX_ILLEGAL_STATE If there was an error creating an instance. + */ + @nullable IBluetooth getBluetooth(); + /** * Set a device port of an external device into connected state. * diff --git a/audio/aidl/default/Android.bp b/audio/aidl/default/Android.bp index 8cd56c05c4..6f23636744 100644 --- a/audio/aidl/default/Android.bp +++ b/audio/aidl/default/Android.bp @@ -62,6 +62,7 @@ cc_library_static { export_include_dirs: ["include"], srcs: [ "AudioPolicyConfigXmlConverter.cpp", + "Bluetooth.cpp", "Config.cpp", "Configuration.cpp", "EngineConfigXmlConverter.cpp", diff --git a/audio/aidl/default/Bluetooth.cpp b/audio/aidl/default/Bluetooth.cpp new file mode 100644 index 0000000000..38e0c21ea8 --- /dev/null +++ b/audio/aidl/default/Bluetooth.cpp @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#define LOG_TAG "AHAL_Bluetooth" +#include + +#include "core-impl/Bluetooth.h" + +using aidl::android::media::audio::common::Boolean; +using aidl::android::media::audio::common::Float; +using aidl::android::media::audio::common::Int; + +namespace aidl::android::hardware::audio::core { + +Bluetooth::Bluetooth() { + mScoConfig.isEnabled = Boolean{false}; + mScoConfig.isNrecEnabled = Boolean{false}; + mScoConfig.mode = ScoConfig::Mode::SCO; + mHfpConfig.isEnabled = Boolean{false}; + mHfpConfig.sampleRate = Int{8000}; + mHfpConfig.volume = Float{HfpConfig::VOLUME_MAX}; +} + +ndk::ScopedAStatus Bluetooth::setScoConfig(const ScoConfig& in_config, ScoConfig* _aidl_return) { + if (in_config.isEnabled.has_value()) { + mScoConfig.isEnabled = in_config.isEnabled; + } + if (in_config.isNrecEnabled.has_value()) { + mScoConfig.isNrecEnabled = in_config.isNrecEnabled; + } + if (in_config.mode != ScoConfig::Mode::UNSPECIFIED) { + mScoConfig.mode = in_config.mode; + } + if (in_config.debugName.has_value()) { + mScoConfig.debugName = in_config.debugName; + } + *_aidl_return = mScoConfig; + LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning " + << _aidl_return->toString(); + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig* _aidl_return) { + if (in_config.sampleRate.has_value() && in_config.sampleRate.value().value <= 0) { + LOG(ERROR) << __func__ << ": invalid sample rate: " << in_config.sampleRate.value().value; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + if (in_config.volume.has_value() && (in_config.volume.value().value < HfpConfig::VOLUME_MIN || + in_config.volume.value().value > HfpConfig::VOLUME_MAX)) { + LOG(ERROR) << __func__ << ": invalid volume: " << in_config.volume.value().value; + return ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT); + } + + if (in_config.isEnabled.has_value()) { + mHfpConfig.isEnabled = in_config.isEnabled; + } + if (in_config.sampleRate.has_value()) { + mHfpConfig.sampleRate = in_config.sampleRate; + } + if (in_config.volume.has_value()) { + mHfpConfig.volume = in_config.volume; + } + *_aidl_return = mHfpConfig; + LOG(DEBUG) << __func__ << ": received " << in_config.toString() << ", returning " + << _aidl_return->toString(); + return ndk::ScopedAStatus::ok(); +} + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/Module.cpp b/audio/aidl/default/Module.cpp index b58c5621c4..e8b5bfc8f3 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -25,6 +25,7 @@ #include #include +#include "core-impl/Bluetooth.h" #include "core-impl/Module.h" #include "core-impl/SoundDose.h" #include "core-impl/Telephony.h" @@ -326,6 +327,18 @@ ndk::ScopedAStatus Module::getTelephony(std::shared_ptr* _aidl_retur return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Module::getBluetooth(std::shared_ptr* _aidl_return) { + if (mBluetooth == nullptr) { + mBluetooth = ndk::SharedRefBase::make(); + mBluetoothBinder = mBluetooth->asBinder(); + AIBinder_setMinSchedulerPolicy(mBluetoothBinder.get(), SCHED_NORMAL, + ANDROID_PRIORITY_AUDIO); + } + *_aidl_return = mBluetooth; + LOG(DEBUG) << __func__ << ": returning instance of IBluetooth: " << _aidl_return->get(); + return ndk::ScopedAStatus::ok(); +} + ndk::ScopedAStatus Module::connectExternalDevice(const AudioPort& in_templateIdAndAdditionalData, AudioPort* _aidl_return) { const int32_t templateId = in_templateIdAndAdditionalData.id; diff --git a/audio/aidl/default/include/core-impl/Bluetooth.h b/audio/aidl/default/include/core-impl/Bluetooth.h new file mode 100644 index 0000000000..f2e762dd40 --- /dev/null +++ b/audio/aidl/default/include/core-impl/Bluetooth.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#pragma once + +#include + +namespace aidl::android::hardware::audio::core { + +class Bluetooth : public BnBluetooth { + public: + Bluetooth(); + + private: + ndk::ScopedAStatus setScoConfig(const ScoConfig& in_config, ScoConfig* _aidl_return) override; + ndk::ScopedAStatus setHfpConfig(const HfpConfig& in_config, HfpConfig* _aidl_return) override; + + ScoConfig mScoConfig; + HfpConfig mHfpConfig; +}; + +} // namespace aidl::android::hardware::audio::core diff --git a/audio/aidl/default/include/core-impl/Module.h b/audio/aidl/default/include/core-impl/Module.h index faca61e481..e9f43d8643 100644 --- a/audio/aidl/default/include/core-impl/Module.h +++ b/audio/aidl/default/include/core-impl/Module.h @@ -39,6 +39,7 @@ class Module : public BnModule { ndk::ScopedAStatus setModuleDebug( const ::aidl::android::hardware::audio::core::ModuleDebug& in_debug) override; ndk::ScopedAStatus getTelephony(std::shared_ptr* _aidl_return) override; + ndk::ScopedAStatus getBluetooth(std::shared_ptr* _aidl_return) override; ndk::ScopedAStatus connectExternalDevice( const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData, ::aidl::android::media::audio::common::AudioPort* _aidl_return) override; @@ -127,10 +128,12 @@ class Module : public BnModule { const Type mType; std::unique_ptr mConfig; ModuleDebug mDebug; - // Since it is required to return the same instance of the ITelephony, even - // if the client has released it on its side, we need to hold it via a strong pointer. + // For the interfaces requiring to return the same instance, we need to hold them + // via a strong pointer. The binder token is retained for a call to 'setMinSchedulerPolicy'. std::shared_ptr mTelephony; ndk::SpAIBinder mTelephonyBinder; + std::shared_ptr mBluetooth; + ndk::SpAIBinder mBluetoothBinder; // ids of ports created at runtime via 'connectExternalDevice'. std::set mConnectedDevicePorts; Streams mStreams; diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp index 0451c0b19c..8da475e4a2 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -56,6 +56,7 @@ using aidl::android::hardware::audio::common::SourceMetadata; using aidl::android::hardware::audio::core::AudioMode; using aidl::android::hardware::audio::core::AudioPatch; using aidl::android::hardware::audio::core::AudioRoute; +using aidl::android::hardware::audio::core::IBluetooth; using aidl::android::hardware::audio::core::IModule; using aidl::android::hardware::audio::core::IStreamCommon; using aidl::android::hardware::audio::core::IStreamIn; @@ -85,6 +86,7 @@ using aidl::android::media::audio::common::AudioPortExt; using aidl::android::media::audio::common::AudioSource; using aidl::android::media::audio::common::AudioUsage; using aidl::android::media::audio::common::Float; +using aidl::android::media::audio::common::Int; using aidl::android::media::audio::common::Void; using android::hardware::audio::common::getChannelCount; using android::hardware::audio::common::isBitPositionFlagSet; @@ -1776,6 +1778,89 @@ TEST_P(AudioCoreModule, AddRemoveEffectInvalidArguments) { } } +class AudioCoreBluetooth : public AudioCoreModuleBase, public testing::TestWithParam { + public: + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam())); + ASSERT_IS_OK(module->getBluetooth(&bluetooth)); + } + + void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); } + + std::shared_ptr bluetooth; +}; + +TEST_P(AudioCoreBluetooth, SameInstance) { + if (bluetooth == nullptr) { + GTEST_SKIP() << "Bluetooth is not supported"; + } + std::shared_ptr bluetooth2; + EXPECT_IS_OK(module->getBluetooth(&bluetooth2)); + ASSERT_NE(nullptr, bluetooth2.get()); + EXPECT_EQ(bluetooth->asBinder(), bluetooth2->asBinder()) + << "getBluetooth must return the same interface instance across invocations"; +} + +TEST_P(AudioCoreBluetooth, ScoConfig) { + static const auto kStatuses = {EX_NONE, EX_UNSUPPORTED_OPERATION}; + if (bluetooth == nullptr) { + GTEST_SKIP() << "Bluetooth is not supported"; + } + ndk::ScopedAStatus status; + IBluetooth::ScoConfig scoConfig; + ASSERT_STATUS(kStatuses, status = bluetooth->setScoConfig({}, &scoConfig)); + if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + GTEST_SKIP() << "BT SCO is not supported"; + } + EXPECT_TRUE(scoConfig.isEnabled.has_value()); + EXPECT_TRUE(scoConfig.isNrecEnabled.has_value()); + EXPECT_NE(IBluetooth::ScoConfig::Mode::UNSPECIFIED, scoConfig.mode); + IBluetooth::ScoConfig scoConfig2; + ASSERT_IS_OK(bluetooth->setScoConfig(scoConfig, &scoConfig2)); + EXPECT_EQ(scoConfig, scoConfig2); +} + +TEST_P(AudioCoreBluetooth, HfpConfig) { + static const auto kStatuses = {EX_NONE, EX_UNSUPPORTED_OPERATION}; + if (bluetooth == nullptr) { + GTEST_SKIP() << "Bluetooth is not supported"; + } + ndk::ScopedAStatus status; + IBluetooth::HfpConfig hfpConfig; + ASSERT_STATUS(kStatuses, status = bluetooth->setHfpConfig({}, &hfpConfig)); + if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + GTEST_SKIP() << "BT HFP is not supported"; + } + EXPECT_TRUE(hfpConfig.isEnabled.has_value()); + EXPECT_TRUE(hfpConfig.sampleRate.has_value()); + EXPECT_TRUE(hfpConfig.volume.has_value()); + IBluetooth::HfpConfig hfpConfig2; + ASSERT_IS_OK(bluetooth->setHfpConfig(hfpConfig, &hfpConfig2)); + EXPECT_EQ(hfpConfig, hfpConfig2); +} + +TEST_P(AudioCoreBluetooth, HfpConfigInvalid) { + static const auto kStatuses = {EX_NONE, EX_UNSUPPORTED_OPERATION}; + if (bluetooth == nullptr) { + GTEST_SKIP() << "Bluetooth is not supported"; + } + ndk::ScopedAStatus status; + IBluetooth::HfpConfig hfpConfig; + ASSERT_STATUS(kStatuses, status = bluetooth->setHfpConfig({}, &hfpConfig)); + if (status.getExceptionCode() == EX_UNSUPPORTED_OPERATION) { + GTEST_SKIP() << "BT HFP is not supported"; + } + EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, + bluetooth->setHfpConfig({.sampleRate = Int{-1}}, &hfpConfig)); + EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, bluetooth->setHfpConfig({.sampleRate = Int{0}}, &hfpConfig)); + EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, + bluetooth->setHfpConfig({.volume = Float{IBluetooth::HfpConfig::VOLUME_MIN - 1}}, + &hfpConfig)); + EXPECT_STATUS(EX_ILLEGAL_ARGUMENT, + bluetooth->setHfpConfig({.volume = Float{IBluetooth::HfpConfig::VOLUME_MAX + 1}}, + &hfpConfig)); +} + class AudioCoreTelephony : public AudioCoreModuleBase, public testing::TestWithParam { public: void SetUp() override { @@ -1788,6 +1873,17 @@ class AudioCoreTelephony : public AudioCoreModuleBase, public testing::TestWithP std::shared_ptr telephony; }; +TEST_P(AudioCoreTelephony, SameInstance) { + if (telephony == nullptr) { + GTEST_SKIP() << "Telephony is not supported"; + } + std::shared_ptr telephony2; + EXPECT_IS_OK(module->getTelephony(&telephony2)); + ASSERT_NE(nullptr, telephony2.get()); + EXPECT_EQ(telephony->asBinder(), telephony2->asBinder()) + << "getTelephony must return the same interface instance across invocations"; +} + TEST_P(AudioCoreTelephony, GetSupportedAudioModes) { if (telephony == nullptr) { GTEST_SKIP() << "Telephony is not supported"; @@ -3039,6 +3135,17 @@ ndk::ScopedAStatus AudioCoreSoundDose::NoOpHalSoundDoseCallback::onNewMelValues( return ndk::ScopedAStatus::ok(); } +TEST_P(AudioCoreSoundDose, SameInstance) { + if (soundDose == nullptr) { + GTEST_SKIP() << "SoundDose is not supported"; + } + std::shared_ptr soundDose2; + EXPECT_IS_OK(module->getSoundDose(&soundDose2)); + ASSERT_NE(nullptr, soundDose2.get()); + EXPECT_EQ(soundDose->asBinder(), soundDose2->asBinder()) + << "getSoundDose must return the same interface instance across invocations"; +} + TEST_P(AudioCoreSoundDose, GetSetOutputRs2) { if (soundDose == nullptr) { GTEST_SKIP() << "SoundDose is not supported"; @@ -3085,6 +3192,10 @@ INSTANTIATE_TEST_SUITE_P(AudioCoreModuleTest, AudioCoreModule, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreModule); +INSTANTIATE_TEST_SUITE_P(AudioCoreBluetoothTest, AudioCoreBluetooth, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreBluetooth); INSTANTIATE_TEST_SUITE_P(AudioCoreTelephonyTest, AudioCoreTelephony, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString);