audio: Add IBluetoothLe

Similar to IBluetooth and IBluetoothA2dp interfaces,
IBluetoothLe controls the LE profile. This interface
replaces the following string parameter:

"LeAudioSuspended"

Bug: 270731693
Test: atest VtsHalAudioCoreTargetTest
Change-Id: I9df28053cd270fc2d446ba9a8ada4dd6ab761c7b
diff --git a/audio/aidl/Android.bp b/audio/aidl/Android.bp
index bb48ecd..515787e 100644
--- a/audio/aidl/Android.bp
+++ b/audio/aidl/Android.bp
@@ -114,6 +114,7 @@
         "android/hardware/audio/core/AudioRoute.aidl",
         "android/hardware/audio/core/IBluetooth.aidl",
         "android/hardware/audio/core/IBluetoothA2dp.aidl",
+        "android/hardware/audio/core/IBluetoothLe.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/IBluetoothLe.aidl b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetoothLe.aidl
new file mode 100644
index 0000000..f29e1fd
--- /dev/null
+++ b/audio/aidl/aidl_api/android.hardware.audio.core/current/android/hardware/audio/core/IBluetoothLe.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.audio.core;
+@VintfStability
+interface IBluetoothLe {
+  boolean isEnabled();
+  void setEnabled(boolean enabled);
+}
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 f18d6a5..e14e9c0 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
@@ -38,6 +38,7 @@
   @nullable android.hardware.audio.core.ITelephony getTelephony();
   @nullable android.hardware.audio.core.IBluetooth getBluetooth();
   @nullable android.hardware.audio.core.IBluetoothA2dp getBluetoothA2dp();
+  @nullable android.hardware.audio.core.IBluetoothLe getBluetoothLe();
   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
index 21ac8e2..7fa2e24 100644
--- a/audio/aidl/android/hardware/audio/core/IBluetooth.aidl
+++ b/audio/aidl/android/hardware/audio/core/IBluetooth.aidl
@@ -25,6 +25,9 @@
  * 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.
