diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp index 2c91918d76..bb48ecd001 100644 --- a/audio/aidl/Android.bp +++ b/audio/aidl/Android.bp @@ -113,6 +113,7 @@ aidl_interface { "android/hardware/audio/core/AudioPatch.aidl", "android/hardware/audio/core/AudioRoute.aidl", "android/hardware/audio/core/IBluetooth.aidl", + "android/hardware/audio/core/IBluetoothA2dp.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/IBluetoothA2dp.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetoothA2dp.aidl new file mode 100644 index 0000000000..0f4c46d73f --- /dev/null +++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetoothA2dp.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 -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 IBluetoothA2dp { + boolean isEnabled(); + void setEnabled(boolean enabled); + boolean supportsOffloadReconfiguration(); + void reconfigureOffload(in android.hardware.audio.core.VendorParameter[] parameters); +} 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 1eafdaba57..f18d6a526e 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 @@ -37,6 +37,7 @@ 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(); + @nullable android.hardware.audio.core.IBluetoothA2dp getBluetoothA2dp(); 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/IBluetoothA2dp.aidl b/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl new file mode 100644 index 0000000000..dc4b8d0054 --- /dev/null +++ b/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl @@ -0,0 +1,83 @@ +/* + * 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.audio.core; + +import android.hardware.audio.core.VendorParameter; + +/** + * An instance of IBluetoothA2dp manages settings for the A2DP (Advanced Audio + * Distribution Profile) profiles. This interface is optional to implement by + * the vendor. It needs to be provided only if the device actually supports BT + * A2DP. + * + * This interface is separate from IBluetooth interface which manages SCO & HFP. + * The HAL module can handle both SCO and A2DP profiles or only one of them. + */ +@VintfStability +interface IBluetoothA2dp { + /** + * Whether BT A2DP is enabled. + * + * Returns the current state of A2DP support. The client might need to + * disable (suspend) A2DP when another profile (for example, SCO) is + * activated. + * + * @return Whether BT A2DP is enabled. + */ + boolean isEnabled(); + + /** + * Enable or disable A2DP. + * + * Sets the current state of A2DP support. The client might need to + * disable (suspend) A2DP when another profile (for example, SCO) is + * activated. + * + * @param enabled Whether BT A2DP must be enabled or suspended. + * @throws EX_ILLEGAL_STATE If there was an error performing the operation. + */ + void setEnabled(boolean enabled); + + /** + * Indicates whether the module supports reconfiguration of offloaded codecs. + * + * Offloaded coded implementations may need to be reconfigured when the + * active A2DP device changes. This method indicates whether the HAL module + * supports the reconfiguration event. The result returned from this method + * must not change over time. + * + * @return Whether reconfiguration offload of offloaded codecs is supported. + */ + boolean supportsOffloadReconfiguration(); + + /** + * Instructs the HAL module to reconfigure offloaded codec. + * + * Offloaded coded implementations may need to be reconfigured when the + * active A2DP device changes. This method is a notification for the HAL + * module to commence reconfiguration. + * + * Note that 'EX_UNSUPPORTED_OPERATION' may only be thrown when + * 'supportsOffloadReconfiguration' returns 'false'. + * + * @param parameter Optional vendor-specific parameters, can be left empty. + * @throws EX_ILLEGAL_STATE If there was an error performing the operation, + * or the operation can not be commenced in the current state. + * @throws EX_UNSUPPORTED_OPERATION If the module does not support codec reconfiguration. + */ + void reconfigureOffload(in VendorParameter[] parameters); +} diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl index edfb9f27f6..5a6df97a89 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.AudioPatch; import android.hardware.audio.core.AudioRoute; import android.hardware.audio.core.IBluetooth; +import android.hardware.audio.core.IBluetoothA2dp; import android.hardware.audio.core.IStreamCallback; import android.hardware.audio.core.IStreamIn; import android.hardware.audio.core.IStreamOut; @@ -102,6 +103,20 @@ interface IModule { */ @nullable IBluetooth getBluetooth(); + /** + * Retrieve the interface to control Bluetooth A2DP. + * + * If the HAL module supports A2DP Profile functionality for Bluetooth, it + * must return an instance of the IBluetoothA2dp interface. The same + * instance must be returned during the lifetime of the HAL module. If the + * HAL module does not support BT A2DP, a null must be returned, without + * throwing any errors. + * + * @return An instance of the IBluetoothA2dp interface implementation. + * @throws EX_ILLEGAL_STATE If there was an error creating an instance. + */ + @nullable IBluetoothA2dp getBluetoothA2dp(); + /** * Set a device port of an external device into connected state. * diff --git a/audio/aidl/default/Bluetooth.cpp b/audio/aidl/default/Bluetooth.cpp index 38e0c21ea8..bd9a86431d 100644 --- a/audio/aidl/default/Bluetooth.cpp +++ b/audio/aidl/default/Bluetooth.cpp @@ -19,6 +19,7 @@ #include "core-impl/Bluetooth.h" +using aidl::android::hardware::audio::core::VendorParameter; using aidl::android::media::audio::common::Boolean; using aidl::android::media::audio::common::Float; using aidl::android::media::audio::common::Int; @@ -79,4 +80,29 @@ ndk::ScopedAStatus Bluetooth::setHfpConfig(const HfpConfig& in_config, HfpConfig return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus BluetoothA2dp::isEnabled(bool* _aidl_return) { + *_aidl_return = mEnabled; + LOG(DEBUG) << __func__ << ": returning " << *_aidl_return; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothA2dp::setEnabled(bool in_enabled) { + mEnabled = in_enabled; + LOG(DEBUG) << __func__ << ": " << mEnabled; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothA2dp::supportsOffloadReconfiguration(bool* _aidl_return) { + *_aidl_return = true; + LOG(DEBUG) << __func__ << ": returning " << *_aidl_return; + return ndk::ScopedAStatus::ok(); +} + +ndk::ScopedAStatus BluetoothA2dp::reconfigureOffload( + const std::vector<::aidl::android::hardware::audio::core::VendorParameter>& in_parameters + __unused) { + LOG(DEBUG) << __func__ << ": " << ::android::internal::ToString(in_parameters); + 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 5440b8d640..a8f03af4fb 100644 --- a/audio/aidl/default/Module.cpp +++ b/audio/aidl/default/Module.cpp @@ -370,29 +370,32 @@ ndk::ScopedAStatus Module::setModuleDebug( } ndk::ScopedAStatus Module::getTelephony(std::shared_ptr* _aidl_return) { - if (mTelephony == nullptr) { + if (!mTelephony) { mTelephony = ndk::SharedRefBase::make(); - mTelephonyBinder = mTelephony->asBinder(); - AIBinder_setMinSchedulerPolicy(mTelephonyBinder.get(), SCHED_NORMAL, - ANDROID_PRIORITY_AUDIO); } - *_aidl_return = mTelephony; + *_aidl_return = mTelephony.getPtr(); LOG(DEBUG) << __func__ << ": returning instance of ITelephony: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } ndk::ScopedAStatus Module::getBluetooth(std::shared_ptr* _aidl_return) { - if (mBluetooth == nullptr) { + if (!mBluetooth) { mBluetooth = ndk::SharedRefBase::make(); - mBluetoothBinder = mBluetooth->asBinder(); - AIBinder_setMinSchedulerPolicy(mBluetoothBinder.get(), SCHED_NORMAL, - ANDROID_PRIORITY_AUDIO); } - *_aidl_return = mBluetooth; + *_aidl_return = mBluetooth.getPtr(); LOG(DEBUG) << __func__ << ": returning instance of IBluetooth: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } +ndk::ScopedAStatus Module::getBluetoothA2dp(std::shared_ptr* _aidl_return) { + if (!mBluetoothA2dp) { + mBluetoothA2dp = ndk::SharedRefBase::make(); + } + *_aidl_return = mBluetoothA2dp.getPtr(); + LOG(DEBUG) << __func__ << ": returning instance of IBluetoothA2dp: " << _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; @@ -1039,13 +1042,10 @@ ndk::ScopedAStatus Module::updateScreenState(bool in_isTurnedOn) { } ndk::ScopedAStatus Module::getSoundDose(std::shared_ptr* _aidl_return) { - if (mSoundDose == nullptr) { + if (!mSoundDose) { mSoundDose = ndk::SharedRefBase::make(); - mSoundDoseBinder = mSoundDose->asBinder(); - AIBinder_setMinSchedulerPolicy(mSoundDoseBinder.get(), SCHED_NORMAL, - ANDROID_PRIORITY_AUDIO); } - *_aidl_return = mSoundDose; + *_aidl_return = mSoundDose.getPtr(); LOG(DEBUG) << __func__ << ": returning instance of ISoundDose: " << _aidl_return->get(); return ndk::ScopedAStatus::ok(); } diff --git a/audio/aidl/default/include/core-impl/Bluetooth.h b/audio/aidl/default/include/core-impl/Bluetooth.h index f2e762dd40..e2f48bae2b 100644 --- a/audio/aidl/default/include/core-impl/Bluetooth.h +++ b/audio/aidl/default/include/core-impl/Bluetooth.h @@ -17,6 +17,7 @@ #pragma once #include +#include namespace aidl::android::hardware::audio::core { @@ -32,4 +33,19 @@ class Bluetooth : public BnBluetooth { HfpConfig mHfpConfig; }; +class BluetoothA2dp : public BnBluetoothA2dp { + public: + BluetoothA2dp() = default; + + 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( + const std::vector<::aidl::android::hardware::audio::core::VendorParameter>& + in_parameters) override; + + bool mEnabled = false; +}; + } // 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 8365b3438f..6b254dbbe4 100644 --- a/audio/aidl/default/include/core-impl/Module.h +++ b/audio/aidl/default/include/core-impl/Module.h @@ -46,11 +46,32 @@ class Module : public BnModule { bool forceTransientBurst = false; bool forceSynchronousDrain = false; }; + // Helper used for interfaces that require a persistent instance. We hold them via a strong + // pointer. The binder token is retained for a call to 'setMinSchedulerPolicy'. + template + struct ChildInterface : private std::pair, ndk::SpAIBinder> { + ChildInterface() {} + ChildInterface& operator=(const std::shared_ptr& c) { + return operator=(std::shared_ptr(c)); + } + ChildInterface& operator=(std::shared_ptr&& c) { + this->first = std::move(c); + this->second = this->first->asBinder(); + AIBinder_setMinSchedulerPolicy(this->second.get(), SCHED_NORMAL, + ANDROID_PRIORITY_AUDIO); + return *this; + } + explicit operator bool() const { return !!this->first; } + C& operator*() const { return *(this->first); } + C* operator->() const { return this->first; } + std::shared_ptr getPtr() const { return this->first; } + }; 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 getBluetoothA2dp(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; @@ -151,12 +172,9 @@ class Module : public BnModule { std::unique_ptr mConfig; ModuleDebug mDebug; VendorDebug mVendorDebug; - // 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; + ChildInterface mTelephony; + ChildInterface mBluetooth; + ChildInterface mBluetoothA2dp; // ids of ports created at runtime via 'connectExternalDevice'. std::set mConnectedDevicePorts; Streams mStreams; @@ -166,8 +184,7 @@ class Module : public BnModule { bool mMasterMute = false; float mMasterVolume = 1.0f; bool mMicMute = false; - std::shared_ptr mSoundDose; - ndk::SpAIBinder mSoundDoseBinder; + ChildInterface mSoundDose; std::optional mIsMmapSupported; protected: diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp index cd7ab0e91e..1e0c900577 100644 --- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp +++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp @@ -61,6 +61,7 @@ using aidl::android::hardware::audio::common::SourceMetadata; 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::IBluetoothA2dp; using aidl::android::hardware::audio::core::IModule; using aidl::android::hardware::audio::core::IStreamCommon; using aidl::android::hardware::audio::core::IStreamIn; @@ -2055,6 +2056,59 @@ TEST_P(AudioCoreBluetooth, HfpConfigInvalid) { &hfpConfig)); } +class AudioCoreBluetoothA2dp : public AudioCoreModuleBase, + public testing::TestWithParam { + public: + void SetUp() override { + ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam())); + ASSERT_IS_OK(module->getBluetoothA2dp(&bluetooth)); + } + + void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); } + + std::shared_ptr bluetooth; +}; + +TEST_P(AudioCoreBluetoothA2dp, SameInstance) { + if (bluetooth == nullptr) { + GTEST_SKIP() << "BluetoothA2dp is not supported"; + } + std::shared_ptr bluetooth2; + EXPECT_IS_OK(module->getBluetoothA2dp(&bluetooth2)); + ASSERT_NE(nullptr, bluetooth2.get()); + EXPECT_EQ(bluetooth->asBinder(), bluetooth2->asBinder()) + << "getBluetoothA2dp must return the same interface instance across invocations"; +} + +TEST_P(AudioCoreBluetoothA2dp, Enabled) { + if (bluetooth == nullptr) { + GTEST_SKIP() << "BluetoothA2dp is not supported"; + } + // Since enabling A2DP may require having an actual device connection, + // limit testing to setting back the current value. + bool enabled; + ASSERT_IS_OK(bluetooth->isEnabled(&enabled)); + EXPECT_IS_OK(bluetooth->setEnabled(enabled)) + << "setEnabled without actual state change must not fail"; +} + +TEST_P(AudioCoreBluetoothA2dp, OffloadReconfiguration) { + if (bluetooth == nullptr) { + GTEST_SKIP() << "BluetoothA2dp is not supported"; + } + bool isSupported; + ASSERT_IS_OK(bluetooth->supportsOffloadReconfiguration(&isSupported)); + bool isSupported2; + ASSERT_IS_OK(bluetooth->supportsOffloadReconfiguration(&isSupported2)); + EXPECT_EQ(isSupported, isSupported2); + if (isSupported) { + static const auto kStatuses = {EX_NONE, EX_ILLEGAL_STATE}; + EXPECT_STATUS(kStatuses, bluetooth->reconfigureOffload({})); + } else { + EXPECT_STATUS(EX_UNSUPPORTED_OPERATION, bluetooth->reconfigureOffload({})); + } +} + class AudioCoreTelephony : public AudioCoreModuleBase, public testing::TestWithParam { public: void SetUp() override { @@ -3462,6 +3516,10 @@ INSTANTIATE_TEST_SUITE_P(AudioCoreBluetoothTest, AudioCoreBluetooth, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString); GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreBluetooth); +INSTANTIATE_TEST_SUITE_P(AudioCoreBluetoothA2dpTest, AudioCoreBluetoothA2dp, + testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), + android::PrintInstanceNameToString); +GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreBluetoothA2dp); INSTANTIATE_TEST_SUITE_P(AudioCoreTelephonyTest, AudioCoreTelephony, testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)), android::PrintInstanceNameToString);