diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-03-08 02:08:28 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2022-03-08 02:08:28 +0000 |
commit | 22389fee67a014fed221aadadcfbc1dc9cd7335f (patch) | |
tree | 24b5864c0fccfd1ae7549e3616dbc5ee1aa5bafb | |
parent | 4739b8feb4c2a5becbb584b1f62f0e5321079d17 (diff) | |
parent | c4c4f9227fb198e838b5b7de5d1a048f2d3a0c52 (diff) |
Snap for 8270536 from c4c4f9227fb198e838b5b7de5d1a048f2d3a0c52 to tm-release
Change-Id: I11136ce6eeaed10d012ef29fb7c081cbc5c1ced2
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 |