+ *
+ * Each of IBluetooth* interfaces is independent of each other. The HAL module
+ * can provide any combination of them.
  */
 @VintfStability
 interface IBluetooth {
diff --git a/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl b/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl
index dc4b8d0..c4dd738 100644
--- a/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl
+++ b/audio/aidl/android/hardware/audio/core/IBluetoothA2dp.aidl
@@ -24,8 +24,8 @@
  * 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.
+ * Each of IBluetooth* interfaces is independent of each other. The HAL module
+ * can provide any combination of them.
  */
 @VintfStability
 interface IBluetoothA2dp {
diff --git a/audio/aidl/android/hardware/audio/core/IBluetoothLe.aidl b/audio/aidl/android/hardware/audio/core/IBluetoothLe.aidl
new file mode 100644
index 0000000..272f862
--- /dev/null
+++ b/audio/aidl/android/hardware/audio/core/IBluetoothLe.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.audio.core;
+
+/**
+ * An instance of IBluetoothLe manages settings for the LE (Low Energy)
+ * profiles. This interface is optional to implement by the vendor. It needs to
+ * be provided only if the device actually supports BT LE.
+ *
+ * Each of IBluetooth* interfaces is independent of each other. The HAL module
+ * can provide any combination of them.
+ */
+@VintfStability
+interface IBluetoothLe {
+    /**
+     * Whether BT LE is enabled.
+     *
+     * Returns the current state of LE support. The client might need to
+     * disable (suspend) LE when another profile (for example, SCO) is
+     * activated.
+     *
+     * @return Whether BT LE is enabled.
+     */
+    boolean isEnabled();
+
+    /**
+     * Enable or disable LE.
+     *
+     * Sets the current state of LE support. The client might need to
+     * disable (suspend) LE when another Bluetooth profile (for example, SCO) is
+     * activated.
+     *
+     * @param enabled Whether BT LE must be enabled or suspended.
+     * @throws EX_ILLEGAL_STATE If there was an error performing the operation.
+     */
+    void setEnabled(boolean enabled);
+}
diff --git a/audio/aidl/android/hardware/audio/core/IModule.aidl b/audio/aidl/android/hardware/audio/core/IModule.aidl
index 080d826..7830501 100644
--- a/audio/aidl/android/hardware/audio/core/IModule.aidl
+++ b/audio/aidl/android/hardware/audio/core/IModule.aidl
@@ -22,6 +22,7 @@
 import android.hardware.audio.core.AudioRoute;
 import android.hardware.audio.core.IBluetooth;
 import android.hardware.audio.core.IBluetoothA2dp;
+import android.hardware.audio.core.IBluetoothLe;
 import android.hardware.audio.core.IStreamCallback;
 import android.hardware.audio.core.IStreamIn;
 import android.hardware.audio.core.IStreamOut;
@@ -118,6 +119,20 @@
     @nullable IBluetoothA2dp getBluetoothA2dp();
 
     /**
+     * Retrieve the interface to control Bluetooth LE.
+     *
+     * If the HAL module supports LE Profile functionality for Bluetooth, it
+     * must return an instance of the IBluetoothLe interface. The same
+     * instance must be returned during the lifetime of the HAL module. If the
+     * HAL module does not support BT LE, a null must be returned, without
+     * throwing any errors.
+     *
+     * @return An instance of the IBluetoothLe interface implementation.
+     * @throws EX_ILLEGAL_STATE If there was an error creating an instance.
+     */
+    @nullable IBluetoothLe getBluetoothLe();
+
+    /**
      * Set a device port of an external device into connected state.
      *
      * This method is used to inform the HAL module that an external device has
@@ -842,7 +857,7 @@
     AudioMMapPolicyInfo[] getMmapPolicyInfos(AudioMMapPolicyType mmapPolicyType);
 
     /**
-     * Indicates if this module supports variable latency control for instance
+     * Indicates if this module supports variable latency control, for instance,
      * over Bluetooth A2DP or LE Audio links.
      *
      * If supported, all instances of IStreamOut interface returned by this module must
diff --git a/audio/aidl/default/Bluetooth.cpp b/audio/aidl/default/Bluetooth.cpp
index bd9a864..8115e7b 100644
--- a/audio/aidl/default/Bluetooth.cpp
+++ b/audio/aidl/default/Bluetooth.cpp
@@ -105,4 +105,16 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus BluetoothLe::isEnabled(bool* _aidl_return) {
+    *_aidl_return = mEnabled;
+    LOG(DEBUG) << __func__ << ": returning " << *_aidl_return;
+    return ndk::ScopedAStatus::ok();
+}
+
+ndk::ScopedAStatus BluetoothLe::setEnabled(bool in_enabled) {
+    mEnabled = in_enabled;
+    LOG(DEBUG) << __func__ << ": " << mEnabled;
+    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 c4f3844..c95c199 100644
--- a/audio/aidl/default/Module.cpp
+++ b/audio/aidl/default/Module.cpp
@@ -396,6 +396,15 @@
     return ndk::ScopedAStatus::ok();
 }
 
+ndk::ScopedAStatus Module::getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) {
+    if (!mBluetoothLe) {
+        mBluetoothLe = ndk::SharedRefBase::make<BluetoothLe>();
+    }
+    *_aidl_return = mBluetoothLe.getPtr();
+    LOG(DEBUG) << __func__ << ": returning instance of IBluetoothLe: " << _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
index e2f48ba..1cd0355 100644
--- a/audio/aidl/default/include/core-impl/Bluetooth.h
+++ b/audio/aidl/default/include/core-impl/Bluetooth.h
@@ -18,6 +18,7 @@
 
 #include <aidl/android/hardware/audio/core/BnBluetooth.h>
 #include <aidl/android/hardware/audio/core/BnBluetoothA2dp.h>
+#include <aidl/android/hardware/audio/core/BnBluetoothLe.h>
 
 namespace aidl::android::hardware::audio::core {
 
@@ -48,4 +49,15 @@
     bool mEnabled = false;
 };
 
+class BluetoothLe : public BnBluetoothLe {
+  public:
+    BluetoothLe() = default;
+
+  private:
+    ndk::ScopedAStatus isEnabled(bool* _aidl_return) override;
+    ndk::ScopedAStatus setEnabled(bool in_enabled) 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 0544646..2cbda7d 100644
--- a/audio/aidl/default/include/core-impl/Module.h
+++ b/audio/aidl/default/include/core-impl/Module.h
@@ -72,6 +72,7 @@
     ndk::ScopedAStatus getTelephony(std::shared_ptr<ITelephony>* _aidl_return) override;
     ndk::ScopedAStatus getBluetooth(std::shared_ptr<IBluetooth>* _aidl_return) override;
     ndk::ScopedAStatus getBluetoothA2dp(std::shared_ptr<IBluetoothA2dp>* _aidl_return) override;
+    ndk::ScopedAStatus getBluetoothLe(std::shared_ptr<IBluetoothLe>* _aidl_return) override;
     ndk::ScopedAStatus connectExternalDevice(
             const ::aidl::android::media::audio::common::AudioPort& in_templateIdAndAdditionalData,
             ::aidl::android::media::audio::common::AudioPort* _aidl_return) override;
@@ -175,6 +176,7 @@
     ChildInterface<ITelephony> mTelephony;
     ChildInterface<IBluetooth> mBluetooth;
     ChildInterface<IBluetoothA2dp> mBluetoothA2dp;
+    ChildInterface<IBluetoothLe> mBluetoothLe;
     // ids of ports created at runtime via 'connectExternalDevice'.
     std::set<int32_t> mConnectedDevicePorts;
     Streams mStreams;
diff --git a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
index f06c0f1..650a543 100644
--- a/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
+++ b/audio/aidl/vts/VtsHalAudioCoreModuleTargetTest.cpp
@@ -62,6 +62,7 @@
 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::IBluetoothLe;
 using aidl::android::hardware::audio::core::IModule;
 using aidl::android::hardware::audio::core::IStreamCommon;
 using aidl::android::hardware::audio::core::IStreamIn;
@@ -2111,6 +2112,42 @@
     }
 }
 
+class AudioCoreBluetoothLe : public AudioCoreModuleBase,
+                             public testing::TestWithParam<std::string> {
+  public:
+    void SetUp() override {
+        ASSERT_NO_FATAL_FAILURE(SetUpImpl(GetParam()));
+        ASSERT_IS_OK(module->getBluetoothLe(&bluetooth));
+    }
+
+    void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownImpl()); }
+
+    std::shared_ptr<IBluetoothLe> bluetooth;
+};
+
+TEST_P(AudioCoreBluetoothLe, SameInstance) {
+    if (bluetooth == nullptr) {
+        GTEST_SKIP() << "BluetoothLe is not supported";
+    }
+    std::shared_ptr<IBluetoothLe> bluetooth2;
+    EXPECT_IS_OK(module->getBluetoothLe(&bluetooth2));
+    ASSERT_NE(nullptr, bluetooth2.get());
+    EXPECT_EQ(bluetooth->asBinder(), bluetooth2->asBinder())
+            << "getBluetoothLe must return the same interface instance across invocations";
+}
+
+TEST_P(AudioCoreBluetoothLe, Enabled) {
+    if (bluetooth == nullptr) {
+        GTEST_SKIP() << "BluetoothLe is not supported";
+    }
+    // Since enabling LE 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";
+}
+
 class AudioCoreTelephony : public AudioCoreModuleBase, public testing::TestWithParam<std::string> {
   public:
     void SetUp() override {
@@ -3543,6 +3580,10 @@
                          testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
                          android::PrintInstanceNameToString);
 GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreBluetoothA2dp);
+INSTANTIATE_TEST_SUITE_P(AudioCoreBluetoothLeTest, AudioCoreBluetoothLe,
+                         testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
+                         android::PrintInstanceNameToString);
+GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(AudioCoreBluetoothLe);
 INSTANTIATE_TEST_SUITE_P(AudioCoreTelephonyTest, AudioCoreTelephony,
                          testing::ValuesIn(android::getAidlHalInstanceNames(IModule::descriptor)),
                          android::PrintInstanceNameToString);