summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-08 02:08:28 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2022-03-08 02:08:28 +0000
commit22389fee67a014fed221aadadcfbc1dc9cd7335f (patch)
tree24b5864c0fccfd1ae7549e3616dbc5ee1aa5bafb
parent4739b8feb4c2a5becbb584b1f62f0e5321079d17 (diff)
parentc4c4f9227fb198e838b5b7de5d1a048f2d3a0c52 (diff)
Snap for 8270536 from c4c4f9227fb198e838b5b7de5d1a048f2d3a0c52 to tm-release
Change-Id: I11136ce6eeaed10d012ef29fb7c081cbc5c1ced2
-rw-r--r--android/app/jni/com_android_bluetooth_vc.cpp250
-rw-r--r--android/app/res/values/config.xml2
-rw-r--r--android/app/src/com/android/bluetooth/le_audio/LeAudioService.java76
-rw-r--r--android/app/src/com/android/bluetooth/pan/PanService.java5
-rw-r--r--android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java146
-rw-r--r--android/app/src/com/android/bluetooth/vc/VolumeControlService.java308
-rw-r--r--android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java31
-rw-r--r--android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java4
-rw-r--r--framework/api/system-current.txt4
-rw-r--r--framework/java/android/bluetooth/BluetoothAdapter.java36
-rw-r--r--framework/java/android/bluetooth/BluetoothDevice.java16
-rw-r--r--framework/java/android/bluetooth/BluetoothHeadsetClient.java23
-rw-r--r--framework/java/android/bluetooth/BluetoothLeAudio.java29
-rw-r--r--framework/java/android/bluetooth/BluetoothLeAudioCodecStatus.java18
-rw-r--r--service/java/com/android/server/bluetooth/BluetoothManagerService.java40
-rw-r--r--system/audio_bluetooth_hw/stream_apis.cc42
-rw-r--r--system/binder/Android.bp1
-rw-r--r--system/binder/android/bluetooth/BluetoothLeAudioCodecConfig.aidl19
-rw-r--r--system/binder/android/bluetooth/IBluetoothLeAudio.aidl5
-rw-r--r--system/binder/android/bluetooth/IBluetoothVolumeControl.aidl8
-rw-r--r--system/binder/android/bluetooth/IBluetoothVolumeControlCallback.aidl30
-rw-r--r--system/bta/include/bta_vc_api.h18
-rw-r--r--system/bta/le_audio/client.cc215
-rw-r--r--system/bta/vc/device.cc181
-rw-r--r--system/bta/vc/devices.h43
-rw-r--r--system/bta/vc/devices_test.cc230
-rw-r--r--system/bta/vc/types.h108
-rw-r--r--system/bta/vc/vc.cc224
-rw-r--r--system/bta/vc/vc_test.cc300
-rw-r--r--system/btif/src/bluetooth.cc2
-rw-r--r--system/btif/src/btif_vc.cc126
-rw-r--r--system/embdrv/lc3/Android.bp3
-rw-r--r--system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp (renamed from system/embdrv/lc3/fuzzer/liblc3encoder_fuzzer.cpp)53
-rw-r--r--system/gd/packet/parser/gen_rust.cc4
-rw-r--r--system/gd/packet/parser/packet_def.cc33
-rw-r--r--system/gd/rust/linux/dbus_projection/dbus_macros/src/lib.rs27
-rw-r--r--system/include/hardware/bt_vc.h30
-rw-r--r--system/stack/btm/btm_sco.cc3
-rw-r--r--system/test/mock/mock_bta_vc_device.cc5
-rw-r--r--tools/OWNERS13
40 files changed, 2552 insertions, 159 deletions
diff --git a/android/app/jni/com_android_bluetooth_vc.cpp b/android/app/jni/com_android_bluetooth_vc.cpp
index 5bb744db7a..22baaeaa7f 100644
--- a/android/app/jni/com_android_bluetooth_vc.cpp
+++ b/android/app/jni/com_android_bluetooth_vc.cpp
@@ -34,6 +34,10 @@ namespace android {
static jmethodID method_onConnectionStateChanged;
static jmethodID method_onVolumeStateChanged;
static jmethodID method_onGroupVolumeStateChanged;
+static jmethodID method_onDeviceAvailable;
+static jmethodID method_onExtAudioOutVolumeOffsetChanged;
+static jmethodID method_onExtAudioOutLocationChanged;
+static jmethodID method_onExtAudioOutDescriptionChanged;
static VolumeControlInterface* sVolumeControlInterface = nullptr;
static std::shared_timed_mutex interface_mutex;
@@ -98,6 +102,100 @@ class VolumeControlCallbacksImpl : public VolumeControlCallbacks {
method_onGroupVolumeStateChanged, (jint)volume,
(jboolean)mute, group_id, (jboolean)isAutonomous);
}
+
+ void OnDeviceAvailable(const RawAddress& bd_addr,
+ uint8_t num_offsets) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for onDeviceAvailable";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onDeviceAvailable,
+ (jint)num_offsets, addr.get());
+ }
+
+ void OnExtAudioOutVolumeOffsetChanged(const RawAddress& bd_addr,
+ uint8_t ext_output_id,
+ int16_t offset) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for "
+ "OnExtAudioOutVolumeOffsetChanged";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(mCallbacksObj,
+ method_onExtAudioOutVolumeOffsetChanged,
+ (jint)ext_output_id, (jint)offset, addr.get());
+ }
+
+ void OnExtAudioOutLocationChanged(const RawAddress& bd_addr,
+ uint8_t ext_output_id,
+ uint32_t location) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for "
+ "OnExtAudioOutLocationChanged";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ sCallbackEnv->CallVoidMethod(
+ mCallbacksObj, method_onExtAudioOutLocationChanged, (jint)ext_output_id,
+ (jint)location, addr.get());
+ }
+
+ void OnExtAudioOutDescriptionChanged(const RawAddress& bd_addr,
+ uint8_t ext_output_id,
+ std::string descr) override {
+ LOG(INFO) << __func__;
+
+ std::shared_lock<std::shared_timed_mutex> lock(callbacks_mutex);
+ CallbackEnv sCallbackEnv(__func__);
+ if (!sCallbackEnv.valid() || mCallbacksObj == nullptr) return;
+
+ ScopedLocalRef<jbyteArray> addr(
+ sCallbackEnv.get(), sCallbackEnv->NewByteArray(sizeof(RawAddress)));
+ if (!addr.get()) {
+ LOG(ERROR) << "Failed to new jbyteArray bd addr for "
+ "OnExtAudioOutDescriptionChanged";
+ return;
+ }
+
+ sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress),
+ (jbyte*)&bd_addr);
+ jstring description = sCallbackEnv->NewStringUTF(descr.c_str());
+ sCallbackEnv->CallVoidMethod(mCallbacksObj,
+ method_onExtAudioOutDescriptionChanged,
+ (jint)ext_output_id, description, addr.get());
+ }
};
static VolumeControlCallbacksImpl sVolumeControlCallbacks;
@@ -112,6 +210,18 @@ static void classInitNative(JNIEnv* env, jclass clazz) {
method_onGroupVolumeStateChanged =
env->GetMethodID(clazz, "onGroupVolumeStateChanged", "(IZIZ)V");
+ method_onDeviceAvailable =
+ env->GetMethodID(clazz, "onDeviceAvailable", "(I[B)V");
+
+ method_onExtAudioOutVolumeOffsetChanged =
+ env->GetMethodID(clazz, "onExtAudioOutVolumeOffsetChanged", "(II[B)V");
+
+ method_onExtAudioOutLocationChanged =
+ env->GetMethodID(clazz, "onExtAudioOutLocationChanged", "(II[B)V");
+
+ method_onExtAudioOutDescriptionChanged = env->GetMethodID(
+ clazz, "onExtAudioOutDescriptionChanged", "(ILjava/lang/String;[B)V");
+
LOG(INFO) << __func__ << ": succeeds";
}
@@ -249,6 +359,134 @@ static void setVolumeGroupNative(JNIEnv* env, jobject object, jint group_id,
sVolumeControlInterface->SetVolume(group_id, volume);
}
+/* Native methods for exterbak audio outputs */
+static jboolean getExtAudioOutVolumeOffsetNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->GetExtAudioOutVolumeOffset(*tmpraw, ext_output_id);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean setExtAudioOutVolumeOffsetNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id,
+ jint offset) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->SetExtAudioOutVolumeOffset(*tmpraw, ext_output_id,
+ offset);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean getExtAudioOutLocationNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->GetExtAudioOutLocation(*tmpraw, ext_output_id);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean setExtAudioOutLocationNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id,
+ jint location) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->SetExtAudioOutLocation(*tmpraw, ext_output_id,
+ location);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean getExtAudioOutDescriptionNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->GetExtAudioOutDescription(*tmpraw, ext_output_id);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
+static jboolean setExtAudioOutDescriptionNative(JNIEnv* env, jobject object,
+ jbyteArray address,
+ jint ext_output_id,
+ jstring descr) {
+ LOG(INFO) << __func__;
+ std::shared_lock<std::shared_timed_mutex> lock(interface_mutex);
+ if (!sVolumeControlInterface) return JNI_FALSE;
+
+ jbyte* addr = env->GetByteArrayElements(address, nullptr);
+ if (!addr) {
+ jniThrowIOException(env, EINVAL);
+ return JNI_FALSE;
+ }
+
+ std::string description;
+ if (descr != nullptr) {
+ const char* value = env->GetStringUTFChars(descr, nullptr);
+ description = std::string(value);
+ env->ReleaseStringUTFChars(descr, value);
+ }
+
+ RawAddress* tmpraw = (RawAddress*)addr;
+ sVolumeControlInterface->SetExtAudioOutDescription(*tmpraw, ext_output_id,
+ description);
+ env->ReleaseByteArrayElements(address, addr, 0);
+ return JNI_TRUE;
+}
+
static JNINativeMethod sMethods[] = {
{"classInitNative", "()V", (void*)classInitNative},
{"initNative", "()V", (void*)initNative},
@@ -258,6 +496,18 @@ static JNINativeMethod sMethods[] = {
(void*)disconnectVolumeControlNative},
{"setVolumeNative", "([BI)V", (void*)setVolumeNative},
{"setVolumeGroupNative", "(II)V", (void*)setVolumeGroupNative},
+ {"getExtAudioOutVolumeOffsetNative", "([BI)Z",
+ (void*)getExtAudioOutVolumeOffsetNative},
+ {"setExtAudioOutVolumeOffsetNative", "([BII)Z",
+ (void*)setExtAudioOutVolumeOffsetNative},
+ {"getExtAudioOutLocationNative", "([BI)Z",
+ (void*)getExtAudioOutLocationNative},
+ {"setExtAudioOutLocationNative", "([BII)Z",
+ (void*)setExtAudioOutLocationNative},
+ {"getExtAudioOutDescriptionNative", "([BI)Z",
+ (void*)getExtAudioOutDescriptionNative},
+ {"setExtAudioOutDescriptionNative", "([BILjava/lang/String;)Z",
+ (void*)setExtAudioOutDescriptionNative},
};
int register_com_android_bluetooth_vc(JNIEnv* env) {
diff --git a/android/app/res/values/config.xml b/android/app/res/values/config.xml
index dddf9ad3e5..ea18d15133 100644
--- a/android/app/res/values/config.xml
+++ b/android/app/res/values/config.xml
@@ -39,7 +39,7 @@
<bool name="profile_supported_csip_set_coordinator">true</bool>
<bool name="profile_supported_le_call_control">true</bool>
<bool name="profile_supported_hap_client">true</bool>
- <bool name="profile_supported_bass_client">true</bool>
+ <bool name="profile_supported_bass_client">false</bool>
<!-- If true, we will require location to be enabled on the device to
fire Bluetooth LE scan result callbacks in addition to having one
diff --git a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
index 121dc49320..7df5de50bc 100644
--- a/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
+++ b/android/app/src/com/android/bluetooth/le_audio/LeAudioService.java
@@ -26,6 +26,8 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressLint;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothLeAudio;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
+import android.bluetooth.BluetoothLeAudioCodecStatus;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.BluetoothLeBroadcastMetadata;
import android.bluetooth.BluetoothProfile;
@@ -1695,6 +1697,52 @@ public class LeAudioService extends ProfileService {
}
/**
+ * Gets the current codec status (configuration and capability).
+ *
+ * @param device the remote Bluetooth device.
+ * @return the current codec status
+ * @hide
+ */
+ public BluetoothLeAudioCodecStatus getCodecStatus(BluetoothDevice device) {
+ if (DBG) {
+ Log.d(TAG, "getCodecStatus(" + device + ")");
+ }
+
+ return null;
+ }
+
+ /**
+ * Sets the codec configuration preference.
+ *
+ * @param device the remote Bluetooth device.
+ * @param codecConfig the codec configuration preference
+ * @hide
+ */
+ public void setCodecConfigPreference(BluetoothDevice device,
+ BluetoothLeAudioCodecConfig codecConfig) {
+ if (DBG) {
+ Log.d(TAG, "setCodecConfigPreference(" + device + "): "
+ + Objects.toString(codecConfig));
+ }
+ if (device == null) {
+ Log.e(TAG, "setCodecConfigPreference: Invalid device");
+ return;
+ }
+ if (codecConfig == null) {
+ Log.e(TAG, "setCodecConfigPreference: Codec config can't be null");
+ return;
+ }
+ BluetoothLeAudioCodecStatus codecStatus = getCodecStatus(device);
+ if (codecStatus == null) {
+ Log.e(TAG, "setCodecConfigPreference: Codec status is null");
+ return;
+ }
+
+ // TODO: pass the information to bt stack
+ }
+
+
+ /**
* Binder object: must be a static class or memory leak may occur
*/
@VisibleForTesting
@@ -2057,6 +2105,34 @@ public class LeAudioService extends ProfileService {
receiver.propagateException(e);
}
}
+
+ @Override
+ public void getCodecStatus(BluetoothDevice device,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ try {
+ LeAudioService service = getService(source);
+ BluetoothLeAudioCodecStatus codecStatus = null;
+ if (service != null) {
+ enforceBluetoothPrivilegedPermission(service);
+ codecStatus = service.getCodecStatus(device);
+ }
+ receiver.send(codecStatus);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+
+ @Override
+ public void setCodecConfigPreference(BluetoothDevice device,
+ BluetoothLeAudioCodecConfig codecConfig, AttributionSource source) {
+ LeAudioService service = getService(source);
+ if (service == null) {
+ return;
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+ service.setCodecConfigPreference(device, codecConfig);
+ }
}
@Override
diff --git a/android/app/src/com/android/bluetooth/pan/PanService.java b/android/app/src/com/android/bluetooth/pan/PanService.java
index cb1e845c4b..3e3cb38003 100644
--- a/android/app/src/com/android/bluetooth/pan/PanService.java
+++ b/android/app/src/com/android/bluetooth/pan/PanService.java
@@ -169,7 +169,10 @@ public class PanService extends ProfileService {
@Override
protected boolean stop() {
mAdapterService = null;
- mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback);
+ if (mTetheringManager != null) {
+ mTetheringManager.unregisterTetheringEventCallback(mTetheringCallback);
+ mTetheringManager = null;
+ }
mHandler.removeCallbacksAndMessages(null);
return true;
}
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
index 0d2b0ec0af..421405c6ba 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java
@@ -117,6 +117,84 @@ public class VolumeControlNativeInterface {
setVolumeGroupNative(groupId, volume);
}
+ /**
+ * Gets external audio output volume offset from a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean getExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId) {
+ return getExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId);
+ }
+
+ /**
+ * Sets external audio output volume offset to a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @param offset requested offset
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean setExtAudioOutVolumeOffset(BluetoothDevice device, int externalOutputId,
+ int offset) {
+ return setExtAudioOutVolumeOffsetNative(getByteAddress(device), externalOutputId, offset);
+ }
+
+ /**
+ * Gets external audio output location from a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean getExtAudioOutLocation(BluetoothDevice device, int externalOutputId) {
+ return getExtAudioOutLocationNative(getByteAddress(device), externalOutputId);
+ }
+
+ /**
+ * Sets external audio volume offset to a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @param location requested location
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean setExtAudioOutLocation(BluetoothDevice device, int externalOutputId,
+ int location) {
+ return setExtAudioOutLocationNative(getByteAddress(device), externalOutputId, location);
+ }
+
+ /**
+ * Gets external audio output description from a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean getExtAudioOutDescription(BluetoothDevice device, int externalOutputId) {
+ return getExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId);
+ }
+
+ /**
+ * Sets external audio volume description to a remote device.
+ *
+ * @param device the remote device
+ * @param externalOutputId external audio output id
+ * @param descr requested description
+ * @return true on success, otherwise false.
+ */
+ @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
+ public boolean setExtAudioOutDescription(BluetoothDevice device, int externalOutputId,
+ String descr) {
+ return setExtAudioOutDescriptionNative(getByteAddress(device), externalOutputId, descr);
+ }
+
private BluetoothDevice getDevice(byte[] address) {
return mAdapter.getRemoteDevice(address);
}
@@ -188,6 +266,65 @@ public class VolumeControlNativeInterface {
sendMessageToService(event);
}
+ private void onDeviceAvailable(int numOfExternalOutputs,
+ byte[] address) {
+ VolumeControlStackEvent event =
+ new VolumeControlStackEvent(
+ VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE);
+ event.device = getDevice(address);
+ event.valueInt1 = numOfExternalOutputs;
+
+ if (DBG) {
+ Log.d(TAG, "onDeviceAvailable: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onExtAudioOutVolumeOffsetChanged(int externalOutputId, int offset,
+ byte[] address) {
+ VolumeControlStackEvent event =
+ new VolumeControlStackEvent(
+ VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED);
+ event.device = getDevice(address);
+ event.valueInt1 = externalOutputId;
+ event.valueInt2 = offset;
+
+ if (DBG) {
+ Log.d(TAG, "onExtAudioOutVolumeOffsetChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onExtAudioOutLocationChanged(int externalOutputId, int location,
+ byte[] address) {
+ VolumeControlStackEvent event =
+ new VolumeControlStackEvent(
+ VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED);
+ event.device = getDevice(address);
+ event.valueInt1 = externalOutputId;
+ event.valueInt2 = location;
+
+ if (DBG) {
+ Log.d(TAG, "onExtAudioOutLocationChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
+ private void onExtAudioOutDescriptionChanged(int externalOutputId, String descr,
+ byte[] address) {
+ VolumeControlStackEvent event =
+ new VolumeControlStackEvent(
+ VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED);
+ event.device = getDevice(address);
+ event.valueInt1 = externalOutputId;
+ event.valueString1 = descr;
+
+ if (DBG) {
+ Log.d(TAG, "onExtAudioOutLocationChanged: " + event);
+ }
+ sendMessageToService(event);
+ }
+
// Native methods that call into the JNI interface
private static native void classInitNative();
private native void initNative();
@@ -196,4 +333,13 @@ public class VolumeControlNativeInterface {
private native boolean disconnectVolumeControlNative(byte[] address);
private native void setVolumeNative(byte[] address, int volume);
private native void setVolumeGroupNative(int groupId, int volume);
+ private native boolean getExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId);
+ private native boolean setExtAudioOutVolumeOffsetNative(byte[] address, int externalOutputId,
+ int offset);
+ private native boolean getExtAudioOutLocationNative(byte[] address, int externalOutputId);
+ private native boolean setExtAudioOutLocationNative(byte[] address, int externalOutputId,
+ int location);
+ private native boolean getExtAudioOutDescriptionNative(byte[] address, int externalOutputId);
+ private native boolean setExtAudioOutDescriptionNative(byte[] address, int externalOutputId,
+ String descr);
}
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
index f6a8e037dc..e976271860 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlService.java
@@ -27,6 +27,7 @@ import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.BluetoothVolumeControl;
import android.bluetooth.IBluetoothVolumeControl;
+import android.bluetooth.IBluetoothVolumeControlCallback;
import android.content.AttributionSource;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -37,6 +38,8 @@ import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.HandlerThread;
import android.os.ParcelUuid;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
import android.util.Log;
import com.android.bluetooth.Utils;
@@ -70,6 +73,98 @@ public class VolumeControlService extends ProfileService {
private HandlerThread mStateMachinesThread;
private BluetoothDevice mPreviousAudioDevice;
+ @VisibleForTesting
+ RemoteCallbackList<IBluetoothVolumeControlCallback> mCallbacks;
+
+ private class VolumeControlOffsetDescriptor {
+ private class Descriptor {
+ Descriptor() {
+ mValue = 0;
+ mLocation = 0;
+ mDescription = null;
+ }
+ int mValue;
+ int mLocation;
+ String mDescription;
+ };
+
+ VolumeControlOffsetDescriptor() {
+ mVolumeOffsets = new HashMap<>();
+ }
+ int size() {
+ return mVolumeOffsets.size();
+ }
+ void add(int id) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ mVolumeOffsets.put(id, new Descriptor());
+ }
+ }
+ boolean setValue(int id, int value) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return false;
+ }
+ d.mValue = value;
+ return true;
+ }
+ int getValue(int id) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return 0;
+ }
+ return d.mValue;
+ }
+ boolean setDescription(int id, String desc) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return false;
+ }
+ d.mDescription = desc;
+ return true;
+ }
+ String getDescription(int id) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return null;
+ }
+ return d.mDescription;
+ }
+ boolean setLocation(int id, int location) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return false;
+ }
+ d.mLocation = location;
+ return true;
+ }
+ int getLocation(int id) {
+ Descriptor d = mVolumeOffsets.get(id);
+ if (d == null) {
+ return 0;
+ }
+ return d.mLocation;
+ }
+ void remove(int id) {
+ mVolumeOffsets.remove(id);
+ }
+ void clear() {
+ mVolumeOffsets.clear();
+ }
+ void dump(StringBuilder sb) {
+ for (Map.Entry<Integer, Descriptor> entry : mVolumeOffsets.entrySet()) {
+ Descriptor descriptor = entry.getValue();
+ Integer id = entry.getKey();
+ ProfileService.println(sb, " Id: " + id);
+ ProfileService.println(sb, " value: " + descriptor.mValue);
+ ProfileService.println(sb, " location: " + descriptor.mLocation);
+ ProfileService.println(sb, " description: " + descriptor.mDescription);
+ }
+ }
+
+ Map<Integer, Descriptor> mVolumeOffsets;
+ }
+
private int mMusicMaxVolume = 0;
private int mMusicMinVolume = 0;
private int mVoiceCallMaxVolume = 0;
@@ -81,6 +176,8 @@ public class VolumeControlService extends ProfileService {
AudioManager mAudioManager;
private final Map<BluetoothDevice, VolumeControlStateMachine> mStateMachines = new HashMap<>();
+ private final Map<BluetoothDevice, VolumeControlOffsetDescriptor> mAudioOffsets =
+ new HashMap<>();
private BroadcastReceiver mBondStateChangedReceiver;
private BroadcastReceiver mConnectionStateChangedReceiver;
@@ -139,6 +236,9 @@ public class VolumeControlService extends ProfileService {
mConnectionStateChangedReceiver = new ConnectionStateChangedReceiver();
registerReceiver(mConnectionStateChangedReceiver, filter);
+ mAudioOffsets.clear();
+ mCallbacks = new RemoteCallbackList<IBluetoothVolumeControlCallback>();
+
// Mark service as started
setVolumeControlService(this);
@@ -158,10 +258,6 @@ public class VolumeControlService extends ProfileService {
return true;
}
- // Cleanup native interface
- mVolumeControlNativeInterface.cleanup();
- mVolumeControlNativeInterface = null;
-
// Mark service as stopped
setVolumeControlService(null);
@@ -180,7 +276,6 @@ public class VolumeControlService extends ProfileService {
mStateMachines.clear();
}
-
if (mStateMachinesThread != null) {
try {
mStateMachinesThread.quitSafely();
@@ -191,11 +286,21 @@ public class VolumeControlService extends ProfileService {
}
}
+ // Cleanup native interface
+ mVolumeControlNativeInterface.cleanup();
+ mVolumeControlNativeInterface = null;
+
+ mAudioOffsets.clear();
+
// Clear AdapterService, VolumeControlNativeInterface
mAudioManager = null;
mVolumeControlNativeInterface = null;
mAdapterService = null;
+ if (mCallbacks != null) {
+ mCallbacks.kill();
+ }
+
return true;
}
@@ -433,8 +538,31 @@ public class VolumeControlService extends ProfileService {
.getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL);
}
+ boolean isVolumeOffsetAvailable(BluetoothDevice device) {
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ Log.i(TAG, " There is no offset service for device: " + device);
+ return false;
+ }
+ Log.i(TAG, " Offset service available for device: " + device);
+ return true;
+ }
+
void setVolumeOffset(BluetoothDevice device, int volumeOffset) {
- // TODO Implement
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ Log.e(TAG, " There is no offset service for device: " + device);
+ return;
+ }
+
+ /* Use first offset always */
+ int value = offsets.getValue(1);
+ if (value == volumeOffset) {
+ /* Nothing to do - offset already applied */
+ return;
+ }
+
+ mVolumeControlNativeInterface.setExtAudioOutVolumeOffset(device, 1, volumeOffset);
}
/**
@@ -484,6 +612,85 @@ public class VolumeControlService extends ProfileService {
return AudioManager.STREAM_MUSIC;
}
+ void handleDeviceAvailable(BluetoothDevice device, int numberOfExternalOutputs) {
+ if (numberOfExternalOutputs == 0) {
+ Log.i(TAG, "Volume offset not available");
+ return;
+ }
+
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ offsets = new VolumeControlOffsetDescriptor();
+ mAudioOffsets.put(device, offsets);
+ } else if (offsets.size() != numberOfExternalOutputs) {
+ Log.i(TAG, "Number of offset changed: ");
+ offsets.clear();
+ }
+
+ /* Stack delivers us number of audio outputs.
+ * Offset ids a countinous from 1 to number_of_ext_outputs*/
+ for (int i = 1; i <= numberOfExternalOutputs; i++) {
+ offsets.add(i);
+ mVolumeControlNativeInterface.getExtAudioOutVolumeOffset(device, i);
+ mVolumeControlNativeInterface.getExtAudioOutDescription(device, i);
+ }
+ }
+
+ void handleDeviceExtAudioOffsetChanged(BluetoothDevice device, int id, int value) {
+ if (DBG) {
+ Log.d(TAG, " device: " + device + " offset_id: " + id + " value: " + value);
+ }
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ Log.e(TAG, " Offsets not found for device: " + device);
+ return;
+ }
+ offsets.setValue(id, value);
+
+ if (mCallbacks == null) {
+ return;
+ }
+
+ int n = mCallbacks.beginBroadcast();
+ for (int i = 0; i < n; i++) {
+ try {
+ mCallbacks.getBroadcastItem(i).onVolumeOffsetChanged(device, value);
+ } catch (RemoteException e) {
+ continue;
+ }
+ }
+ mCallbacks.finishBroadcast();
+ }
+
+ void handleDeviceExtAudioLocationChanged(BluetoothDevice device, int id, int location) {
+ if (DBG) {
+ Log.d(TAG, " device: " + device + " offset_id: "
+ + id + " location: " + location);
+ }
+
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ Log.e(TAG, " Offsets not found for device: " + device);
+ return;
+ }
+ offsets.setLocation(id, location);
+ }
+
+ void handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id,
+ String description) {
+ if (DBG) {
+ Log.d(TAG, " device: " + device + " offset_id: "
+ + id + " description: " + description);
+ }
+
+ VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
+ if (offsets == null) {
+ Log.e(TAG, " Offsets not found for device: " + device);
+ return;
+ }
+ offsets.setDescription(id, description);
+ }
+
void messageFromNative(VolumeControlStackEvent stackEvent) {
if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) {
@@ -505,8 +712,33 @@ public class VolumeControlService extends ProfileService {
return;
}
+ BluetoothDevice device = stackEvent.device;
+ if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE) {
+ handleDeviceAvailable(device, stackEvent.valueInt1);
+ return;
+ }
+
+ if (stackEvent.type
+ == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED) {
+ handleDeviceExtAudioOffsetChanged(device, stackEvent.valueInt1, stackEvent.valueInt2);
+ return;
+ }
+
+ if (stackEvent.type
+ == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED) {
+ handleDeviceExtAudioLocationChanged(device, stackEvent.valueInt1,
+ stackEvent.valueInt2);
+ return;
+ }
+
+ if (stackEvent.type
+ == VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED) {
+ handleDeviceExtAudioDescriptionChanged(device, stackEvent.valueInt1,
+ stackEvent.valueString1);
+ return;
+ }
+
synchronized (mStateMachines) {
- BluetoothDevice device = stackEvent.device;
VolumeControlStateMachine sm = mStateMachines.get(device);
if (sm == null) {
if (stackEvent.type
@@ -792,6 +1024,21 @@ public class VolumeControlService extends ProfileService {
}
@Override
+ public void isVolumeOffsetAvailable(BluetoothDevice device,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ try {
+ boolean defaultValue = false;
+ VolumeControlService service = getService(source);
+ if (service != null) {
+ defaultValue = service.isVolumeOffsetAvailable(device);
+ }
+ receiver.send(defaultValue);
+ } catch (RuntimeException e) {
+ receiver.propagateException(e);
+ }
+ }
+
+ @Override
public void setVolumeOffset(BluetoothDevice device, int volumeOffset,
AttributionSource source, SynchronousResultReceiver receiver) {
try {
@@ -818,6 +1065,44 @@ public class VolumeControlService extends ProfileService {
receiver.propagateException(e);
}
}
+
+ @Override
+ public void registerCallback(IBluetoothVolumeControlCallback callback,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ VolumeControlService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
+ try {
+ service.mCallbacks.register(callback);
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ throw new IllegalArgumentException(" Invalid callback");
+ }
+ }
+
+ @Override
+ public void unregisterCallback(IBluetoothVolumeControlCallback callback,
+ AttributionSource source, SynchronousResultReceiver receiver) {
+ VolumeControlService service = getService(source);
+ if (service == null) {
+ throw new IllegalStateException("Service is unavailable");
+ }
+
+ enforceBluetoothPrivilegedPermission(service);
+
+ try {
+ service.mCallbacks.unregister(callback);
+ receiver.send(null);
+ } catch (RuntimeException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ throw new IllegalArgumentException(" Invalid callback ");
+ }
+ }
}
@Override
@@ -826,5 +1111,14 @@ public class VolumeControlService extends ProfileService {
for (VolumeControlStateMachine sm : mStateMachines.values()) {
sm.dump(sb);
}
+
+ for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry :
+ mAudioOffsets.entrySet()) {
+ VolumeControlOffsetDescriptor descriptor = entry.getValue();
+ BluetoothDevice device = entry.getKey();
+ ProfileService.println(sb, " Device: " + device);
+ ProfileService.println(sb, " Volume offset cnt: " + descriptor.size());
+ descriptor.dump(sb);
+ }
}
}
diff --git a/android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java b/android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java
index 2750c7e694..38674532f6 100644
--- a/android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java
+++ b/android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java
@@ -24,6 +24,10 @@ public class VolumeControlStackEvent {
private static final int EVENT_TYPE_NONE = 0;
public static final int EVENT_TYPE_CONNECTION_STATE_CHANGED = 1;
public static final int EVENT_TYPE_VOLUME_STATE_CHANGED = 2;
+ public static final int EVENT_TYPE_DEVICE_AVAILABLE = 3;
+ public static final int EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED = 4;
+ public static final int EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED = 5;
+ public static final int EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED = 6;
// Do not modify without updating the HAL bt_vc_aid.h files.
// Match up with enum class ConnectionState of bt_vc_aid.h.
@@ -38,6 +42,8 @@ public class VolumeControlStackEvent {
public int valueInt2;
public boolean valueBool1;
public boolean valueBool2;
+ public String valueString1;
+
/* Might need more for other callbacks*/
VolumeControlStackEvent(int type) {
@@ -54,6 +60,7 @@ public class VolumeControlStackEvent {
result.append(", valueInt2:" + eventTypeValue2ToString(type, valueInt2));
result.append(", valueBool1:" + eventTypeValueBool1ToString(type, valueBool1));
result.append(", valueBool2:" + eventTypeValueBool2ToString(type, valueBool2));
+ result.append(", valueString1:" + eventTypeString1ToString(type, valueString1));
result.append("}");
return result.toString();
}
@@ -66,6 +73,14 @@ public class VolumeControlStackEvent {
return "EVENT_TYPE_CONNECTION_STATE_CHANGED";
case EVENT_TYPE_VOLUME_STATE_CHANGED:
return "EVENT_TYPE_VOLUME_STATE_CHANGED";
+ case EVENT_TYPE_DEVICE_AVAILABLE:
+ return "EVENT_TYPE_DEVICE_AVAILABLE";
+ case EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED:
+ return "EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED";
+ case EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED:
+ return "EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED";
+ case EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED:
+ return "EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED";
default:
return "EVENT_TYPE_UNKNOWN:" + type;
}
@@ -88,6 +103,12 @@ public class VolumeControlStackEvent {
}
case EVENT_TYPE_VOLUME_STATE_CHANGED:
return "{group_id:" + value + "}";
+ case EVENT_TYPE_DEVICE_AVAILABLE:
+ return "{num_ext_outputs:" + value + "}";
+ case EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED:
+ case EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED:
+ case EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED:
+ return "{ext output id:" + value + "}";
default:
break;
}
@@ -136,4 +157,14 @@ public class VolumeControlStackEvent {
}
return Boolean.toString(value);
}
+
+ private static String eventTypeString1ToString(int type, String value) {
+ switch (type) {
+ case EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED:
+ return "{descrition:" + value + "}";
+ default:
+ break;
+ }
+ return value;
+ }
}
diff --git a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
index 1ef07996e7..ed40cfe3fc 100644
--- a/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
+++ b/android/app/tests/unit/src/com/android/bluetooth/hfp/HeadsetPhoneStateTest.java
@@ -18,12 +18,12 @@ package com.android.bluetooth.hfp;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
-import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.content.Context;
import android.os.HandlerThread;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.Looper;
import android.os.ServiceManager;
import android.telephony.PhoneStateListener;
@@ -68,7 +68,7 @@ public class HeadsetPhoneStateTest {
if (Looper.myLooper() == null) {
Looper.prepare();
}
- PropertyInvalidatedCache.disableForTestMode();
+ IpcDataCache.disableForTestMode();
MockitoAnnotations.initMocks(this);
SubscriptionManager.disableCaching();
TelephonyManager.disableServiceHandleCaching();
diff --git a/framework/api/system-current.txt b/framework/api/system-current.txt
index 9e0decb32b..c8694d175e 100644
--- a/framework/api/system-current.txt
+++ b/framework/api/system-current.txt
@@ -328,8 +328,8 @@ package android.bluetooth {
public static final class BluetoothHeadsetClient.NetworkServiceState implements android.os.Parcelable {
method @NonNull public android.bluetooth.BluetoothDevice getDevice();
- method @Nullable public String getOperatorName();
- method public int getSignalStrength();
+ method @Nullable public String getNetworkOperatorName();
+ method @IntRange(from=0, to=5) public int getSignalStrength();
method public boolean isRoaming();
method public boolean isServiceAvailable();
field @NonNull public static final android.os.Parcelable.Creator<android.bluetooth.BluetoothHeadsetClient.NetworkServiceState> CREATOR;
diff --git a/framework/java/android/bluetooth/BluetoothAdapter.java b/framework/java/android/bluetooth/BluetoothAdapter.java
index 936a08569b..5767abb2b5 100644
--- a/framework/java/android/bluetooth/BluetoothAdapter.java
+++ b/framework/java/android/bluetooth/BluetoothAdapter.java
@@ -32,7 +32,6 @@ import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
import android.app.PendingIntent;
-import android.app.PropertyInvalidatedCache;
import android.bluetooth.BluetoothDevice.AddressType;
import android.bluetooth.BluetoothDevice.Transport;
import android.bluetooth.BluetoothProfile.ConnectionPolicy;
@@ -57,6 +56,7 @@ import android.os.Binder;
import android.os.BluetoothServiceManager;
import android.os.Build;
import android.os.IBinder;
+import android.os.IpcDataCache;
import android.os.ParcelUuid;
import android.os.RemoteException;
import android.os.ResultReceiver;
@@ -1125,13 +1125,13 @@ public final class BluetoothAdapter {
}
/**
- * There are several instances of PropertyInvalidatedCache used in this class.
+ * There are several instances of IpcDataCache used in this class.
* BluetoothCache wraps up the common code. All caches are created with a maximum of
* eight entries, and the key is in the bluetooth module. The name is set to the api.
*/
- private static class BluetoothCache<Q, R> extends PropertyInvalidatedCache<Q, R> {
- BluetoothCache(String api, PropertyInvalidatedCache.QueryHandler query) {
- super(8, PropertyInvalidatedCache.MODULE_BLUETOOTH, api, api, query);
+ private static class BluetoothCache<Q, R> extends IpcDataCache<Q, R> {
+ BluetoothCache(String api, IpcDataCache.QueryHandler query) {
+ super(8, IpcDataCache.MODULE_BLUETOOTH, api, api, query);
}};
/**
@@ -1139,7 +1139,7 @@ public final class BluetoothAdapter {
* enforces the bluetooth module.
*/
private static void invalidateCache(@NonNull String api) {
- PropertyInvalidatedCache.invalidateCache(PropertyInvalidatedCache.MODULE_BLUETOOTH, api);
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
/**
@@ -1147,8 +1147,8 @@ public final class BluetoothAdapter {
*/
private static final String GET_STATE_API = "getState";
- private final PropertyInvalidatedCache.QueryHandler<Void, Integer> mBluetoothGetStateQuery =
- new PropertyInvalidatedCache.QueryHandler<>() {
+ private final IpcDataCache.QueryHandler<Void, Integer> mBluetoothGetStateQuery =
+ new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@AdapterState
@@ -1174,7 +1174,7 @@ public final class BluetoothAdapter {
return state;
}};
- private final PropertyInvalidatedCache<Void, Integer> mBluetoothGetStateCache =
+ private final IpcDataCache<Void, Integer> mBluetoothGetStateCache =
new BluetoothCache<Void, Integer>(GET_STATE_API, mBluetoothGetStateQuery);
/** @hide */
@@ -2267,8 +2267,8 @@ public final class BluetoothAdapter {
}
}
- private final PropertyInvalidatedCache.QueryHandler<Void, Boolean> mBluetoothFilteringQuery =
- new PropertyInvalidatedCache.QueryHandler<>() {
+ private final IpcDataCache.QueryHandler<Void, Boolean> mBluetoothFilteringQuery =
+ new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@Override
@@ -2290,7 +2290,7 @@ public final class BluetoothAdapter {
private static final String FILTERING_API = "isOffloadedFilteringSupported";
- private final PropertyInvalidatedCache<Void, Boolean> mBluetoothFilteringCache =
+ private final IpcDataCache<Void, Boolean> mBluetoothFilteringCache =
new BluetoothCache<Void, Boolean>(FILTERING_API, mBluetoothFilteringQuery);
/** @hide */
@@ -2810,8 +2810,8 @@ public final class BluetoothAdapter {
return supportedProfiles;
}
- private final PropertyInvalidatedCache.QueryHandler<Void, Integer> mBluetoothGetAdapterQuery =
- new PropertyInvalidatedCache.QueryHandler<>() {
+ private final IpcDataCache.QueryHandler<Void, Integer> mBluetoothGetAdapterQuery =
+ new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresNoPermission
@Override
@@ -2836,7 +2836,7 @@ public final class BluetoothAdapter {
}};
private static final String GET_CONNECTION_API = "getAdapterConnectionState";
- private final PropertyInvalidatedCache<Void, Integer>
+ private final IpcDataCache<Void, Integer>
mBluetoothGetAdapterConnectionStateCache =
new BluetoothCache<Void, Integer>(GET_CONNECTION_API, mBluetoothGetAdapterQuery);
@@ -2872,8 +2872,8 @@ public final class BluetoothAdapter {
return mBluetoothGetAdapterConnectionStateCache.query(null);
}
- private final PropertyInvalidatedCache.QueryHandler<Integer, Integer> mBluetoothProfileQuery =
- new PropertyInvalidatedCache.QueryHandler<>() {
+ private final IpcDataCache.QueryHandler<Integer, Integer> mBluetoothProfileQuery =
+ new IpcDataCache.QueryHandler<>() {
@RequiresNoPermission
@Override
public Integer apply(Integer query) {
@@ -2896,7 +2896,7 @@ public final class BluetoothAdapter {
}};
private static final String PROFILE_API = "getProfileConnectionState";
- private final PropertyInvalidatedCache<Integer, Integer>
+ private final IpcDataCache<Integer, Integer>
mGetProfileConnectionStateCache =
new BluetoothCache<Integer, Integer>(PROFILE_API, mBluetoothProfileQuery);
diff --git a/framework/java/android/bluetooth/BluetoothDevice.java b/framework/java/android/bluetooth/BluetoothDevice.java
index 8c591e897d..71e15f0063 100644
--- a/framework/java/android/bluetooth/BluetoothDevice.java
+++ b/framework/java/android/bluetooth/BluetoothDevice.java
@@ -26,7 +26,6 @@ import android.annotation.SdkConstant;
import android.annotation.SdkConstant.SdkConstantType;
import android.annotation.SuppressLint;
import android.annotation.SystemApi;
-import android.app.PropertyInvalidatedCache;
import android.bluetooth.annotations.RequiresBluetoothConnectPermission;
import android.bluetooth.annotations.RequiresBluetoothLocationPermission;
import android.bluetooth.annotations.RequiresBluetoothScanPermission;
@@ -38,6 +37,7 @@ import android.content.AttributionSource;
import android.content.Context;
import android.os.Build;
import android.os.Handler;
+import android.os.IpcDataCache;
import android.os.Parcel;
import android.os.ParcelUuid;
import android.os.Parcelable;
@@ -1823,13 +1823,13 @@ public final class BluetoothDevice implements Parcelable, Attributable {
}
/**
- * There are several instances of PropertyInvalidatedCache used in this class.
+ * There are several instances of IpcDataCache used in this class.
* BluetoothCache wraps up the common code. All caches are created with a maximum of
* eight entries, and the key is in the bluetooth module. The name is set to the api.
*/
- private static class BluetoothCache<Q, R> extends PropertyInvalidatedCache<Q, R> {
- BluetoothCache(String api, PropertyInvalidatedCache.QueryHandler query) {
- super(8, PropertyInvalidatedCache.MODULE_BLUETOOTH, api, api, query);
+ private static class BluetoothCache<Q, R> extends IpcDataCache<Q, R> {
+ BluetoothCache(String api, IpcDataCache.QueryHandler query) {
+ super(8, IpcDataCache.MODULE_BLUETOOTH, api, api, query);
}};
/**
@@ -1837,12 +1837,12 @@ public final class BluetoothDevice implements Parcelable, Attributable {
* enforces the bluetooth module.
*/
private static void invalidateCache(@NonNull String api) {
- PropertyInvalidatedCache.invalidateCache(PropertyInvalidatedCache.MODULE_BLUETOOTH, api);
+ IpcDataCache.invalidateCache(IpcDataCache.MODULE_BLUETOOTH, api);
}
private final
- PropertyInvalidatedCache.QueryHandler<BluetoothDevice, Integer> mBluetoothBondQuery =
- new PropertyInvalidatedCache.QueryHandler<>() {
+ IpcDataCache.QueryHandler<BluetoothDevice, Integer> mBluetoothBondQuery =
+ new IpcDataCache.QueryHandler<>() {
@RequiresLegacyBluetoothPermission
@RequiresBluetoothConnectPermission
@RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)
diff --git a/framework/java/android/bluetooth/BluetoothHeadsetClient.java b/framework/java/android/bluetooth/BluetoothHeadsetClient.java
index 9be1b328f3..6a67923f11 100644
--- a/framework/java/android/bluetooth/BluetoothHeadsetClient.java
+++ b/framework/java/android/bluetooth/BluetoothHeadsetClient.java
@@ -17,6 +17,7 @@ package android.bluetooth;
import static android.bluetooth.BluetoothUtils.getSyncTimeout;
+import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -1742,8 +1743,19 @@ public final class BluetoothHeadsetClient implements BluetoothProfile, AutoClose
private final String mOperatorName;
/**
- * The general signal strength
- * (0 - Unknown, 1 - Poor, 2 - Fair, 3 - Good, 4 - Great, 5 - Excellent)
+ * The general signal strength, from 0 to 5.
+ *
+ * Bluetooth HFP v1.8 specifies that the signal strength of a device can be [0, 5]. It does
+ * place any requirements on how a device derives those values. While they're typically
+ * derived from signal quality/RSSI buckets, there's way to be certain on the exact meaning.
+ *
+ * That said, you can "generally" interpret the values relative to each other as follows:
+ * - Level 0: None/Unknown
+ * - Level 1: Very Poor
+ * - Level 2: Poor
+ * - Level 3: Fair
+ * - Level 4: Good
+ * - Level 5: Great
*/
private final int mSignalStrength;
@@ -1804,20 +1816,19 @@ public final class BluetoothHeadsetClient implements BluetoothProfile, AutoClose
* @hide
*/
@SystemApi
- public @Nullable String getOperatorName() {
+ public @Nullable String getNetworkOperatorName() {
return mOperatorName;
}
/**
* Get the network's general signal strength
*
- * @return The general signal strength (0 - None, 1 - Poor, 2 - Fair, 3 - Good,
- * 4 - Great, 5 - Excellent)
+ * @return The general signal strength, range [0, 5]
*
* @hide
*/
@SystemApi
- public int getSignalStrength() {
+ public @IntRange(from = 0, to = 5) int getSignalStrength() {
return mSignalStrength;
}
diff --git a/framework/java/android/bluetooth/BluetoothLeAudio.java b/framework/java/android/bluetooth/BluetoothLeAudio.java
index 2db1d1afe5..23e9a039f4 100644
--- a/framework/java/android/bluetooth/BluetoothLeAudio.java
+++ b/framework/java/android/bluetooth/BluetoothLeAudio.java
@@ -966,9 +966,22 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
Log.d(TAG, "getCodecStatus(" + device + ")");
}
+ final IBluetoothLeAudio service = getService();
final BluetoothLeAudioCodecStatus defaultValue = null;
- // TODO: Add the implementation to get codec status
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ final SynchronousResultReceiver<BluetoothLeAudioCodecStatus> recv =
+ new SynchronousResultReceiver();
+ service.getCodecStatus(device, mAttributionSource, recv);
+ return recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(defaultValue);
+ } catch (RemoteException | TimeoutException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
return defaultValue;
}
@@ -994,8 +1007,18 @@ public final class BluetoothLeAudio implements BluetoothProfile, AutoCloseable {
throw new IllegalArgumentException("codecConfig cannot be null");
}
- // TODO: Add the implementation to set config preference
- return;
+ final IBluetoothLeAudio service = getService();
+
+ if (service == null) {
+ Log.w(TAG, "Proxy not attached to service");
+ if (DBG) log(Log.getStackTraceString(new Throwable()));
+ } else if (mAdapter.isEnabled() && isValidDevice(device)) {
+ try {
+ service.setCodecConfigPreference(device, codecConfig, mAttributionSource);
+ } catch (RemoteException e) {
+ Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
+ }
+ }
}
}
diff --git a/framework/java/android/bluetooth/BluetoothLeAudioCodecStatus.java b/framework/java/android/bluetooth/BluetoothLeAudioCodecStatus.java
index dea0642041..399ffa743c 100644
--- a/framework/java/android/bluetooth/BluetoothLeAudioCodecStatus.java
+++ b/framework/java/android/bluetooth/BluetoothLeAudioCodecStatus.java
@@ -109,8 +109,15 @@ public final class BluetoothLeAudioCodecStatus implements Parcelable {
* @return {@code true} if the codec config matches, {@code false} otherwise
*/
public boolean isCodecConfigSelectable(@Nullable BluetoothLeAudioCodecConfig codecConfig) {
- // TODO: Add the implementation to check the config is selectable
- return true;
+ if (codecConfig == null) {
+ return false;
+ }
+ for (BluetoothLeAudioCodecConfig selectableConfig : mCodecsSelectableCapabilities) {
+ if (codecConfig.equals(selectableConfig)) {
+ return true;
+ }
+ }
+ return false;
}
/**
@@ -171,6 +178,8 @@ public final class BluetoothLeAudioCodecStatus implements Parcelable {
/**
* Returns the current codec configuration.
+ *
+ * @return The current codec config.
*/
public @Nullable BluetoothLeAudioCodecConfig getCodecConfig() {
return mCodecConfig;
@@ -178,6 +187,8 @@ public final class BluetoothLeAudioCodecStatus implements Parcelable {
/**
* Returns the codecs local capabilities.
+ *
+ * @return The list of codec config that supported by the local system.
*/
public @NonNull List<BluetoothLeAudioCodecConfig> getCodecLocalCapabilities() {
return (mCodecsLocalCapabilities == null)
@@ -186,6 +197,9 @@ public final class BluetoothLeAudioCodecStatus implements Parcelable {
/**
* Returns the codecs selectable capabilities.
+ *
+ * @return The list of codec config that supported by both of the local system and
+ * remote devices.
*/
public @NonNull List<BluetoothLeAudioCodecConfig> getCodecSelectableCapabilities() {
return (mCodecsSelectableCapabilities == null)
diff --git a/service/java/com/android/server/bluetooth/BluetoothManagerService.java b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
index e2fc938efd..5e27ee66d9 100644
--- a/service/java/com/android/server/bluetooth/BluetoothManagerService.java
+++ b/service/java/com/android/server/bluetooth/BluetoothManagerService.java
@@ -58,7 +58,6 @@ import android.content.ServiceConnection;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
-import android.content.res.Resources;
import android.database.ContentObserver;
import android.os.BatteryStatsManager;
import android.os.Binder;
@@ -1231,9 +1230,11 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- if (!isPrivileged(callingPid, callingUid) && !isDeviceOwner(callingUid, packageName)
- && CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid)
- && !isSystem(packageName, callingUid)) {
+ if (CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid)
+ && !isPrivileged(callingPid, callingUid)
+ && !isSystem(packageName, callingUid)
+ && !isDeviceOwner(callingUid, packageName)
+ && !isProfileOwner(callingUid, packageName)) {
return false;
}
@@ -1272,9 +1273,11 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
final int callingUid = Binder.getCallingUid();
final int callingPid = Binder.getCallingPid();
- if (!isPrivileged(callingPid, callingUid) && !isDeviceOwner(callingUid, packageName)
- && CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid)
- && !isSystem(packageName, callingUid)) {
+ if (CompatChanges.isChangeEnabled(RESTRICT_ENABLE_DISABLE, callingUid)
+ && !isPrivileged(callingPid, callingUid)
+ && !isSystem(packageName, callingUid)
+ && !isDeviceOwner(callingUid, packageName)
+ && !isProfileOwner(callingUid, packageName)) {
return false;
}
@@ -3093,6 +3096,7 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
Log.e(TAG, "isDeviceOwner: packageName is null, returning false");
return false;
}
+
Pair<UserHandle, ComponentName> deviceOwner = getDeviceOwner();
// no device owner
@@ -3102,6 +3106,28 @@ public class BluetoothManagerService extends IBluetoothManager.Stub {
&& deviceOwner.second.getPackageName().equals(packageName);
}
+ private boolean isProfileOwner(int uid, String packageName) {
+ Context userContext;
+ try {
+ userContext = mContext.createPackageContextAsUser(mContext.getPackageName(), 0,
+ UserHandle.getUserHandleForUid(uid));
+ } catch (PackageManager.NameNotFoundException e) {
+ Log.e(TAG, "Unknown package name");
+ return false;
+ }
+ if (userContext == null) {
+ Log.e(TAG, "Unable to retrieve user context for " + uid);
+ return false;
+ }
+ DevicePolicyManager devicePolicyManager =
+ userContext.getSystemService(DevicePolicyManager.class);
+ if (devicePolicyManager == null) {
+ Log.w(TAG, "Error retrieving DPM service");
+ return false;
+ }
+ return devicePolicyManager.isProfileOwnerApp(packageName);
+ }
+
public boolean isSystem(String packageName, int uid) {
long ident = Binder.clearCallingIdentity();
try {
diff --git a/system/audio_bluetooth_hw/stream_apis.cc b/system/audio_bluetooth_hw/stream_apis.cc
index cd5e1de4d2..2288698658 100644
--- a/system/audio_bluetooth_hw/stream_apis.cc
+++ b/system/audio_bluetooth_hw/stream_apis.cc
@@ -27,6 +27,8 @@
#include <time.h>
#include <unistd.h>
+#include <memory>
+
#include "BluetoothAudioSession.h"
#include "stream_apis.h"
#include "utils.h"
@@ -697,7 +699,7 @@ int adev_open_output_stream(struct audio_hw_device* dev,
struct audio_stream_out** stream_out,
const char* address __unused) {
*stream_out = nullptr;
- auto* out = new BluetoothStreamOut{};
+ auto out = std::make_unique<BluetoothStreamOut>();
if (::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::
IsAidlAvailable()) {
out->bluetooth_output_ = std::make_unique<
@@ -711,7 +713,6 @@ int adev_open_output_stream(struct audio_hw_device* dev,
if (!out->bluetooth_output_->SetUp(devices)) {
out->bluetooth_output_ = nullptr;
LOG(ERROR) << __func__ << ": cannot init HAL";
- delete out;
return -EINVAL;
}
LOG(VERBOSE) << __func__ << ": device=" << StringPrintf("%#x", devices);
@@ -783,18 +784,21 @@ int adev_open_output_stream(struct audio_hw_device* dev,
out->frames_rendered_ = 0;
out->frames_presented_ = 0;
+ BluetoothStreamOut* out_ptr = out.release();
{
auto* bluetooth_device = reinterpret_cast<BluetoothAudioDevice*>(dev);
std::lock_guard<std::mutex> guard(bluetooth_device->mutex_);
- bluetooth_device->opened_stream_outs_.push_back(out);
+ bluetooth_device->opened_stream_outs_.push_back(out_ptr);
}
- *stream_out = &out->stream_out_;
- LOG(INFO) << __func__ << ": state=" << out->bluetooth_output_->GetState()
- << ", sample_rate=" << out->sample_rate_
- << ", channels=" << StringPrintf("%#x", out->channel_mask_)
- << ", format=" << out->format_ << ", preferred_data_interval_us="
- << out->preferred_data_interval_us
- << ", frames=" << out->frames_count_;
+
+ *stream_out = &out_ptr->stream_out_;
+ LOG(INFO) << __func__ << ": state=" << out_ptr->bluetooth_output_->GetState()
+ << ", sample_rate=" << out_ptr->sample_rate_
+ << ", channels=" << StringPrintf("%#x", out_ptr->channel_mask_)
+ << ", format=" << out_ptr->format_
+ << ", preferred_data_interval_us="
+ << out_ptr->preferred_data_interval_us
+ << ", frames=" << out_ptr->frames_count_;
return 0;
}
@@ -1204,7 +1208,7 @@ int adev_open_input_stream(struct audio_hw_device* dev,
const char* address __unused,
audio_source_t source __unused) {
*stream_in = nullptr;
- auto* in = new BluetoothStreamIn{};
+ auto in = std::make_unique<BluetoothStreamIn>();
if (::aidl::android::hardware::bluetooth::audio::BluetoothAudioSession::
IsAidlAvailable()) {
in->bluetooth_input_ = std::make_unique<
@@ -1218,7 +1222,6 @@ int adev_open_input_stream(struct audio_hw_device* dev,
if (!in->bluetooth_input_->SetUp(devices)) {
in->bluetooth_input_ = nullptr;
LOG(ERROR) << __func__ << ": cannot init HAL";
- delete in;
return -EINVAL;
}
@@ -1274,13 +1277,14 @@ int adev_open_input_stream(struct audio_hw_device* dev,
frame_count(in->preferred_data_interval_us, in->sample_rate_);
in->frames_presented_ = 0;
- *stream_in = &in->stream_in_;
- LOG(INFO) << __func__ << ": state=" << in->bluetooth_input_->GetState()
- << ", sample_rate=" << in->sample_rate_
- << ", channels=" << StringPrintf("%#x", in->channel_mask_)
- << ", format=" << in->format_
- << ", preferred_data_interval_us=" << in->preferred_data_interval_us
- << ", frames=" << in->frames_count_;
+ BluetoothStreamIn* in_ptr = in.release();
+ *stream_in = &in_ptr->stream_in_;
+ LOG(INFO) << __func__ << ": state=" << in_ptr->bluetooth_input_->GetState()
+ << ", sample_rate=" << in_ptr->sample_rate_
+ << ", channels=" << StringPrintf("%#x", in_ptr->channel_mask_)
+ << ", format=" << in_ptr->format_ << ", preferred_data_interval_us="
+ << in_ptr->preferred_data_interval_us
+ << ", frames=" << in_ptr->frames_count_;
return 0;
}
diff --git a/system/binder/Android.bp b/system/binder/Android.bp
index ff18409fd4..c5244526c0 100644
--- a/system/binder/Android.bp
+++ b/system/binder/Android.bp
@@ -28,6 +28,7 @@ filegroup {
"android/bluetooth/IBluetoothHapClient.aidl",
"android/bluetooth/IBluetoothHapClientCallback.aidl",
"android/bluetooth/IBluetoothVolumeControl.aidl",
+ "android/bluetooth/IBluetoothVolumeControlCallback.aidl",
"android/bluetooth/IBluetoothHidHost.aidl",
"android/bluetooth/IBluetoothLeAudio.aidl",
"android/bluetooth/IBluetoothLeBroadcastCallback.aidl",
diff --git a/system/binder/android/bluetooth/BluetoothLeAudioCodecConfig.aidl b/system/binder/android/bluetooth/BluetoothLeAudioCodecConfig.aidl
new file mode 100644
index 0000000000..e32217eff7
--- /dev/null
+++ b/system/binder/android/bluetooth/BluetoothLeAudioCodecConfig.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.bluetooth;
+
+parcelable BluetoothLeAudioCodecConfig;
diff --git a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
index 22a90a0758..c588802fee 100644
--- a/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
+++ b/system/binder/android/bluetooth/IBluetoothLeAudio.aidl
@@ -18,6 +18,7 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothLeAudioCodecConfig;
import android.bluetooth.BluetoothLeAudioContentMetadata;
import android.bluetooth.IBluetoothLeBroadcastCallback;
import android.content.AttributionSource;
@@ -51,6 +52,10 @@ oneway interface IBluetoothLeAudio {
void getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void getConnectedGroupLeadDevice(int groupId, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void getCodecStatus(in BluetoothDevice device, in AttributionSource source, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void setCodecConfigPreference(in BluetoothDevice device, in BluetoothLeAudioCodecConfig codecConfig, in AttributionSource source);
/* Same value as bluetooth::groups::kGroupUnknown */
const int LE_AUDIO_GROUP_ID_INVALID = -1;
diff --git a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
index 346a10f842..9049260e82 100644
--- a/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
+++ b/system/binder/android/bluetooth/IBluetoothVolumeControl.aidl
@@ -18,6 +18,7 @@
package android.bluetooth;
import android.bluetooth.BluetoothDevice;
+import android.bluetooth.IBluetoothVolumeControlCallback;
import android.content.AttributionSource;
import com.android.modules.utils.SynchronousResultReceiver;
@@ -45,7 +46,14 @@ oneway interface IBluetoothVolumeControl {
void getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+ void isVolumeOffsetAvailable(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void setVolumeOffset(in BluetoothDevice device, int volumeOffset, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
@JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
void setVolumeGroup(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void registerCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
+ @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
+ void unregisterCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
}
diff --git a/system/binder/android/bluetooth/IBluetoothVolumeControlCallback.aidl b/system/binder/android/bluetooth/IBluetoothVolumeControlCallback.aidl
new file mode 100644
index 0000000000..420cde4ac6
--- /dev/null
+++ b/system/binder/android/bluetooth/IBluetoothVolumeControlCallback.aidl
@@ -0,0 +1,30 @@
+/*
+ * Copyright 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.bluetooth;
+
+import android.bluetooth.BluetoothDevice;
+
+import java.util.List;
+
+/**
+ * Callback definitions for interacting with Volume Control service
+ *
+ * @hide
+ */
+oneway interface IBluetoothVolumeControlCallback {
+ void onVolumeOffsetChanged(in BluetoothDevice device, in int volumeOffset);
+}
diff --git a/system/bta/include/bta_vc_api.h b/system/bta/include/bta_vc_api.h
index 8a1370e8c1..b8913ebb21 100644
--- a/system/bta/include/bta_vc_api.h
+++ b/system/bta/include/bta_vc_api.h
@@ -39,4 +39,22 @@ class VolumeControl {
virtual void Disconnect(const RawAddress& address) = 0;
virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
uint8_t volume) = 0;
+ /* Volume Offset Control Service (VOCS) */
+ virtual void SetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset) = 0;
+ virtual void GetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+
+ /* Location as per Bluetooth Assigned Numbers.*/
+ virtual void SetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id,
+ uint32_t location) = 0;
+ virtual void GetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+ virtual void GetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+ virtual void SetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) = 0;
};
diff --git a/system/bta/le_audio/client.cc b/system/bta/le_audio/client.cc
index 569a84459a..54526a2887 100644
--- a/system/bta/le_audio/client.cc
+++ b/system/bta/le_audio/client.cc
@@ -174,8 +174,10 @@ class LeAudioClientImpl : public LeAudioClient {
current_sink_codec_config({0, 0, 0, 0}),
lc3_encoder_left_mem(nullptr),
lc3_encoder_right_mem(nullptr),
- lc3_decoder_mem(nullptr),
- lc3_decoder(nullptr),
+ lc3_decoder_left_mem(nullptr),
+ lc3_decoder_right_mem(nullptr),
+ lc3_decoder_left(nullptr),
+ lc3_decoder_right(nullptr),
audio_source_instance_(nullptr),
audio_sink_instance_(nullptr),
suspend_timeout_(alarm_new("LeAudioSuspendTimeout")) {
@@ -2120,7 +2122,6 @@ class LeAudioClientImpl : public LeAudioClient {
uint32_t timestamp) {
/* Get only one channel for MONO microphone */
/* Gather data for channel */
-
if ((active_group_id_ == bluetooth::groups::kGroupUnknown) ||
(audio_sender_state_ != AudioState::STARTED))
return;
@@ -2133,17 +2134,31 @@ class LeAudioClientImpl : public LeAudioClient {
auto stream_conf = group->stream_conf;
- uint16_t required_for_channel_byte_count =
- stream_conf.source_octets_per_codec_frame;
- size_t required_byte_count = current_sink_codec_config.num_channels *
- required_for_channel_byte_count;
+ uint16_t left_cis_handle = 0;
+ uint16_t right_cis_handle = 0;
+ for (auto [cis_handle, audio_location] : stream_conf.source_streams) {
+ if (audio_location & le_audio::codec_spec_conf::kLeAudioLocationAnyLeft) {
+ left_cis_handle = cis_handle;
+ }
+ if (audio_location &
+ le_audio::codec_spec_conf::kLeAudioLocationAnyRight) {
+ right_cis_handle = cis_handle;
+ }
+ }
- if (required_byte_count != size) {
- LOG(ERROR) << "Insufficient data for decoding and send, required: "
- << int(required_byte_count) << ", received: " << int(size);
+ bool is_left = true;
+ if (cis_conn_hdl == left_cis_handle) {
+ is_left = true;
+ } else if (cis_conn_hdl == right_cis_handle) {
+ is_left = false;
+ } else {
+ LOG_ERROR("Received data for unknown handle: %04x", cis_conn_hdl);
return;
}
+ uint16_t required_for_channel_byte_count =
+ stream_conf.source_octets_per_codec_frame;
+
int dt_us = current_sink_codec_config.data_interval_us;
int af_hz = audio_framework_sink_config.sample_rate;
@@ -2165,19 +2180,146 @@ class LeAudioClientImpl : public LeAudioClient {
std::vector<int16_t> pcm_data_decoded(pcm_size, 0);
- auto err = lc3_decode(lc3_decoder, data, size, pcm_data_decoded.data(),
- 1 /* pitch */);
+ int err = 0;
+
+ if (required_for_channel_byte_count != size) {
+ LOG(INFO) << "Insufficient data for decoding and send, required: "
+ << int(required_for_channel_byte_count)
+ << ", received: " << int(size) << ", will do PLC";
+ size = 0;
+ data = nullptr;
+ }
+
+ lc3_decoder_t decoder_to_use =
+ is_left ? lc3_decoder_left : lc3_decoder_right;
+
+ err = lc3_decode(decoder_to_use, data, size, pcm_data_decoded.data(),
+ 1 /* pitch */);
- /* TODO: How handle failing decoding ? */
if (err < 0) {
- LOG(ERROR) << " error while decoding error code: "
- << static_cast<int>(err);
+ LOG(ERROR) << " bad decoding parameters: " << static_cast<int>(err);
return;
}
- uint16_t to_write = sizeof(int16_t) * pcm_data_decoded.size();
- uint16_t written = LeAudioClientAudioSink::SendData(
- (uint8_t*)pcm_data_decoded.data(), to_write);
+ /* AF == Audio Framework */
+ bool af_is_stereo = (audio_framework_sink_config.num_channels == 2);
+
+ if (!left_cis_handle || !right_cis_handle) {
+ /* mono or just one device connected */
+ SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
+ &pcm_data_decoded, nullptr);
+ return;
+ }
+
+ /* both devices are connected */
+ if (cached_channel_timestamp_ == 0) {
+ /* First packet received, cache it. We need both channel data to send it
+ * to AF. */
+ cached_channel_data_ = pcm_data_decoded;
+ cached_channel_timestamp_ = timestamp;
+ cached_channel_is_left_ = is_left;
+ return;
+ }
+
+ /* We received either data for the other audio channel, or another
+ * packet for same channel */
+
+ if (cached_channel_is_left_ != is_left) {
+ /* It's data for the 2nd channel */
+ if (timestamp == cached_channel_timestamp_) {
+ /* Ready to mix data and send out to AF */
+ if (is_left) {
+ SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo,
+ &cached_channel_data_, &pcm_data_decoded);
+ } else {
+ SendAudioDataToAF(true /* bt_got_stereo */, af_is_stereo,
+ &pcm_data_decoded, &cached_channel_data_);
+ }
+
+ cached_channel_timestamp_ = 0;
+ return;
+ }
+
+ /* 2nd Channel is in the future compared to the cached data.
+ Send the cached data to AF, and keep the new channel data in cache.
+ This should happen only during stream setup */
+
+ if (cached_channel_is_left_) {
+ SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
+ &cached_channel_data_, nullptr);
+ } else {
+ SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr,
+ &cached_channel_data_);
+ }
+
+ cached_channel_data_ = pcm_data_decoded;
+ cached_channel_timestamp_ = timestamp;
+ cached_channel_is_left_ = is_left;
+ return;
+ }
+
+ /* Data for same channel received. 2nd channel is down/not sending
+ * data */
+
+ /* Send the cached data out */
+ if (cached_channel_is_left_) {
+ SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo,
+ &cached_channel_data_, nullptr);
+ } else {
+ SendAudioDataToAF(false /* bt_got_stereo */, af_is_stereo, nullptr,
+ &cached_channel_data_);
+ }
+
+ /* Cache the data in case 2nd channel connects */
+ cached_channel_data_ = pcm_data_decoded;
+ cached_channel_timestamp_ = timestamp;
+ cached_channel_is_left_ = is_left;
+ }
+
+ void SendAudioDataToAF(bool bt_got_stereo, bool af_is_stereo,
+ std::vector<int16_t>* left,
+ std::vector<int16_t>* right) {
+ uint16_t to_write = 0;
+ uint16_t written = 0;
+ if (!bt_got_stereo && !af_is_stereo) {
+ std::vector<int16_t>* mono = left ? left : right;
+ /* mono audio over bluetooth, audio framework expects mono */
+ to_write = sizeof(int16_t) * mono->size();
+ written =
+ LeAudioClientAudioSink::SendData((uint8_t*)mono->data(), to_write);
+ } else if (bt_got_stereo && af_is_stereo) {
+ /* stero audio over bluetooth, audio framework expects stereo */
+ std::vector<uint16_t> mixed(left->size() * 2);
+
+ for (size_t i = 0; i < left->size(); i++) {
+ mixed[2 * i] = (*left)[i];
+ mixed[2 * i + 1] = (*right)[i];
+ }
+ to_write = sizeof(int16_t) * mixed.size();
+ written =
+ LeAudioClientAudioSink::SendData((uint8_t*)mixed.data(), to_write);
+ } else if (bt_got_stereo && !af_is_stereo) {
+ /* stero audio over bluetooth, audio framework expects mono */
+ std::vector<uint16_t> mixed(left->size() * 2);
+
+ for (size_t i = 0; i < left->size(); i++) {
+ (*left)[i] = ((*left)[i] + (*right)[i]) / 2;
+ }
+ to_write = sizeof(int16_t) * left->size();
+ written =
+ LeAudioClientAudioSink::SendData((uint8_t*)left->data(), to_write);
+ } else if (!bt_got_stereo && af_is_stereo) {
+ /* mono audio over bluetooth, audio framework expects stereo */
+ std::vector<uint16_t> mixed(left ? left->size() * 2 : right->size() * 2);
+
+ for (size_t i = 0; i < left->size(); i++) {
+ mixed[2 * i] = left ? (*left)[i] : 0;
+ mixed[2 * i + 1] = right ? (*right)[i] : 0;
+ }
+ to_write = sizeof(int16_t) * mixed.size();
+ written =
+ LeAudioClientAudioSink::SendData((uint8_t*)mixed.data(), to_write);
+ }
/* TODO: What to do if not all data sinked ? */
if (written != to_write) LOG(ERROR) << __func__ << ", not all data sinked";
@@ -2257,22 +2399,29 @@ class LeAudioClientImpl : public LeAudioClient {
uint16_t remote_delay_ms =
group->GetRemoteDelay(le_audio::types::kLeAudioDirectionSource);
+ cached_channel_timestamp_ = 0;
if (CodecManager::GetInstance()->GetCodecLocation() ==
le_audio::types::CodecLocation::HOST) {
- if (lc3_decoder_mem) {
+ if (lc3_decoder_left_mem) {
LOG(WARNING)
<< " The decoder instance should have been already released.";
- free(lc3_decoder_mem);
- lc3_decoder_mem = nullptr;
+ free(lc3_decoder_left_mem);
+ lc3_decoder_left_mem = nullptr;
+ free(lc3_decoder_right_mem);
+ lc3_decoder_right_mem = nullptr;
}
int dt_us = current_sink_codec_config.data_interval_us;
int sr_hz = current_sink_codec_config.sample_rate;
int af_hz = audio_framework_sink_config.sample_rate;
unsigned dec_size = lc3_decoder_size(dt_us, af_hz);
- lc3_decoder_mem = malloc(dec_size);
+ lc3_decoder_left_mem = malloc(dec_size);
+ lc3_decoder_right_mem = malloc(dec_size);
- lc3_decoder = lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_mem);
+ lc3_decoder_left =
+ lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_left_mem);
+ lc3_decoder_right =
+ lc3_setup_decoder(dt_us, sr_hz, af_hz, lc3_decoder_right_mem);
} else if (CodecManager::GetInstance()->GetCodecLocation() ==
le_audio::types::CodecLocation::ADSP) {
CodecManager::GetInstance()->UpdateActiveSinkAudioConfig(*stream_conf,
@@ -2295,10 +2444,11 @@ class LeAudioClientImpl : public LeAudioClient {
lc3_encoder_right_mem = nullptr;
}
- if (lc3_decoder_mem) {
- LOG(INFO) << __func__ << " stopping sink";
- free(lc3_decoder_mem);
- lc3_decoder_mem = nullptr;
+ if (lc3_decoder_left_mem) {
+ free(lc3_decoder_left_mem);
+ lc3_decoder_left_mem = nullptr;
+ free(lc3_decoder_left_mem);
+ lc3_decoder_right_mem = nullptr;
}
}
@@ -3189,8 +3339,11 @@ class LeAudioClientImpl : public LeAudioClient {
lc3_encoder_t lc3_encoder_left;
lc3_encoder_t lc3_encoder_right;
- void* lc3_decoder_mem;
- lc3_decoder_t lc3_decoder;
+ void* lc3_decoder_left_mem;
+ void* lc3_decoder_right_mem;
+
+ lc3_decoder_t lc3_decoder_left;
+ lc3_decoder_t lc3_decoder_right;
std::vector<uint8_t> encoded_data;
const void* audio_source_instance_;
@@ -3200,6 +3353,10 @@ class LeAudioClientImpl : public LeAudioClient {
"persist.bluetooth.leaudio.audio.suspend.timeoutms";
alarm_t* suspend_timeout_;
+ std::vector<int16_t> cached_channel_data_;
+ uint32_t cached_channel_timestamp_ = 0;
+ uint32_t cached_channel_is_left_;
+
void ClientAudioIntefraceRelease() {
if (audio_source_instance_) {
LeAudioClientAudioSource::Stop();
diff --git a/system/bta/vc/device.cc b/system/bta/vc/device.cc
index 7135408209..290a2f7b59 100644
--- a/system/bta/vc/device.cc
+++ b/system/bta/vc/device.cc
@@ -41,6 +41,14 @@ void VolumeControlDevice::Disconnect(tGATT_IF gatt_if) {
BTA_GATTC_DeregisterForNotifications(gatt_if, address,
volume_flags_handle);
+ for (const VolumeOffset& of : audio_offsets.volume_offsets) {
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+ of.audio_descr_handle);
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address,
+ of.audio_location_handle);
+ BTA_GATTC_DeregisterForNotifications(gatt_if, address, of.state_handle);
+ }
+
BtaGattQueue::Clean(connection_id);
BTA_GATTC_Close(connection_id);
connection_id = GATT_INVALID_CONN_ID;
@@ -108,6 +116,50 @@ bool VolumeControlDevice::set_volume_control_service_handles(
return false;
}
+void VolumeControlDevice::set_volume_offset_control_service_handles(
+ const gatt::Service& service) {
+ VolumeOffset offset = VolumeOffset(service.handle);
+
+ for (const gatt::Characteristic& chrc : service.characteristics) {
+ if (chrc.uuid == kVolumeOffsetStateUuid) {
+ offset.state_handle = chrc.value_handle;
+ offset.state_ccc_handle = find_ccc_handle(chrc.value_handle);
+
+ } else if (chrc.uuid == kVolumeOffsetLocationUuid) {
+ offset.audio_location_handle = chrc.value_handle;
+ offset.audio_location_ccc_handle = find_ccc_handle(chrc.value_handle);
+ offset.audio_location_writable =
+ chrc.properties & GATT_CHAR_PROP_BIT_WRITE_NR;
+
+ } else if (chrc.uuid == kVolumeOffsetControlPointUuid) {
+ offset.control_point_handle = chrc.value_handle;
+
+ } else if (chrc.uuid == kVolumeOffsetOutputDescriptionUuid) {
+ offset.audio_descr_handle = chrc.value_handle;
+ offset.audio_descr_ccc_handle = find_ccc_handle(chrc.value_handle);
+ offset.audio_descr_writable =
+ chrc.properties & GATT_CHAR_PROP_BIT_WRITE_NR;
+
+ } else {
+ LOG(WARNING) << __func__ << ": unknown characteristic=" << chrc.uuid;
+ }
+ }
+
+ // Check if all mandatory attributes are present
+ if (GATT_HANDLE_IS_VALID(offset.state_handle) &&
+ GATT_HANDLE_IS_VALID(offset.state_ccc_handle) &&
+ GATT_HANDLE_IS_VALID(offset.audio_location_handle) &&
+ /* audio_location_ccc_handle is optional */
+ GATT_HANDLE_IS_VALID(offset.control_point_handle) &&
+ GATT_HANDLE_IS_VALID(offset.audio_descr_handle)
+ /* audio_descr_ccc_handle is optional */) {
+ audio_offsets.Add(offset);
+ LOG(INFO) << "Offset added id=" << loghex(offset.id);
+ } else {
+ LOG(WARNING) << "Ignoring offset handle=" << loghex(service.handle);
+ }
+}
+
bool VolumeControlDevice::UpdateHandles(void) {
ResetHandles();
@@ -124,6 +176,20 @@ bool VolumeControlDevice::UpdateHandles(void) {
LOG(INFO) << "Found VCS, handle=" << loghex(service.handle);
vcs_found = set_volume_control_service_handles(service);
if (!vcs_found) break;
+
+ for (auto const& included : service.included_services) {
+ const gatt::Service* service =
+ BTA_GATTC_GetOwningService(connection_id, included.start_handle);
+ if (service == nullptr) continue;
+
+ if (included.uuid == kVolumeOffsetUuid) {
+ LOG(INFO) << "Found VOCS, handle=" << loghex(service->handle);
+ set_volume_offset_control_service_handles(*service);
+
+ } else {
+ LOG(WARNING) << __func__ << ": unknown service=" << service->uuid;
+ }
+ }
}
}
@@ -141,6 +207,8 @@ void VolumeControlDevice::ResetHandles(void) {
volume_control_point_handle = 0;
volume_flags_handle = 0;
volume_flags_ccc_handle = 0;
+
+ if (audio_offsets.Size() != 0) audio_offsets.Clear();
}
void VolumeControlDevice::ControlPointOperation(uint8_t opcode,
@@ -194,6 +262,18 @@ bool VolumeControlDevice::EnqueueInitialRequests(
return false;
}
+ for (auto const& offset : audio_offsets.volume_offsets) {
+ handles_pending.insert(offset.state_handle);
+ handles_pending.insert(offset.state_ccc_handle);
+ if (!subscribe_for_notifications(gatt_if, offset.state_handle,
+ offset.state_ccc_handle, cccd_write_cb)) {
+ return false;
+ }
+
+ BtaGattQueue::ReadCharacteristic(connection_id, offset.state_handle,
+ chrc_read_cb, nullptr);
+ }
+
BtaGattQueue::ReadCharacteristic(connection_id, volume_state_handle,
chrc_read_cb, nullptr);
@@ -213,6 +293,12 @@ void VolumeControlDevice::EnqueueRemainingRequests(
{volume_flags_handle, volume_flags_ccc_handle},
};
+ for (auto const& offset : audio_offsets.volume_offsets) {
+ handle_pairs[offset.audio_location_handle] =
+ offset.audio_location_ccc_handle;
+ handle_pairs[offset.audio_descr_handle] = offset.audio_descr_ccc_handle;
+ }
+
for (auto const& handles : handle_pairs) {
if (GATT_HANDLE_IS_VALID(handles.second)) {
subscribe_for_notifications(gatt_if, handles.first, handles.second,
@@ -230,6 +316,101 @@ bool VolumeControlDevice::VerifyReady(uint16_t handle) {
return device_ready;
}
+void VolumeControlDevice::GetExtAudioOutVolumeOffset(uint8_t ext_output_id,
+ GATT_READ_OP_CB cb,
+ void* cb_data) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ BtaGattQueue::ReadCharacteristic(connection_id, offset->state_handle, cb,
+ cb_data);
+}
+
+void VolumeControlDevice::GetExtAudioOutLocation(uint8_t ext_output_id,
+ GATT_READ_OP_CB cb,
+ void* cb_data) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ BtaGattQueue::ReadCharacteristic(connection_id, offset->audio_location_handle,
+ cb, cb_data);
+}
+
+void VolumeControlDevice::SetExtAudioOutLocation(uint8_t ext_output_id,
+ uint32_t location) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ if (!offset->audio_location_writable) {
+ LOG(WARNING) << __func__ << ": not writable";
+ return;
+ }
+
+ std::vector<uint8_t> value(4);
+ uint8_t* ptr = value.data();
+ UINT32_TO_STREAM(ptr, location);
+ BtaGattQueue::WriteCharacteristic(connection_id,
+ offset->audio_location_handle, value,
+ GATT_WRITE_NO_RSP, nullptr, nullptr);
+}
+
+void VolumeControlDevice::GetExtAudioOutDescription(uint8_t ext_output_id,
+ GATT_READ_OP_CB cb,
+ void* cb_data) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ BtaGattQueue::ReadCharacteristic(connection_id, offset->audio_descr_handle,
+ cb, cb_data);
+}
+
+void VolumeControlDevice::SetExtAudioOutDescription(uint8_t ext_output_id,
+ std::string& descr) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ if (!offset->audio_descr_writable) {
+ LOG(WARNING) << __func__ << ": not writable";
+ return;
+ }
+
+ std::vector<uint8_t> value(descr.begin(), descr.end());
+ BtaGattQueue::WriteCharacteristic(connection_id, offset->audio_descr_handle,
+ value, GATT_WRITE_NO_RSP, nullptr, nullptr);
+}
+
+void VolumeControlDevice::ExtAudioOutControlPointOperation(
+ uint8_t ext_output_id, uint8_t opcode, const std::vector<uint8_t>* arg,
+ GATT_WRITE_OP_CB cb, void* cb_data) {
+ VolumeOffset* offset = audio_offsets.FindById(ext_output_id);
+ if (!offset) {
+ LOG(ERROR) << __func__ << ": no such offset!";
+ return;
+ }
+
+ std::vector<uint8_t> set_value({opcode, offset->change_counter});
+ if (arg != nullptr)
+ set_value.insert(set_value.end(), (*arg).begin(), (*arg).end());
+
+ BtaGattQueue::WriteCharacteristic(connection_id, offset->control_point_handle,
+ set_value, GATT_WRITE, cb, cb_data);
+}
+
bool VolumeControlDevice::IsEncryptionEnabled() {
return BTM_IsEncrypted(address, BT_TRANSPORT_LE);
}
diff --git a/system/bta/vc/devices.h b/system/bta/vc/devices.h
index f2ce3014ba..dcf22b0dac 100644
--- a/system/bta/vc/devices.h
+++ b/system/bta/vc/devices.h
@@ -59,6 +59,8 @@ class VolumeControlDevice {
uint16_t volume_flags_handle;
uint16_t volume_flags_ccc_handle;
+ VolumeOffsets audio_offsets;
+
bool device_ready; /* Set when device read server status and registgered for
notifications */
@@ -83,7 +85,25 @@ class VolumeControlDevice {
inline std::string ToString() { return address.ToString(); }
- void DebugDump(int fd) { dprintf(fd, "%s\n", this->ToString().c_str()); }
+ void DebugDump(int fd) {
+ std::stringstream stream;
+ stream << " == device address: " << address << " == \n";
+
+ if (connection_id == GATT_INVALID_CONN_ID)
+ stream << " Not connected\n";
+ else
+ stream << " Connected. Conn_id = " << connection_id << "\n";
+
+ stream << " volume: " << +volume << "\n"
+ << " mute: " << +mute << "\n"
+ << " flags: " << +flags << "\n"
+ << " device read: " << device_ready << "\n"
+ << " first_connection_: " << first_connection << "\n"
+ << " connecting_actively_: " << connecting_actively << "\n";
+
+ dprintf(fd, "%s", stream.str().c_str());
+ audio_offsets.Dump(fd);
+ }
bool IsConnected() { return connection_id != GATT_INVALID_CONN_ID; }
@@ -97,6 +117,17 @@ class VolumeControlDevice {
void ControlPointOperation(uint8_t opcode, const std::vector<uint8_t>* arg,
GATT_WRITE_OP_CB cb, void* cb_data);
+ void GetExtAudioOutVolumeOffset(uint8_t ext_output_id, GATT_READ_OP_CB cb,
+ void* cb_data);
+ void SetExtAudioOutLocation(uint8_t ext_output_id, uint32_t location);
+ void GetExtAudioOutLocation(uint8_t ext_output_id, GATT_READ_OP_CB cb,
+ void* cb_data);
+ void GetExtAudioOutDescription(uint8_t ext_output_id, GATT_READ_OP_CB cb,
+ void* cb_data);
+ void SetExtAudioOutDescription(uint8_t ext_output_id, std::string& descr);
+ void ExtAudioOutControlPointOperation(uint8_t ext_output_id, uint8_t opcode,
+ const std::vector<uint8_t>* arg,
+ GATT_WRITE_OP_CB cb, void* cb_data);
bool IsEncryptionEnabled();
bool EnableEncryption(tBTM_SEC_CALLBACK* callback);
@@ -118,6 +149,7 @@ class VolumeControlDevice {
uint16_t find_ccc_handle(uint16_t chrc_handle);
bool set_volume_control_service_handles(const gatt::Service& service);
+ void set_volume_offset_control_service_handles(const gatt::Service& service);
bool subscribe_for_notifications(tGATT_IF gatt_if, uint16_t handle,
uint16_t ccc_handle, GATT_WRITE_OP_CB cb);
};
@@ -163,8 +195,13 @@ class VolumeControlDevices {
void Clear() { devices_.clear(); }
void DebugDump(int fd) {
- for (auto& device : devices_) {
- device.DebugDump(fd);
+ if (devices_.empty()) {
+ dprintf(fd, " No VC devices:\n");
+ } else {
+ dprintf(fd, " Devices:\n");
+ for (auto& device : devices_) {
+ device.DebugDump(fd);
+ }
}
}
diff --git a/system/bta/vc/devices_test.cc b/system/bta/vc/devices_test.cc
index 691669275d..f19c6e1424 100644
--- a/system/bta/vc/devices_test.cc
+++ b/system/bta/vc/devices_test.cc
@@ -261,6 +261,8 @@ class VolumeControlDeviceTest : public ::testing::Test {
void SetSampleDatabase1(void) {
gatt::DatabaseBuilder builder;
builder.AddService(0x0001, 0x0016, kVolumeControlUuid, true);
+ builder.AddIncludedService(0x0004, kVolumeOffsetUuid, 0x0060, 0x0069);
+ builder.AddIncludedService(0x0005, kVolumeOffsetUuid, 0x0080, 0x008b);
builder.AddCharacteristic(
0x0010, 0x0011, kVolumeControlStateUuid,
GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
@@ -270,6 +272,39 @@ class VolumeControlDeviceTest : public ::testing::Test {
GATT_CHAR_PROP_BIT_WRITE);
builder.AddCharacteristic(0x0015, 0x0016, kVolumeFlagsUuid,
GATT_CHAR_PROP_BIT_READ);
+ builder.AddService(0x0060, 0x0069, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x0061, 0x0062, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0063,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0064, 0x0065, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ);
+ builder.AddCharacteristic(0x0066, 0x0067, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(0x0068, 0x0069,
+ kVolumeOffsetOutputDescriptionUuid,
+ GATT_CHAR_PROP_BIT_READ);
+ builder.AddService(0x0080, 0x008b, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x0081, 0x0082, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0083,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0084, 0x0085, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ |
+ GATT_CHAR_PROP_BIT_WRITE_NR |
+ GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0086,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0087, 0x0088, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(
+ 0x0089, 0x008a, kVolumeOffsetOutputDescriptionUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE_NR |
+ GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x008b,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
builder.AddService(0x00a0, 0x00a3,
Uuid::From16Bit(UUID_SERVCLASS_GATT_SERVER), true);
builder.AddCharacteristic(0x00a1, 0x00a2,
@@ -323,13 +358,104 @@ TEST_F(VolumeControlDeviceTest, test_service_volume_control_incomplete) {
ASSERT_EQ(false, device->HasHandles());
}
+TEST_F(VolumeControlDeviceTest, test_service_vocs_incomplete) {
+ gatt::DatabaseBuilder builder;
+ builder.AddService(0x0001, 0x000a, kVolumeControlUuid, true);
+ builder.AddIncludedService(0x0002, kVolumeOffsetUuid, 0x000b, 0x0013);
+ builder.AddCharacteristic(
+ 0x0003, 0x0004, kVolumeControlStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0005, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0006, 0x0007, kVolumeControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(
+ 0x0008, 0x0009, kVolumeFlagsUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x000a, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddService(0x000b, 0x0013, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x000c, 0x000d, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x000e, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(
+ 0x000f, 0x0010, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0011, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0012, 0x0013, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ /* no Audio Output Description characteristic */
+ services = builder.Build().Services();
+ ASSERT_EQ(true, device->UpdateHandles());
+ ASSERT_EQ((size_t)0, device->audio_offsets.Size());
+ ASSERT_EQ(0x0004, device->volume_state_handle);
+ ASSERT_EQ(0x0005, device->volume_state_ccc_handle);
+ ASSERT_EQ(0x0007, device->volume_control_point_handle);
+ ASSERT_EQ(0x0009, device->volume_flags_handle);
+ ASSERT_EQ(0x000a, device->volume_flags_ccc_handle);
+ ASSERT_EQ(true, device->HasHandles());
+}
+
+TEST_F(VolumeControlDeviceTest, test_service_vocs_found) {
+ gatt::DatabaseBuilder builder;
+ builder.AddService(0x0001, 0x000a, kVolumeControlUuid, true);
+ builder.AddIncludedService(0x0002, kVolumeOffsetUuid, 0x000b, 0x0015);
+ builder.AddCharacteristic(
+ 0x0003, 0x0004, kVolumeControlStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0005, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0006, 0x0007, kVolumeControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(
+ 0x0008, 0x0009, kVolumeFlagsUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x000a, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddService(0x000b, 0x0015, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x000c, 0x000d, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x000e, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(
+ 0x000f, 0x0010, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0011, Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0012, 0x0013, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(0x0014, 0x0015, kVolumeOffsetOutputDescriptionUuid,
+ GATT_CHAR_PROP_BIT_READ);
+ services = builder.Build().Services();
+ ASSERT_EQ(true, device->UpdateHandles());
+ ASSERT_EQ((size_t)1, device->audio_offsets.Size());
+ VolumeOffset* offset = device->audio_offsets.FindByServiceHandle(0x000b);
+ ASSERT_NE(nullptr, offset);
+ ASSERT_EQ(0x000d, offset->state_handle);
+ ASSERT_EQ(0x000e, offset->state_ccc_handle);
+ ASSERT_EQ(0x0010, offset->audio_location_handle);
+ ASSERT_EQ(0x0011, offset->audio_location_ccc_handle);
+ ASSERT_EQ(0x0013, offset->control_point_handle);
+ ASSERT_EQ(0x0015, offset->audio_descr_handle);
+ ASSERT_EQ(0x0000, offset->audio_descr_ccc_handle);
+ ASSERT_EQ(true, device->HasHandles());
+}
+
+TEST_F(VolumeControlDeviceTest, test_multiple_services_found) {
+ SetSampleDatabase1();
+ ASSERT_EQ((size_t)2, device->audio_offsets.Size());
+ VolumeOffset* offset_1 = device->audio_offsets.FindById(1);
+ VolumeOffset* offset_2 = device->audio_offsets.FindById(2);
+ ASSERT_NE(nullptr, offset_1);
+ ASSERT_NE(nullptr, offset_2);
+ ASSERT_NE(offset_1->service_handle, offset_2->service_handle);
+}
+
TEST_F(VolumeControlDeviceTest, test_services_changed) {
SetSampleDatabase1();
+ ASSERT_NE((size_t)0, device->audio_offsets.Size());
ASSERT_NE(0, device->volume_state_handle);
ASSERT_NE(0, device->volume_control_point_handle);
ASSERT_NE(0, device->volume_flags_handle);
ASSERT_EQ(true, device->HasHandles());
SetSampleDatabase2();
+ ASSERT_EQ((size_t)0, device->audio_offsets.Size());
ASSERT_EQ(0, device->volume_state_handle);
ASSERT_EQ(0, device->volume_control_point_handle);
ASSERT_EQ(0, device->volume_flags_handle);
@@ -343,7 +469,9 @@ TEST_F(VolumeControlDeviceTest, test_enqueue_initial_requests) {
std::vector<uint8_t> register_for_notification_data({0x01, 0x00});
std::map<uint16_t, uint16_t> expected_to_read_write{
- {0x0011, 0x0012} /* volume control state */};
+ {0x0011, 0x0012} /* volume control state */,
+ {0x0062, 0x0063} /* volume offset state 1 */,
+ {0x0082, 0x0083} /* volume offset state 2 */};
for (auto const& handle_pair : expected_to_read_write) {
EXPECT_CALL(gatt_queue, ReadCharacteristic(_, handle_pair.first, _, _));
@@ -408,9 +536,15 @@ TEST_F(VolumeControlDeviceTest, test_enqueue_remaining_requests) {
tGATT_IF gatt_if = 0x0001;
std::vector<uint8_t> register_for_notification_data({0x01, 0x00});
- std::vector<uint16_t> expected_to_read{0x0016 /* volume flags */};
+ std::vector<uint16_t> expected_to_read{
+ 0x0016 /* volume flags */, 0x0065 /* audio location 1 */,
+ 0x0069 /* audio output description 1 */, 0x0085 /* audio location 1 */,
+ 0x008a /* audio output description 1 */};
- std::map<uint16_t, uint16_t> expected_to_write_value_ccc_handle_map{};
+ std::map<uint16_t, uint16_t> expected_to_write_value_ccc_handle_map{
+ {0x0085, 0x0086} /* audio location ccc 2 */,
+ {0x008a, 0x008b} /* audio output description ccc */
+ };
for (uint16_t handle : expected_to_read) {
EXPECT_CALL(gatt_queue, ReadCharacteristic(_, handle, _, _));
@@ -467,6 +601,96 @@ TEST_F(VolumeControlDeviceTest, test_control_point_operation_arg) {
device->ControlPointOperation(0x01, &arg, write_cb, nullptr);
}
+TEST_F(VolumeControlDeviceTest, test_get_ext_audio_out_volume_offset) {
+ GATT_READ_OP_CB read_cb = [](uint16_t conn_id, tGATT_STATUS status,
+ uint16_t handle, uint16_t len, uint8_t* value,
+ void* data) {};
+ SetSampleDatabase1();
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(_, 0x0062, read_cb, nullptr));
+ device->GetExtAudioOutVolumeOffset(1, read_cb, nullptr);
+}
+
+TEST_F(VolumeControlDeviceTest, test_get_ext_audio_out_location) {
+ GATT_READ_OP_CB read_cb = [](uint16_t conn_id, tGATT_STATUS status,
+ uint16_t handle, uint16_t len, uint8_t* value,
+ void* data) {};
+ SetSampleDatabase1();
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(_, 0x0085, read_cb, nullptr));
+ device->GetExtAudioOutLocation(2, read_cb, nullptr);
+}
+
+TEST_F(VolumeControlDeviceTest, test_set_ext_audio_out_location) {
+ SetSampleDatabase1();
+ std::vector<uint8_t> expected_data({0x44, 0x33, 0x22, 0x11});
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(_, 0x0085, expected_data, GATT_WRITE_NO_RSP,
+ nullptr, nullptr));
+ device->SetExtAudioOutLocation(2, 0x11223344);
+}
+
+TEST_F(VolumeControlDeviceTest, test_set_ext_audio_out_location_non_writable) {
+ SetSampleDatabase1();
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+ device->SetExtAudioOutLocation(1, 0x11223344);
+}
+
+TEST_F(VolumeControlDeviceTest, test_get_ext_audio_out_description) {
+ GATT_READ_OP_CB read_cb = [](uint16_t conn_id, tGATT_STATUS status,
+ uint16_t handle, uint16_t len, uint8_t* value,
+ void* data) {};
+ SetSampleDatabase1();
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(_, 0x008a, read_cb, nullptr));
+ device->GetExtAudioOutDescription(2, read_cb, nullptr);
+}
+
+TEST_F(VolumeControlDeviceTest, test_set_ext_audio_out_description) {
+ SetSampleDatabase1();
+ std::string descr = "right front";
+ std::vector<uint8_t> expected_data(descr.begin(), descr.end());
+ EXPECT_CALL(gatt_queue,
+ WriteCharacteristic(_, 0x008a, expected_data, GATT_WRITE_NO_RSP,
+ nullptr, nullptr));
+ device->SetExtAudioOutDescription(2, descr);
+}
+
+TEST_F(VolumeControlDeviceTest,
+ test_set_ext_audio_out_description_non_writable) {
+ SetSampleDatabase1();
+ std::string descr = "left front";
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+ device->SetExtAudioOutDescription(1, descr);
+}
+
+TEST_F(VolumeControlDeviceTest, test_ext_audio_out_control_point_operation) {
+ GATT_WRITE_OP_CB write_cb = [](uint16_t conn_id, tGATT_STATUS status,
+ uint16_t handle, uint16_t len,
+ const uint8_t* value, void* data) {};
+ SetSampleDatabase1();
+ VolumeOffset* offset = device->audio_offsets.FindById(1);
+ ASSERT_NE(nullptr, offset);
+ offset->change_counter = 0x09;
+ std::vector<uint8_t> expected_data({0x0b, 0x09});
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, 0x0067, expected_data,
+ GATT_WRITE, write_cb, nullptr));
+ device->ExtAudioOutControlPointOperation(1, 0x0b, nullptr, write_cb, nullptr);
+}
+
+TEST_F(VolumeControlDeviceTest,
+ test_ext_audio_out_control_point_operation_arg) {
+ GATT_WRITE_OP_CB write_cb = [](uint16_t conn_id, tGATT_STATUS status,
+ uint16_t handle, uint16_t len,
+ const uint8_t* value, void* data) {};
+ SetSampleDatabase1();
+ VolumeOffset* offset = device->audio_offsets.FindById(1);
+ ASSERT_NE(nullptr, offset);
+ offset->change_counter = 0x09;
+ std::vector<uint8_t> expected_data({0x0b, 0x09, 0x01, 0x02, 0x03, 0x04});
+ std::vector<uint8_t> arg({0x01, 0x02, 0x03, 0x04});
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, 0x0067, expected_data,
+ GATT_WRITE, write_cb, nullptr));
+ device->ExtAudioOutControlPointOperation(1, 0x0b, &arg, write_cb, nullptr);
+}
+
} // namespace internal
} // namespace vc
} // namespace bluetooth
diff --git a/system/bta/vc/types.h b/system/bta/vc/types.h
index a8a4b252c8..6a2cd753ad 100644
--- a/system/bta/vc/types.h
+++ b/system/bta/vc/types.h
@@ -39,10 +39,26 @@ static constexpr uint8_t kControlPointOpcodeSetAbsoluteVolume = 0x04;
static constexpr uint8_t kControlPointOpcodeUnmute = 0x05;
static constexpr uint8_t kControlPointOpcodeMute = 0x06;
+/* Volume offset control point opcodes */
+static constexpr uint8_t kVolumeOffsetControlPointOpcodeSet = 0x01;
+
+/* Volume input control point opcodes */
+static constexpr uint8_t kVolumeInputControlPointOpcodeSetGain = 0x01;
+static constexpr uint8_t kVolumeInputControlPointOpcodeUnmute = 0x02;
+static constexpr uint8_t kVolumeInputControlPointOpcodeMute = 0x03;
+static constexpr uint8_t kVolumeInputControlPointOpcodeSetManualGainMode = 0x04;
+static constexpr uint8_t kVolumeInputControlPointOpcodeSetAutoGainMode = 0x05;
+
static const Uuid kVolumeControlUuid = Uuid::From16Bit(0x1844);
static const Uuid kVolumeControlStateUuid = Uuid::From16Bit(0x2B7D);
static const Uuid kVolumeControlPointUuid = Uuid::From16Bit(0x2B7E);
static const Uuid kVolumeFlagsUuid = Uuid::From16Bit(0x2B7F);
+
+static const Uuid kVolumeOffsetUuid = Uuid::From16Bit(0x1845);
+static const Uuid kVolumeOffsetStateUuid = Uuid::From16Bit(0x2B80);
+static const Uuid kVolumeOffsetLocationUuid = Uuid::From16Bit(0x2B81);
+static const Uuid kVolumeOffsetControlPointUuid = Uuid::From16Bit(0x2B82);
+static const Uuid kVolumeOffsetOutputDescriptionUuid = Uuid::From16Bit(0x2B83);
/* clang-format on */
struct VolumeOperation {
@@ -88,6 +104,98 @@ struct VolumeOperation {
void Start(void) { started_ = true; }
};
+struct VolumeOffset {
+ uint8_t id;
+ uint8_t change_counter;
+ int16_t offset;
+ uint32_t location;
+ uint16_t service_handle;
+ uint16_t state_handle;
+ uint16_t state_ccc_handle;
+ uint16_t audio_location_handle;
+ uint16_t audio_location_ccc_handle;
+ uint16_t audio_descr_handle;
+ uint16_t audio_descr_ccc_handle;
+ uint16_t control_point_handle;
+ bool audio_location_writable;
+ bool audio_descr_writable;
+
+ VolumeOffset(uint16_t service_handle)
+ : id(0),
+ change_counter(0),
+ offset(0),
+ location(0),
+ service_handle(service_handle),
+ state_handle(0),
+ state_ccc_handle(0),
+ audio_location_handle(0),
+ audio_location_ccc_handle(0),
+ audio_descr_handle(0),
+ audio_descr_ccc_handle(0),
+ control_point_handle(0),
+ audio_location_writable(false),
+ audio_descr_writable(false) {}
+};
+
+class VolumeOffsets {
+ public:
+ void Add(VolumeOffset& offset) {
+ offset.id = (uint8_t)Size() + 1;
+ volume_offsets.push_back(offset);
+ }
+
+ VolumeOffset* FindByLocation(uint8_t location) {
+ auto iter = std::find_if(volume_offsets.begin(), volume_offsets.end(),
+ [&location](const VolumeOffset& item) {
+ return item.location == location;
+ });
+
+ return (iter == volume_offsets.end()) ? nullptr : &(*iter);
+ }
+
+ VolumeOffset* FindByServiceHandle(uint16_t service_handle) {
+ auto iter = std::find_if(volume_offsets.begin(), volume_offsets.end(),
+ [&service_handle](const VolumeOffset& item) {
+ return item.service_handle == service_handle;
+ });
+
+ return (iter == volume_offsets.end()) ? nullptr : &(*iter);
+ }
+
+ VolumeOffset* FindById(uint16_t id) {
+ auto iter =
+ std::find_if(volume_offsets.begin(), volume_offsets.end(),
+ [&id](const VolumeOffset& item) { return item.id == id; });
+
+ return (iter == volume_offsets.end()) ? nullptr : &(*iter);
+ }
+
+ void Clear() { volume_offsets.clear(); }
+
+ size_t Size() { return volume_offsets.size(); }
+
+ void Dump(int fd) {
+ std::stringstream stream;
+ int n = Size();
+ stream << " == number of offsets: " << n << " == \n";
+
+ for (int i = 0; i < n; i++) {
+ auto v = volume_offsets[i];
+ stream << " id: " << +v.id << "\n"
+ << " offset: " << +v.offset << "\n"
+ << " changeCnt: " << +v.change_counter << "\n"
+ << " location: " << +v.location << "\n"
+ << " service_handle: " << +v.service_handle << "\n"
+ << " audio_location_writable " << v.audio_location_writable
+ << "\n"
+ << " audio_descr_writable: " << v.audio_descr_writable << "\n";
+ }
+ dprintf(fd, "%s", stream.str().c_str());
+ }
+
+ std::vector<VolumeOffset> volume_offsets;
+};
+
} // namespace internal
} // namespace vc
} // namespace bluetooth
diff --git a/system/bta/vc/vc.cc b/system/bta/vc/vc.cc
index 23a20bf8c8..4ecbb8e9c9 100644
--- a/system/bta/vc/vc.cc
+++ b/system/bta/vc/vc.cc
@@ -18,6 +18,7 @@
#include <base/bind.h>
#include <base/logging.h>
#include <base/strings/string_number_conversions.h>
+#include <base/strings/string_util.h>
#include <hardware/bt_vc.h>
#include <string>
@@ -52,7 +53,9 @@ VolumeControlImpl* instance;
* Profile (VCP)
*
* Each connected peer device supporting Volume Control Service (VCS) is on the
- * list of devices (volume_control_devices_).
+ * list of devices (volume_control_devices_). When VCS is discovered on the peer
+ * device, Android does search for all the instances Volume Offset Service
+ * (VOCS). Note that AIS and VOCS are optional.
*
* Once all the mandatory characteristis for all the services are discovered,
* Fluoride calls ON_CONNECTED callback.
@@ -61,7 +64,13 @@ VolumeControlImpl* instance;
* profile e.g. Volume up/down, mute/unmute etc, profile configures all the
* devices which are active Le Audio devices.
*
- *
+ * Peer devices has at maximum one instance of VCS and 0 or more instance of
+ * VOCS. Android gets access to External Audio Outputs using appropriate ID.
+ * Also each of the External Device has description
+ * characteristic and Type which gives the application hint what it is a device.
+ * Examples of such devices:
+ * External Output: 1 instance to controller ballance between set of devices
+ * External Output: each of 5.1 speaker set etc.
*/
class VolumeControlImpl : public VolumeControl {
public:
@@ -254,6 +263,27 @@ class VolumeControlImpl : public VolumeControl {
return;
}
+ const gatt::Service* service = BTA_GATTC_GetOwningService(conn_id, handle);
+ if (service == nullptr) return;
+
+ VolumeOffset* offset =
+ device->audio_offsets.FindByServiceHandle(service->handle);
+ if (offset != nullptr) {
+ if (handle == offset->state_handle) {
+ OnExtAudioOutStateChanged(device, offset, len, value);
+ } else if (handle == offset->audio_location_handle) {
+ OnExtAudioOutLocationChanged(device, offset, len, value);
+ } else if (handle == offset->audio_descr_handle) {
+ OnOffsetOutputDescChanged(device, offset, len, value);
+ } else {
+ LOG(ERROR) << __func__ << ": unknown offset handle=" << loghex(handle);
+ return;
+ }
+
+ verify_device_ready(device, handle);
+ return;
+ }
+
LOG(ERROR) << __func__ << ": unknown handle=" << loghex(handle);
}
@@ -405,6 +435,81 @@ class VolumeControlImpl : public VolumeControl {
LOG(INFO) << __func__ << " flags " << loghex(device->flags);
}
+ void OnExtAudioOutStateChanged(VolumeControlDevice* device,
+ VolumeOffset* offset, uint16_t len,
+ uint8_t* value) {
+ if (len != 3) {
+ LOG(INFO) << __func__ << ": malformed len=" << loghex(len);
+ return;
+ }
+
+ uint8_t* pp = value;
+ STREAM_TO_UINT16(offset->offset, pp);
+ STREAM_TO_UINT8(offset->change_counter, pp);
+
+ LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
+ LOG(INFO) << __func__ << " id: " << loghex(offset->id)
+ << " offset: " << loghex(offset->offset)
+ << " counter: " << loghex(offset->change_counter);
+
+ if (!device->device_ready) return;
+
+ callbacks_->OnExtAudioOutVolumeOffsetChanged(device->address, offset->id,
+ offset->offset);
+ }
+
+ void OnExtAudioOutLocationChanged(VolumeControlDevice* device,
+ VolumeOffset* offset, uint16_t len,
+ uint8_t* value) {
+ if (len != 4) {
+ LOG(INFO) << __func__ << ": malformed len=" << loghex(len);
+ return;
+ }
+
+ uint8_t* pp = value;
+ STREAM_TO_UINT32(offset->location, pp);
+
+ LOG(INFO) << __func__ << " " << base::HexEncode(value, len);
+ LOG(INFO) << __func__ << "id " << loghex(offset->id) << "location "
+ << loghex(offset->location);
+
+ if (!device->device_ready) return;
+
+ callbacks_->OnExtAudioOutLocationChanged(device->address, offset->id,
+ offset->location);
+ }
+
+ void OnExtAudioOutCPWrite(uint16_t connection_id, tGATT_STATUS status,
+ uint16_t handle, void* /*data*/) {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByConnId(connection_id);
+ if (!device) {
+ LOG(ERROR) << __func__
+ << "Skipping unknown device disconnect, connection_id="
+ << loghex(connection_id);
+ return;
+ }
+
+ LOG(INFO) << "Offset Control Point write response handle" << loghex(handle)
+ << " status: " << loghex((int)(status));
+
+ /* TODO Design callback API to notify about changes */
+ }
+
+ void OnOffsetOutputDescChanged(VolumeControlDevice* device,
+ VolumeOffset* offset, uint16_t len,
+ uint8_t* value) {
+ std::string description = std::string(value, value + len);
+ if (!base::IsStringUTF8(description)) description = "<invalid utf8 string>";
+
+ LOG(INFO) << __func__ << " " << description;
+
+ if (!device->device_ready) return;
+
+ callbacks_->OnExtAudioOutDescriptionChanged(device->address, offset->id,
+ std::move(description));
+ }
+
void OnGattWriteCcc(uint16_t connection_id, tGATT_STATUS status,
uint16_t handle, uint16_t len, const uint8_t* value,
void* /*data*/) {
@@ -598,12 +703,12 @@ class VolumeControlImpl : public VolumeControl {
uint8_t opcode,
std::vector<uint8_t>& arguments) {
DLOG(INFO) << __func__ << " num of devices: " << devices.size()
- << " group_id: " << group_id << " is_autonomous: " << is_autonomous
- << " opcode: " << +opcode
+ << " group_id: " << group_id
+ << " is_autonomous: " << is_autonomous << " opcode: " << +opcode
<< " arg size: " << arguments.size();
- ongoing_operations_.emplace_back(latest_operation_id_++, group_id, is_autonomous,
- opcode, arguments, devices);
+ ongoing_operations_.emplace_back(latest_operation_id_++, group_id,
+ is_autonomous, opcode, arguments, devices);
}
void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
@@ -618,8 +723,8 @@ class VolumeControlImpl : public VolumeControl {
std::vector<RawAddress> devices = {
std::get<RawAddress>(addr_or_group_id)};
- PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false,
- opcode, arg);
+ PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown,
+ false, opcode, arg);
} else {
/* Handle group change */
auto group_id = std::get<int>(addr_or_group_id);
@@ -652,6 +757,81 @@ class VolumeControlImpl : public VolumeControl {
StartQueueOperation();
}
+ /* Methods to operate on Volume Control Offset Service (VOCS) */
+ void GetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+
+ device->GetExtAudioOutVolumeOffset(ext_output_id, chrc_read_callback_static,
+ nullptr);
+ }
+
+ void SetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset_val) override {
+ std::vector<uint8_t> arg(2);
+ uint8_t* ptr = arg.data();
+ UINT16_TO_STREAM(ptr, offset_val);
+ ext_audio_out_control_point_helper(
+ address, ext_output_id, kVolumeOffsetControlPointOpcodeSet, &arg);
+ }
+
+ void GetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+
+ device->GetExtAudioOutLocation(ext_output_id, chrc_read_callback_static,
+ nullptr);
+ }
+
+ void SetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id,
+ uint32_t location) override {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+
+ device->SetExtAudioOutLocation(ext_output_id, location);
+ }
+
+ void GetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+
+ device->GetExtAudioOutDescription(ext_output_id, chrc_read_callback_static,
+ nullptr);
+ }
+
+ void SetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) override {
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+
+ device->SetExtAudioOutDescription(ext_output_id, descr);
+ }
+
void CleanUp() {
LOG(INFO) << __func__;
volume_control_devices_.Disconnect(gatt_if_);
@@ -677,6 +857,8 @@ class VolumeControlImpl : public VolumeControl {
if (device->VerifyReady(handle)) {
LOG(INFO) << __func__ << " Outstanding reads completed.";
+ callbacks_->OnDeviceAvailable(device->address,
+ device->audio_offsets.Size());
callbacks_->OnConnectionState(ConnectionState::CONNECTED,
device->address);
@@ -688,6 +870,11 @@ class VolumeControlImpl : public VolumeControl {
callbacks_->OnVolumeStateChanged(device->address, device->volume,
device->mute, false);
+ for (auto const& offset : device->audio_offsets.volume_offsets) {
+ callbacks_->OnExtAudioOutVolumeOffsetChanged(device->address, offset.id,
+ offset.offset);
+ }
+
device->EnqueueRemainingRequests(gatt_if_, chrc_read_callback_static,
OnGattWriteCccStatic);
}
@@ -716,6 +903,27 @@ class VolumeControlImpl : public VolumeControl {
INT_TO_PTR(operation_id));
}
+ void ext_audio_out_control_point_helper(const RawAddress& address,
+ uint8_t ext_output_id, uint8_t opcode,
+ const std::vector<uint8_t>* arg) {
+ LOG(INFO) << __func__ << ": " << address.ToString()
+ << " id=" << loghex(ext_output_id) << " op=" << loghex(opcode);
+ VolumeControlDevice* device =
+ volume_control_devices_.FindByAddress(address);
+ if (!device) {
+ LOG(ERROR) << __func__ << ", no such device!";
+ return;
+ }
+ device->ExtAudioOutControlPointOperation(
+ ext_output_id, opcode, arg,
+ [](uint16_t connection_id, tGATT_STATUS status, uint16_t handle,
+ uint16_t len, const uint8_t* value, void* data) {
+ if (instance)
+ instance->OnExtAudioOutCPWrite(connection_id, status, handle, data);
+ },
+ nullptr);
+ }
+
void gattc_callback(tBTA_GATTC_EVT event, tBTA_GATTC* p_data) {
LOG(INFO) << __func__ << " event = " << static_cast<int>(event);
diff --git a/system/bta/vc/vc_test.cc b/system/bta/vc/vc_test.cc
index 0e52d4a1be..e90ceee4f5 100644
--- a/system/bta/vc/vc_test.cc
+++ b/system/bta/vc/vc_test.cc
@@ -74,11 +74,25 @@ class MockVolumeControlCallbacks : public VolumeControlCallbacks {
MOCK_METHOD((void), OnConnectionState,
(ConnectionState state, const RawAddress& address), (override));
+ MOCK_METHOD((void), OnDeviceAvailable,
+ (const RawAddress& address, uint8_t num_offset), (override));
MOCK_METHOD((void), OnVolumeStateChanged,
(const RawAddress& address, uint8_t volume, bool mute, bool isAutonomous),
(override));
MOCK_METHOD((void), OnGroupVolumeStateChanged,
(int group_id, uint8_t volume, bool mute, bool isAutonomous), (override));
+ MOCK_METHOD((void), OnExtAudioOutVolumeOffsetChanged,
+ (const RawAddress& address, uint8_t ext_output_id,
+ int16_t offset),
+ (override));
+ MOCK_METHOD((void), OnExtAudioOutLocationChanged,
+ (const RawAddress& address, uint8_t ext_output_id,
+ uint32_t location),
+ (override));
+ MOCK_METHOD((void), OnExtAudioOutDescriptionChanged,
+ (const RawAddress& address, uint8_t ext_output_id,
+ std::string descr),
+ (override));
};
class VolumeControlTest : public ::testing::Test {
@@ -98,7 +112,8 @@ class VolumeControlTest : public ::testing::Test {
/* TODO Place holder */
}
if (vocs) {
- /* TODO Place holder */
+ builder.AddIncludedService(0x0013, kVolumeOffsetUuid, 0x0070, 0x0079);
+ builder.AddIncludedService(0x0014, kVolumeOffsetUuid, 0x0080, 0x008b);
}
/* 0x0015-0x001f RFU */
builder.AddCharacteristic(
@@ -117,7 +132,45 @@ class VolumeControlTest : public ::testing::Test {
/* TODO Place holder for AICS */
}
if (vocs) {
- /* TODO Place holder for VOCS */
+ /* VOCS 1st instance */
+ builder.AddService(0x0070, 0x0079, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x0071, 0x0072, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0073,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ builder.AddCharacteristic(0x0074, 0x0075, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ);
+ builder.AddCharacteristic(0x0076, 0x0077, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(0x0078, 0x0079,
+ kVolumeOffsetOutputDescriptionUuid,
+ GATT_CHAR_PROP_BIT_READ);
+ /* 0x007a-0x007f RFU */
+
+ /* VOCS 2nd instance */
+ builder.AddService(0x0080, 0x008b, kVolumeOffsetUuid, false);
+ builder.AddCharacteristic(
+ 0x0081, 0x0082, kVolumeOffsetStateUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0083,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ if (!vocs_broken) {
+ builder.AddCharacteristic(0x0084, 0x0085, kVolumeOffsetLocationUuid,
+ GATT_CHAR_PROP_BIT_READ |
+ GATT_CHAR_PROP_BIT_WRITE_NR |
+ GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x0086,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
+ }
+ builder.AddCharacteristic(0x0087, 0x0088, kVolumeOffsetControlPointUuid,
+ GATT_CHAR_PROP_BIT_WRITE);
+ builder.AddCharacteristic(
+ 0x0089, 0x008a, kVolumeOffsetOutputDescriptionUuid,
+ GATT_CHAR_PROP_BIT_READ | GATT_CHAR_PROP_BIT_WRITE_NR |
+ GATT_CHAR_PROP_BIT_NOTIFY);
+ builder.AddDescriptor(0x008b,
+ Uuid::From16Bit(GATT_UUID_CHAR_CLIENT_CONFIG));
}
}
/* 0x008c-0x008f RFU */
@@ -153,6 +206,24 @@ class VolumeControlTest : public ::testing::Test {
value.resize(1);
break;
+ case 0x0072: // 1st VOCS instance
+ case 0x0082: // 2nd VOCS instance
+ /* offset state */
+ value.resize(3);
+ break;
+
+ case 0x0075: // 1st VOCS instance
+ case 0x0085: // 2nd VOCS instance
+ /* offset location */
+ value.resize(4);
+ break;
+
+ case 0x0079: // 1st VOCS instance
+ case 0x008a: // 2nd VOCS instance
+ /* offset output description */
+ value.resize(10);
+ break;
+
default:
ASSERT_TRUE(false);
return;
@@ -392,6 +463,14 @@ class VolumeControlTest : public ::testing::Test {
set_sample_database(conn_id, true, true, true, false, true, false);
}
+ void SetSampleDatabaseVOCS(uint16_t conn_id) {
+ set_sample_database(conn_id, true, false, false, false, true, false);
+ }
+
+ void SetSampleDatabaseVOCSBroken(uint16_t conn_id) {
+ set_sample_database(conn_id, true, false, true, false, true, true);
+ }
+
void SetSampleDatabase(uint16_t conn_id) {
set_sample_database(conn_id, true, false, true, false, true, false);
}
@@ -519,6 +598,7 @@ TEST_F(VolumeControlTest, test_discovery_vcs_found) {
SetSampleDatabaseVCS(1);
TestAppRegister();
TestConnect(test_address);
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, _));
EXPECT_CALL(*callbacks,
OnConnectionState(ConnectionState::CONNECTED, test_address));
GetConnectedEvent(test_address, 1);
@@ -559,6 +639,21 @@ TEST_F(VolumeControlTest, test_subscribe_vcs_volume_state) {
TestSubscribeNotifications(GetTestAddress(0), 1, handles);
}
+TEST_F(VolumeControlTest, test_subscribe_vocs_offset_state) {
+ std::map<uint16_t, uint16_t> handles({{0x0072, 0x0073}, {0x0082, 0x0083}});
+ TestSubscribeNotifications(GetTestAddress(0), 1, handles);
+}
+
+TEST_F(VolumeControlTest, test_subscribe_vocs_offset_location) {
+ std::map<uint16_t, uint16_t> handles({{0x0085, 0x0086}});
+ TestSubscribeNotifications(GetTestAddress(0), 1, handles);
+}
+
+TEST_F(VolumeControlTest, test_subscribe_vocs_output_description) {
+ std::map<uint16_t, uint16_t> handles({{0x008a, 0x008b}});
+ TestSubscribeNotifications(GetTestAddress(0), 1, handles);
+}
+
TEST_F(VolumeControlTest, test_read_vcs_volume_state) {
const RawAddress test_address = GetTestAddress(0);
EXPECT_CALL(*callbacks, OnVolumeStateChanged(test_address, _, _, false));
@@ -571,6 +666,72 @@ TEST_F(VolumeControlTest, test_read_vcs_volume_flags) {
TestReadCharacteristic(GetTestAddress(0), 1, handles);
}
+TEST_F(VolumeControlTest, test_read_vocs_volume_offset) {
+ const RawAddress test_address = GetTestAddress(0);
+ EXPECT_CALL(*callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 1, _));
+ EXPECT_CALL(*callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _));
+ std::vector<uint16_t> handles({0x0072, 0x0082});
+ TestReadCharacteristic(test_address, 1, handles);
+}
+
+TEST_F(VolumeControlTest, test_read_vocs_offset_location) {
+ const RawAddress test_address = GetTestAddress(0);
+ EXPECT_CALL(*callbacks, OnExtAudioOutLocationChanged(test_address, 1, _));
+ EXPECT_CALL(*callbacks, OnExtAudioOutLocationChanged(test_address, 2, _));
+ std::vector<uint16_t> handles({0x0075, 0x0085});
+ TestReadCharacteristic(test_address, 1, handles);
+}
+
+TEST_F(VolumeControlTest, test_read_vocs_output_description) {
+ const RawAddress test_address = GetTestAddress(0);
+ EXPECT_CALL(*callbacks, OnExtAudioOutDescriptionChanged(test_address, 1, _));
+ EXPECT_CALL(*callbacks, OnExtAudioOutDescriptionChanged(test_address, 2, _));
+ std::vector<uint16_t> handles({0x0079, 0x008a});
+ TestReadCharacteristic(test_address, 1, handles);
+}
+
+TEST_F(VolumeControlTest, test_discovery_vocs_found) {
+ const RawAddress test_address = GetTestAddress(0);
+ SetSampleDatabaseVOCS(1);
+ TestAppRegister();
+ TestConnect(test_address);
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 2));
+ GetConnectedEvent(test_address, 1);
+ GetSearchCompleteEvent(1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+ TestAppUnregister();
+}
+
+TEST_F(VolumeControlTest, test_discovery_vocs_not_found) {
+ const RawAddress test_address = GetTestAddress(0);
+ SetSampleDatabaseVCS(1);
+ TestAppRegister();
+ TestConnect(test_address);
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 0));
+ GetConnectedEvent(test_address, 1);
+ GetSearchCompleteEvent(1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+ TestAppUnregister();
+}
+
+TEST_F(VolumeControlTest, test_discovery_vocs_broken) {
+ const RawAddress test_address = GetTestAddress(0);
+ SetSampleDatabaseVOCSBroken(1);
+ TestAppRegister();
+ TestConnect(test_address);
+ EXPECT_CALL(*callbacks,
+ OnConnectionState(ConnectionState::CONNECTED, test_address));
+ EXPECT_CALL(*callbacks, OnDeviceAvailable(test_address, 1));
+ GetConnectedEvent(test_address, 1);
+ GetSearchCompleteEvent(1);
+ Mock::VerifyAndClearExpectations(callbacks.get());
+ TestAppUnregister();
+}
+
class VolumeControlCallbackTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
@@ -618,6 +779,106 @@ TEST_F(VolumeControlCallbackTest, test_volume_state_changed_malformed) {
GetNotificationEvent(0x0021, too_long);
}
+TEST_F(VolumeControlCallbackTest, test_volume_offset_changed) {
+ std::vector<uint8_t> value({0x04, 0x05, 0x06});
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutVolumeOffsetChanged(test_address, 2, 0x0504));
+ GetNotificationEvent(0x0082, value);
+}
+
+TEST_F(VolumeControlCallbackTest, test_volume_offset_changed_malformed) {
+ EXPECT_CALL(*callbacks, OnExtAudioOutVolumeOffsetChanged(test_address, 2, _))
+ .Times(0);
+ std::vector<uint8_t> too_short({0x04});
+ GetNotificationEvent(0x0082, too_short);
+ std::vector<uint8_t> too_long({0x04, 0x05, 0x06, 0x07});
+ GetNotificationEvent(0x0082, too_long);
+}
+
+TEST_F(VolumeControlCallbackTest, test_offset_location_changed) {
+ std::vector<uint8_t> value({0x01, 0x02, 0x03, 0x04});
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutLocationChanged(test_address, 2, 0x04030201));
+ GetNotificationEvent(0x0085, value);
+}
+
+TEST_F(VolumeControlCallbackTest, test_offset_location_changed_malformed) {
+ EXPECT_CALL(*callbacks, OnExtAudioOutLocationChanged(test_address, 2, _))
+ .Times(0);
+ std::vector<uint8_t> too_short({0x04});
+ GetNotificationEvent(0x0085, too_short);
+ std::vector<uint8_t> too_long({0x04, 0x05, 0x06});
+ GetNotificationEvent(0x0085, too_long);
+}
+
+TEST_F(VolumeControlCallbackTest, test_audio_output_description_changed) {
+ std::string descr = "left";
+ std::vector<uint8_t> value(descr.begin(), descr.end());
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutDescriptionChanged(test_address, 2, descr));
+ GetNotificationEvent(0x008a, value);
+}
+
+class VolumeControlValueGetTest : public VolumeControlTest {
+ protected:
+ const RawAddress test_address = GetTestAddress(0);
+ uint16_t conn_id = 22;
+ GATT_READ_OP_CB cb;
+ void* cb_data;
+ uint16_t handle;
+
+ void SetUp(void) override {
+ VolumeControlTest::SetUp();
+ SetSampleDatabase(conn_id);
+ TestAppRegister();
+ TestConnect(test_address);
+ GetConnectedEvent(test_address, conn_id);
+ GetSearchCompleteEvent(conn_id);
+ EXPECT_CALL(gatt_queue, ReadCharacteristic(conn_id, _, _, _))
+ .WillOnce(
+ DoAll(SaveArg<1>(&handle), SaveArg<2>(&cb), SaveArg<3>(&cb_data)));
+ }
+
+ void TearDown(void) override {
+ TestAppUnregister();
+ cb = nullptr;
+ cb_data = nullptr;
+ handle = 0;
+ VolumeControlTest::TearDown();
+ }
+};
+
+TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_volume_offset) {
+ VolumeControl::Get()->GetExtAudioOutVolumeOffset(test_address, 1);
+ EXPECT_TRUE(cb);
+ std::vector<uint8_t> value({0x01, 0x02, 0x03});
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutVolumeOffsetChanged(test_address, 1, 0x0201));
+ cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(),
+ cb_data);
+}
+
+TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_location) {
+ VolumeControl::Get()->GetExtAudioOutLocation(test_address, 2);
+ EXPECT_TRUE(cb);
+ std::vector<uint8_t> value({0x01, 0x02, 0x03, 0x04});
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutLocationChanged(test_address, 2, 0x04030201));
+ cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(),
+ cb_data);
+}
+
+TEST_F(VolumeControlValueGetTest, test_get_ext_audio_out_description) {
+ VolumeControl::Get()->GetExtAudioOutDescription(test_address, 2);
+ EXPECT_TRUE(cb);
+ std::string descr = "right";
+ std::vector<uint8_t> value(descr.begin(), descr.end());
+ EXPECT_CALL(*callbacks,
+ OnExtAudioOutDescriptionChanged(test_address, 2, descr));
+ cb(conn_id, GATT_SUCCESS, handle, (uint16_t)value.size(), value.data(),
+ cb_data);
+}
+
class VolumeControlValueSetTest : public VolumeControlTest {
protected:
const RawAddress test_address = GetTestAddress(0);
@@ -658,6 +919,41 @@ TEST_F(VolumeControlValueSetTest, test_set_volume) {
VolumeControl::Get()->SetVolume(test_address, 0x10);
}
+TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_volume_offset) {
+ std::vector<uint8_t> expected_data({0x01, 0x00, 0x34, 0x12});
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0088, expected_data,
+ GATT_WRITE, _, _));
+ VolumeControl::Get()->SetExtAudioOutVolumeOffset(test_address, 2, 0x1234);
+}
+
+TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_location) {
+ std::vector<uint8_t> expected_data({0x44, 0x33, 0x22, 0x11});
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x0085, expected_data,
+ GATT_WRITE_NO_RSP, _, _));
+ VolumeControl::Get()->SetExtAudioOutLocation(test_address, 2, 0x11223344);
+}
+
+TEST_F(VolumeControlValueSetTest,
+ test_set_ext_audio_out_location_non_writable) {
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+ VolumeControl::Get()->SetExtAudioOutLocation(test_address, 1, 0x11223344);
+}
+
+TEST_F(VolumeControlValueSetTest, test_set_ext_audio_out_description) {
+ std::string descr = "right front";
+ std::vector<uint8_t> expected_data(descr.begin(), descr.end());
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(conn_id, 0x008a, expected_data,
+ GATT_WRITE_NO_RSP, _, _));
+ VolumeControl::Get()->SetExtAudioOutDescription(test_address, 2, descr);
+}
+
+TEST_F(VolumeControlValueSetTest,
+ test_set_ext_audio_out_description_non_writable) {
+ std::string descr = "left front";
+ EXPECT_CALL(gatt_queue, WriteCharacteristic(_, _, _, _, _, _)).Times(0);
+ VolumeControl::Get()->SetExtAudioOutDescription(test_address, 1, descr);
+}
+
class VolumeControlCsis : public VolumeControlTest {
protected:
const RawAddress test_address_1 = GetTestAddress(0);
diff --git a/system/btif/src/bluetooth.cc b/system/btif/src/bluetooth.cc
index c6e61194b7..be9e1143b9 100644
--- a/system/btif/src/bluetooth.cc
+++ b/system/btif/src/bluetooth.cc
@@ -56,6 +56,7 @@
#include "bta/include/bta_hf_client_api.h"
#include "bta/include/bta_le_audio_api.h"
#include "bta/include/bta_le_audio_broadcaster_api.h"
+#include "bta/include/bta_vc_api.h"
#include "btif/avrcp/avrcp_service.h"
#include "btif/include/stack_manager.h"
#include "btif_a2dp.h"
@@ -441,6 +442,7 @@ static void dump(int fd, const char** arguments) {
#ifndef TARGET_FLOSS
LeAudioClient::DebugDump(fd);
LeAudioBroadcaster::DebugDump(fd);
+ VolumeControl::DebugDump(fd);
#endif
connection_manager::dump(fd);
bluetooth::bqr::DebugDump(fd);
diff --git a/system/btif/src/btif_vc.cc b/system/btif/src/btif_vc.cc
index 61ecb20172..28aaa34d90 100644
--- a/system/btif/src/btif_vc.cc
+++ b/system/btif/src/btif_vc.cc
@@ -58,18 +58,67 @@ class VolumeControlInterfaceImpl : public VolumeControlInterface,
bool mute, bool isAutonomous) override {
DVLOG(2) << __func__ << " address: " << address << " volume: " << volume
<< " mute: " << mute << " isAutonomous: " << isAutonomous;
- do_in_jni_thread(FROM_HERE,
- Bind(&VolumeControlCallbacks::OnVolumeStateChanged,
- Unretained(callbacks_), address, volume, mute, isAutonomous));
+ do_in_jni_thread(
+ FROM_HERE,
+ Bind(&VolumeControlCallbacks::OnVolumeStateChanged,
+ Unretained(callbacks_), address, volume, mute, isAutonomous));
}
- void OnGroupVolumeStateChanged(int group_id, uint8_t volume,
- bool mute, bool isAutonomous) override {
+ void OnGroupVolumeStateChanged(int group_id, uint8_t volume, bool mute,
+ bool isAutonomous) override {
DVLOG(2) << __func__ << " group_id: " << group_id << " volume: " << volume
<< " mute: " << mute << " isAutonomous: " << isAutonomous;
+ do_in_jni_thread(
+ FROM_HERE,
+ Bind(&VolumeControlCallbacks::OnGroupVolumeStateChanged,
+ Unretained(callbacks_), group_id, volume, mute, isAutonomous));
+ }
+
+ void OnDeviceAvailable(const RawAddress& address,
+ uint8_t num_offset) override {
+ DVLOG(2) << __func__ << " address: " << address;
do_in_jni_thread(FROM_HERE,
- Bind(&VolumeControlCallbacks::OnGroupVolumeStateChanged,
- Unretained(callbacks_), group_id, volume, mute, isAutonomous));
+ Bind(&VolumeControlCallbacks::OnDeviceAvailable,
+ Unretained(callbacks_), address, num_offset));
+ }
+
+ /* Callbacks for Volume Offset Control Service (VOCS) - Extended Audio Outputs
+ */
+
+ void OnExtAudioOutVolumeOffsetChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id: " << ext_output_id << "offset:" << offset;
+
+ do_in_jni_thread(
+ FROM_HERE,
+ Bind(&VolumeControlCallbacks::OnExtAudioOutVolumeOffsetChanged,
+ Unretained(callbacks_), address, ext_output_id, offset));
+ }
+
+ void OnExtAudioOutLocationChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ uint32_t location) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id: " << ext_output_id
+ << "location:" << loghex(location);
+
+ do_in_jni_thread(
+ FROM_HERE,
+ Bind(&VolumeControlCallbacks::OnExtAudioOutLocationChanged,
+ Unretained(callbacks_), address, ext_output_id, location));
+ }
+
+ void OnExtAudioOutDescriptionChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id: " << ext_output_id << "descr:" << descr;
+ do_in_jni_thread(
+ FROM_HERE,
+ Bind(&VolumeControlCallbacks::OnExtAudioOutDescriptionChanged,
+ Unretained(callbacks_), address, ext_output_id, descr));
}
void Connect(const RawAddress& address) override {
@@ -107,6 +156,69 @@ class VolumeControlInterfaceImpl : public VolumeControlInterface,
/* Placeholder: Remove things from storage here */
}
+ void GetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id;
+ do_in_main_thread(
+ FROM_HERE,
+ Bind(&VolumeControl::GetExtAudioOutVolumeOffset,
+ Unretained(VolumeControl::Get()), address, ext_output_id));
+ }
+
+ void SetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset_val) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id
+ << "ext_output_id:" << offset_val;
+ do_in_main_thread(FROM_HERE,
+ Bind(&VolumeControl::SetExtAudioOutVolumeOffset,
+ Unretained(VolumeControl::Get()), address,
+ ext_output_id, offset_val));
+ }
+
+ void GetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id;
+
+ do_in_main_thread(FROM_HERE, Bind(&VolumeControl::GetExtAudioOutLocation,
+ Unretained(VolumeControl::Get()), address,
+ ext_output_id));
+ }
+
+ void SetExtAudioOutLocation(const RawAddress& address, uint8_t ext_output_id,
+ uint32_t location) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id
+ << "location:" << loghex(location);
+ do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetExtAudioOutLocation,
+ Unretained(VolumeControl::Get()), address,
+ ext_output_id, location));
+ }
+
+ void GetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id;
+
+ do_in_main_thread(FROM_HERE, Bind(&VolumeControl::GetExtAudioOutDescription,
+ Unretained(VolumeControl::Get()), address,
+ ext_output_id));
+ }
+
+ void SetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) override {
+ DVLOG(2) << __func__ << " address: " << address
+ << "ext_output_id:" << ext_output_id << "description:" << descr;
+
+ do_in_main_thread(FROM_HERE, Bind(&VolumeControl::SetExtAudioOutDescription,
+ Unretained(VolumeControl::Get()), address,
+ ext_output_id, descr));
+ }
+
void Cleanup(void) override {
DVLOG(2) << __func__;
do_in_main_thread(FROM_HERE, Bind(&VolumeControl::CleanUp));
diff --git a/system/embdrv/lc3/Android.bp b/system/embdrv/lc3/Android.bp
index 7b6e137f01..8219ccf831 100644
--- a/system/embdrv/lc3/Android.bp
+++ b/system/embdrv/lc3/Android.bp
@@ -51,10 +51,11 @@ cc_fuzz {
name: "liblc3_fuzzer",
srcs: [
- "fuzzer/liblc3encoder_fuzzer.cpp",
+ "fuzzer/liblc3_fuzzer.cpp",
],
static_libs: [
"liblc3",
],
}
+
diff --git a/system/embdrv/lc3/fuzzer/liblc3encoder_fuzzer.cpp b/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp
index 767dba495e..a5a7ae7f29 100644
--- a/system/embdrv/lc3/fuzzer/liblc3encoder_fuzzer.cpp
+++ b/system/embdrv/lc3/fuzzer/liblc3_fuzzer.cpp
@@ -17,18 +17,16 @@
#include "include/lc3.h"
-
-extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
- FuzzedDataProvider fdp(data, size);
-
+void TestEncoder(FuzzedDataProvider& fdp) {
int dt_us = fdp.PickValueInArray({10000, 7500});
- int sr_hz = fdp.PickValueInArray({8000, 16000, 24000, 32000, /*44100,*/ 48000});
+ int sr_hz =
+ fdp.PickValueInArray({8000, 16000, 24000, 32000, /*44100,*/ 48000});
unsigned enc_size = lc3_encoder_size(dt_us, sr_hz);
uint16_t output_byte_count = fdp.ConsumeIntegralInRange(20, 400);
uint16_t num_frames = lc3_frame_samples(dt_us, sr_hz);
if (fdp.remaining_bytes() < num_frames * 2) {
- return 0;
+ return;
}
std::vector<uint16_t> input_frames(num_frames);
@@ -41,10 +39,49 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
lc3_setup_encoder(dt_us, sr_hz, 0, lc3_encoder_mem);
std::vector<uint8_t> output(output_byte_count);
- lc3_encode(lc3_encoder, (const int16_t*)input_frames.data(), 1,
- output.size(), output.data());
+ lc3_encode(lc3_encoder, (const int16_t*)input_frames.data(), 1, output.size(),
+ output.data());
free(lc3_encoder_mem);
lc3_encoder_mem = nullptr;
+ return;
+}
+
+void TestDecoder(FuzzedDataProvider& fdp) {
+ int dt_us = fdp.PickValueInArray({10000, 7500});
+ int sr_hz =
+ fdp.PickValueInArray({8000, 16000, 24000, 32000, /*44100,*/ 48000});
+ unsigned dec_size = lc3_decoder_size(dt_us, sr_hz);
+ uint16_t input_byte_count = fdp.ConsumeIntegralInRange(20, 400);
+ uint16_t num_frames = lc3_frame_samples(dt_us, sr_hz);
+
+ if (fdp.remaining_bytes() < input_byte_count) {
+ return;
+ }
+
+ std::vector<uint8_t> input(input_byte_count);
+ fdp.ConsumeData(input.data(), input.size());
+
+ void* lc3_decoder_mem = nullptr;
+ lc3_decoder_mem = malloc(dec_size);
+ lc3_decoder_t lc3_decoder =
+ lc3_setup_decoder(dt_us, sr_hz, 0, lc3_decoder_mem);
+
+ std::vector<uint16_t> output(num_frames);
+ lc3_decode(lc3_decoder, input.data(), input.size(), (int16_t*)output.data(),
+ 1);
+
+ /* Empty input performs PLC (packet loss concealment) */
+ lc3_decode(lc3_decoder, nullptr, 0, (int16_t*)output.data(), 1);
+
+ free(lc3_decoder_mem);
+ lc3_decoder_mem = nullptr;
+ return;
+}
+
+extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
+ FuzzedDataProvider fdp(data, size);
+ TestEncoder(fdp);
+ TestDecoder(fdp);
return 0;
} \ No newline at end of file
diff --git a/system/gd/packet/parser/gen_rust.cc b/system/gd/packet/parser/gen_rust.cc
index 0fe9c71d5b..75e814a578 100644
--- a/system/gd/packet/parser/gen_rust.cc
+++ b/system/gd/packet/parser/gen_rust.cc
@@ -52,6 +52,10 @@ pub enum Error {
ImpossibleStructError,
}
+#[derive(Debug, Error)]
+#[error("{0}")]
+pub struct TryFromError(&'static str);
+
pub trait Packet {
fn to_bytes(self) -> Bytes;
fn to_vec(self) -> Vec<u8>;
diff --git a/system/gd/packet/parser/packet_def.cc b/system/gd/packet/parser/packet_def.cc
index 0229360d32..d30be830d3 100644
--- a/system/gd/packet/parser/packet_def.cc
+++ b/system/gd/packet/parser/packet_def.cc
@@ -1138,7 +1138,8 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
s << "impl CommandExpectations for " << name_ << "Packet {";
s << " type ResponseType = " << complement_->name_ << "Packet;";
s << " fn _to_response_type(pkt: EventPacket) -> Self::ResponseType { ";
- s << complement_->name_ << "Packet::new(pkt." << complement_root_accessor << ".clone())";
+ s << complement_->name_ << "Packet::new(pkt." << complement_root_accessor << ".clone())"
+ << ".unwrap()";
s << " }";
s << "}";
}
@@ -1173,10 +1174,20 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
s << "}\n";
s << "}\n";
+ if (root != this) {
+ s << "impl TryFrom<" << root->name_ << "Packet"
+ << "> for " << name_ << "Packet {\n";
+ s << "type Error = TryFromError;\n";
+ s << "fn try_from(value: " << root->name_ << "Packet)"
+ << " -> std::result::Result<Self, Self::Error> {\n";
+ s << "Self::new(value." << root_accessor << ").map_err(TryFromError)\n", s << "}\n";
+ s << "}\n";
+ }
+
s << "impl " << name_ << "Packet {";
if (parent_ == nullptr) {
s << "pub fn parse(bytes: &[u8]) -> Result<Self> { ";
- s << "Ok(Self::new(Arc::new(" << name_ << "Data::parse(bytes)?)))";
+ s << "Ok(Self::new(Arc::new(" << name_ << "Data::parse(bytes)?)).unwrap())";
s << "}";
}
@@ -1185,7 +1196,7 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
s << " match &self." << util::CamelCaseToUnderScore(name_) << ".child {";
for (const auto& child : children_) {
s << name_ << "DataChild::" << child->name_ << "(_) => " << name_ << "Child::" << child->name_ << "("
- << child->name_ << "Packet::new(self." << root_accessor << ".clone())),";
+ << child->name_ << "Packet::new(self." << root_accessor << ".clone()).unwrap()),";
}
if (fields_.HasPayload()) {
s << name_ << "DataChild::Payload(p) => " << name_ << "Child::Payload(p.clone()),";
@@ -1197,7 +1208,7 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
lineage.push_back(this);
const ParentDef* prev = nullptr;
- s << " fn new(root: Arc<" << root->name_ << "Data>) -> Self {";
+ s << " fn new(root: Arc<" << root->name_ << "Data>) -> std::result::Result<Self, &'static str> {";
for (auto it = lineage.begin(); it != lineage.end(); it++) {
auto def = *it;
auto accessor_name = util::CamelCaseToUnderScore(def->name_);
@@ -1206,17 +1217,17 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
} else {
s << "let " << accessor_name << " = match &" << util::CamelCaseToUnderScore(prev->name_) << ".child {";
s << prev->name_ << "DataChild::" << def->name_ << "(value) => (*value).clone(),";
- s << "_ => panic!(\"inconsistent state - child was not " << def->name_ << "\"),";
+ s << "_ => return Err(\"inconsistent state - child was not " << def->name_ << "\"),";
s << "};";
}
prev = def;
}
- s << "Self {";
+ s << "Ok(Self {";
for (auto it = lineage.begin(); it != lineage.end(); it++) {
auto def = *it;
s << util::CamelCaseToUnderScore(def->name_) << ",";
}
- s << "}}";
+ s << "})}";
for (auto it = lineage.begin(); it != lineage.end(); it++) {
auto def = *it;
@@ -1250,7 +1261,8 @@ void PacketDef::GenRustAccessStructImpls(std::ostream& s) const {
auto def = *it;
s << "impl Into<" << def->name_ << "Packet> for " << name_ << "Packet {";
s << " fn into(self) -> " << def->name_ << "Packet {";
- s << def->name_ << "Packet::new(self." << util::CamelCaseToUnderScore(root->name_) << ")";
+ s << def->name_ << "Packet::new(self." << util::CamelCaseToUnderScore(root->name_) << ")"
+ << ".unwrap()";
s << " }";
s << "}\n";
}
@@ -1263,7 +1275,8 @@ void PacketDef::GenRustBuilderStructImpls(std::ostream& s) const {
s << "impl CommandExpectations for " << name_ << "Builder {";
s << " type ResponseType = " << complement_->name_ << "Packet;";
s << " fn _to_response_type(pkt: EventPacket) -> Self::ResponseType { ";
- s << complement_->name_ << "Packet::new(pkt." << complement_root_accessor << ".clone())";
+ s << complement_->name_ << "Packet::new(pkt." << complement_root_accessor << ".clone())"
+ << ".unwrap()";
s << " }";
s << "}";
}
@@ -1328,7 +1341,7 @@ void PacketDef::GenRustBuilderStructImpls(std::ostream& s) const {
prev = ancestor;
}
- s << name_ << "Packet::new(" << util::CamelCaseToUnderScore(prev->name_) << ")";
+ s << name_ << "Packet::new(" << util::CamelCaseToUnderScore(prev->name_) << ").unwrap()";
s << "}\n";
s << "}\n";
diff --git a/system/gd/rust/linux/dbus_projection/dbus_macros/src/lib.rs b/system/gd/rust/linux/dbus_projection/dbus_macros/src/lib.rs
index 33a8499036..f406577a30 100644
--- a/system/gd/rust/linux/dbus_projection/dbus_macros/src/lib.rs
+++ b/system/gd/rust/linux/dbus_projection/dbus_macros/src/lib.rs
@@ -13,8 +13,20 @@ use syn::{Expr, FnArg, ImplItem, ItemImpl, ItemStruct, Meta, Pat, ReturnType, Ty
use crate::proc_macro::TokenStream;
+const OUTPUT_DEBUG: bool = false;
+
fn debug_output_to_file(gen: &proc_macro2::TokenStream, filename: String) {
- let path = Path::new(filename.as_str());
+ if !OUTPUT_DEBUG {
+ return;
+ }
+
+ let filepath = std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
+ .join(filename)
+ .to_str()
+ .unwrap()
+ .to_string();
+
+ let path = Path::new(&filepath);
let mut file = File::create(&path).unwrap();
file.write_all(gen.to_string().as_bytes()).unwrap();
}
@@ -201,8 +213,7 @@ pub fn generate_dbus_exporter(attr: TokenStream, item: TokenStream) -> TokenStre
}
};
- // TODO: Have a switch to turn on/off this debug.
- debug_output_to_file(&gen, format!("/tmp/out-{}.rs", fn_ident.to_string()));
+ debug_output_to_file(&gen, format!("out-{}.rs", fn_ident.to_string()));
gen.into()
}
@@ -363,7 +374,6 @@ pub fn generate_dbus_interface_client(_attr: TokenStream, item: TokenStream) ->
}
};
- // TODO: Have a switch to turn on/off this debug.
debug_output_to_file(
&gen,
std::path::PathBuf::from(std::env::var("OUT_DIR").unwrap())
@@ -501,8 +511,7 @@ pub fn dbus_propmap(attr: TokenStream, item: TokenStream) -> TokenStream {
}
};
- // TODO: Have a switch to turn this debug off/on.
- debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
+ debug_output_to_file(&gen, format!("out-{}.rs", struct_ident.to_string()));
gen.into()
}
@@ -664,8 +673,7 @@ pub fn dbus_proxy_obj(attr: TokenStream, item: TokenStream) -> TokenStream {
}
};
- // TODO: Have a switch to turn this debug off/on.
- debug_output_to_file(&gen, format!("/tmp/out-{}.rs", struct_ident.to_string()));
+ debug_output_to_file(&gen, format!("out-{}.rs", struct_ident.to_string()));
gen.into()
}
@@ -860,8 +868,7 @@ pub fn generate_dbus_arg(_item: TokenStream) -> TokenStream {
}
};
- // TODO: Have a switch to turn this debug off/on.
- debug_output_to_file(&gen, format!("/tmp/out-generate_dbus_arg.rs"));
+ debug_output_to_file(&gen, format!("out-generate_dbus_arg.rs"));
gen.into()
}
diff --git a/system/include/hardware/bt_vc.h b/system/include/hardware/bt_vc.h
index e7f498b6cb..18fd764b29 100644
--- a/system/include/hardware/bt_vc.h
+++ b/system/include/hardware/bt_vc.h
@@ -47,6 +47,21 @@ class VolumeControlCallbacks {
/* Callback for the volume change changed on the group*/
virtual void OnGroupVolumeStateChanged(int group_id, uint8_t volume,
bool mute, bool isAutonomous) = 0;
+
+ virtual void OnDeviceAvailable(const RawAddress& address,
+ uint8_t num_offset) = 0;
+
+ /* Callbacks for Volume Offset Control Service (VOCS) - Extended Audio Outputs
+ */
+ virtual void OnExtAudioOutVolumeOffsetChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset) = 0;
+ virtual void OnExtAudioOutLocationChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ uint32_t location) = 0;
+ virtual void OnExtAudioOutDescriptionChanged(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) = 0;
};
class VolumeControlInterface {
@@ -71,6 +86,21 @@ class VolumeControlInterface {
/** Set the volume */
virtual void SetVolume(std::variant<RawAddress, int> addr_or_group_id,
uint8_t volume) = 0;
+ virtual void GetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+ virtual void SetExtAudioOutVolumeOffset(const RawAddress& address,
+ uint8_t ext_output_id,
+ int16_t offset_val) = 0;
+ virtual void GetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+ virtual void SetExtAudioOutLocation(const RawAddress& address,
+ uint8_t ext_output_id,
+ uint32_t location) = 0;
+ virtual void GetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id) = 0;
+ virtual void SetExtAudioOutDescription(const RawAddress& address,
+ uint8_t ext_output_id,
+ std::string descr) = 0;
};
} /* namespace vc */
diff --git a/system/stack/btm/btm_sco.cc b/system/stack/btm/btm_sco.cc
index c1d96dec86..a48bdfdaea 100644
--- a/system/stack/btm/btm_sco.cc
+++ b/system/stack/btm/btm_sco.cc
@@ -199,7 +199,6 @@ void btm_route_sco_data(BT_HDR* p_msg) {
osi_free(p_msg);
return;
}
- LOG_INFO("Received SCO packet from HCI. Dropping it since no handler so far");
uint16_t handle = handle_with_flags & 0xFFF;
ASSERT_LOG(handle <= 0xEFF, "Require handle <= 0xEFF, but is 0x%X", handle);
auto* active_sco = btm_get_active_sco();
@@ -210,7 +209,7 @@ void btm_route_sco_data(BT_HDR* p_msg) {
osi_free(p_msg);
// For Chrome OS, we send the outgoing data after receiving an incoming one
uint8_t out_buf[BTM_SCO_DATA_SIZE_MAX];
- auto size_read = bluetooth::audio::sco::read(out_buf, BTM_SCO_DATA_SIZE_MAX);
+ auto size_read = bluetooth::audio::sco::read(out_buf, length);
auto data = std::vector<uint8_t>(out_buf, out_buf + size_read);
// TODO: For MSBC, we need to encode here
btm_send_sco_packet(std::move(data));
diff --git a/system/test/mock/mock_bta_vc_device.cc b/system/test/mock/mock_bta_vc_device.cc
index c2457cc91f..a143cf7ef8 100644
--- a/system/test/mock/mock_bta_vc_device.cc
+++ b/system/test/mock/mock_bta_vc_device.cc
@@ -26,6 +26,7 @@ extern std::map<std::string, int> mock_function_count_map;
#include <map>
#include <vector>
+
#include "bta/vc/devices.h"
#include "stack/btm/btm_sec.h"
@@ -62,6 +63,10 @@ bool VolumeControlDevice::set_volume_control_service_handles(
mock_function_count_map[__func__]++;
return false;
}
+void VolumeControlDevice::set_volume_offset_control_service_handles(
+ const gatt::Service& service) {
+ mock_function_count_map[__func__]++;
+}
bool VolumeControlDevice::subscribe_for_notifications(tGATT_IF gatt_if,
uint16_t handle,
uint16_t ccc_handle,
diff --git a/tools/OWNERS b/tools/OWNERS
new file mode 100644
index 0000000000..c658b617af
--- /dev/null
+++ b/tools/OWNERS
@@ -0,0 +1,13 @@
+# Reviewers for /tools
+
+cmanton@google.com
+cncn@google.com
+hsz@google.com
+jpawlowski@google.com
+mylesgw@google.com
+optedoblivion@google.com
+qasimj@google.com
+rahulsabnis@google.com
+sattiraju@google.com
+siyuanh@google.com
+zachoverflow@google.com