Loading android/app/jni/com_android_bluetooth_vc.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ public: addr.get()); } void OnVolumeStateChanged(const RawAddress& bd_addr, uint8_t volume, bool mute, void OnVolumeStateChanged(const RawAddress& bd_addr, uint8_t volume, bool mute, uint8_t flags, bool isAutonomous) override { log::info(""); Loading @@ -86,7 +86,7 @@ public: sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeStateChanged, (jint)volume, (jboolean)mute, addr.get(), (jboolean)isAutonomous); (jboolean)mute, (jint)flags, addr.get(), (jboolean)isAutonomous); } void OnGroupVolumeStateChanged(int group_id, uint8_t volume, bool mute, Loading Loading @@ -533,7 +533,7 @@ int register_com_android_bluetooth_vc(JNIEnv* env) { const JNIJavaMethod javaMethods[] = { {"onConnectionStateChanged", "(I[B)V", &method_onConnectionStateChanged}, {"onVolumeStateChanged", "(IZ[BZ)V", &method_onVolumeStateChanged}, {"onVolumeStateChanged", "(IZI[BZ)V", &method_onVolumeStateChanged}, {"onGroupVolumeStateChanged", "(IZIZ)V", &method_onGroupVolumeStateChanged}, {"onDeviceAvailable", "(I[B)V", &method_onDeviceAvailable}, {"onExtAudioOutVolumeOffsetChanged", "(II[B)V", &method_onExtAudioOutVolumeOffsetChanged}, Loading android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java +3 −1 Original line number Diff line number Diff line Loading @@ -251,13 +251,15 @@ public class VolumeControlNativeInterface { } @VisibleForTesting void onVolumeStateChanged(int volume, boolean mute, byte[] address, boolean isAutonomous) { void onVolumeStateChanged( int volume, boolean mute, int flags, byte[] address, boolean isAutonomous) { VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); event.device = getDevice(address); event.valueInt1 = -1; event.valueInt2 = volume; event.valueInt3 = flags; event.valueBool1 = mute; event.valueBool2 = isAutonomous; Loading android/app/src/com/android/bluetooth/vc/VolumeControlService.java +48 −16 Original line number Diff line number Diff line Loading @@ -200,6 +200,11 @@ public class VolumeControlService extends ProfileService { private final Map<Integer, Boolean> mGroupMuteCache = new HashMap<>(); private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new HashMap<>(); /* As defined by Volume Control Service 1.0.1, 3.3.1. Volume Flags behavior. * User Set Volume Setting means that remote keeps volume in its cache. */ @VisibleForTesting static final int VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK = 0x01; @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public VolumeControlService(Context ctx) { Loading Loading @@ -862,14 +867,22 @@ public class VolumeControlService extends ProfileService { } } void handleVolumeControlChanged( BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous) { int getBleVolumeFromCurrentStream() { int streamType = getBluetoothContextualVolumeStream(); int streamVolume = mAudioManager.getStreamVolume(streamType); int streamMaxVolume = mAudioManager.getStreamMaxVolume(streamType); if (isAutonomous && device != null) { Log.e(TAG, "We expect only group notification for autonomous updates"); return; /* leaudio expect volume value in range 0 to 255 */ return (int) Math.round((double) streamVolume * LE_AUDIO_MAX_VOL / streamMaxVolume); } void handleVolumeControlChanged( BluetoothDevice device, int groupId, int volume, int flags, boolean mute, boolean isAutonomous) { if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null) { Loading @@ -887,6 +900,34 @@ public class VolumeControlService extends ProfileService { int groupVolume = getGroupVolume(groupId); Boolean groupMute = getGroupMute(groupId); if (isAutonomous && device != null) { Log.i( TAG, ("Initial volume set after connect, volume: " + volume) + (", mute: " + mute) + (", flags: " + flags)); /* We are here, because system has just started and LeAudio device is connected. If * remote device has User Persistent flag set or the volume != 0, Android sets the * volume to local cache and to the audio system. If Reset Flag is set and remote has * volume set to 0, then Android sets to remote devices either cached volume volume * taken from audio manager. Note, to match BR/EDR behavior, don't show volume change in * UI here */ if ((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01 || (volume != 0)) { updateGroupCacheAndAudioSystem(groupId, volume, mute, false); } else { if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { Log.i(TAG, "Setting volume: " + groupVolume + " to the group: " + groupId); setGroupVolume(groupId, groupVolume); } else { int vol = getBleVolumeFromCurrentStream(); Log.i(TAG, "Setting system volume: " + vol + " to the group: " + groupId); setGroupVolume(groupId, getBleVolumeFromCurrentStream()); } } return; } if (Flags.leaudioBroadcastVolumeControlForConnectedDevices()) { Log.i(TAG, "handleVolumeControlChanged: " + device + "; volume: " + volume); if (device == null) { Loading @@ -906,16 +947,6 @@ public class VolumeControlService extends ProfileService { } } if (groupVolume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { /* We are here, because system was just started and LeAudio device just connected. * In such case, we take Volume stored on remote device and apply it to our cache and * audio system. * Note, to match BR/EDR behavior, don't show volume change in UI here */ updateGroupCacheAndAudioSystem(groupId, volume, mute, false); return; } if (!isAutonomous) { /* If the change is triggered by Android device, the stream is already changed. * However it might be called with isAutonomous, one the first read of after Loading Loading @@ -1119,6 +1150,7 @@ public class VolumeControlService extends ProfileService { stackEvent.device, stackEvent.valueInt1, stackEvent.valueInt2, stackEvent.valueInt3, stackEvent.valueBool1, stackEvent.valueBool2); return; Loading android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java +12 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ public class VolumeControlStackEvent { public BluetoothDevice device; public int valueInt1; public int valueInt2; public int valueInt3; public boolean valueBool1; public boolean valueBool2; public String valueString1; Loading @@ -58,6 +59,7 @@ public class VolumeControlStackEvent { result.append(", device:").append(device); result.append(", valueInt1:").append(eventTypeValue1ToString(type, valueInt1)); result.append(", valueInt2:").append(eventTypeValue2ToString(type, valueInt2)); result.append(", valueInt3:").append(eventTypeValue3ToString(type, valueInt3)); result.append(", valueBool1:").append(eventTypeValueBool1ToString(type, valueBool1)); result.append(", valueBool2:").append(eventTypeValueBool2ToString(type, valueBool2)); result.append(", valueString1:").append(eventTypeString1ToString(type, valueString1)); Loading Loading @@ -138,6 +140,16 @@ public class VolumeControlStackEvent { return Integer.toString(value); } private static String eventTypeValue3ToString(int type, int value) { switch (type) { case EVENT_TYPE_VOLUME_STATE_CHANGED: return "{flags:" + value + "}"; default: break; } return Integer.toString(value); } private static String eventTypeValueBool1ToString(int type, boolean value) { switch (type) { case EVENT_TYPE_VOLUME_STATE_CHANGED: Loading android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlNativeInterfaceTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -71,10 +71,11 @@ public class VolumeControlNativeInterfaceTest { public void onVolumeStateChanged() { int volume = 3; boolean mute = false; int flags = 1; byte[] address = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; boolean isAutonomous = false; mNativeInterface.onVolumeStateChanged(volume, mute, address, isAutonomous); mNativeInterface.onVolumeStateChanged(volume, mute, flags, address, isAutonomous); ArgumentCaptor<VolumeControlStackEvent> event = ArgumentCaptor.forClass(VolumeControlStackEvent.class); Loading Loading
android/app/jni/com_android_bluetooth_vc.cpp +3 −3 Original line number Diff line number Diff line Loading @@ -67,7 +67,7 @@ public: addr.get()); } void OnVolumeStateChanged(const RawAddress& bd_addr, uint8_t volume, bool mute, void OnVolumeStateChanged(const RawAddress& bd_addr, uint8_t volume, bool mute, uint8_t flags, bool isAutonomous) override { log::info(""); Loading @@ -86,7 +86,7 @@ public: sCallbackEnv->SetByteArrayRegion(addr.get(), 0, sizeof(RawAddress), (jbyte*)&bd_addr); sCallbackEnv->CallVoidMethod(mCallbacksObj, method_onVolumeStateChanged, (jint)volume, (jboolean)mute, addr.get(), (jboolean)isAutonomous); (jboolean)mute, (jint)flags, addr.get(), (jboolean)isAutonomous); } void OnGroupVolumeStateChanged(int group_id, uint8_t volume, bool mute, Loading Loading @@ -533,7 +533,7 @@ int register_com_android_bluetooth_vc(JNIEnv* env) { const JNIJavaMethod javaMethods[] = { {"onConnectionStateChanged", "(I[B)V", &method_onConnectionStateChanged}, {"onVolumeStateChanged", "(IZ[BZ)V", &method_onVolumeStateChanged}, {"onVolumeStateChanged", "(IZI[BZ)V", &method_onVolumeStateChanged}, {"onGroupVolumeStateChanged", "(IZIZ)V", &method_onGroupVolumeStateChanged}, {"onDeviceAvailable", "(I[B)V", &method_onDeviceAvailable}, {"onExtAudioOutVolumeOffsetChanged", "(II[B)V", &method_onExtAudioOutVolumeOffsetChanged}, Loading
android/app/src/com/android/bluetooth/vc/VolumeControlNativeInterface.java +3 −1 Original line number Diff line number Diff line Loading @@ -251,13 +251,15 @@ public class VolumeControlNativeInterface { } @VisibleForTesting void onVolumeStateChanged(int volume, boolean mute, byte[] address, boolean isAutonomous) { void onVolumeStateChanged( int volume, boolean mute, int flags, byte[] address, boolean isAutonomous) { VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED); event.device = getDevice(address); event.valueInt1 = -1; event.valueInt2 = volume; event.valueInt3 = flags; event.valueBool1 = mute; event.valueBool2 = isAutonomous; Loading
android/app/src/com/android/bluetooth/vc/VolumeControlService.java +48 −16 Original line number Diff line number Diff line Loading @@ -200,6 +200,11 @@ public class VolumeControlService extends ProfileService { private final Map<Integer, Boolean> mGroupMuteCache = new HashMap<>(); private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new HashMap<>(); /* As defined by Volume Control Service 1.0.1, 3.3.1. Volume Flags behavior. * User Set Volume Setting means that remote keeps volume in its cache. */ @VisibleForTesting static final int VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK = 0x01; @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public VolumeControlService(Context ctx) { Loading Loading @@ -862,14 +867,22 @@ public class VolumeControlService extends ProfileService { } } void handleVolumeControlChanged( BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous) { int getBleVolumeFromCurrentStream() { int streamType = getBluetoothContextualVolumeStream(); int streamVolume = mAudioManager.getStreamVolume(streamType); int streamMaxVolume = mAudioManager.getStreamMaxVolume(streamType); if (isAutonomous && device != null) { Log.e(TAG, "We expect only group notification for autonomous updates"); return; /* leaudio expect volume value in range 0 to 255 */ return (int) Math.round((double) streamVolume * LE_AUDIO_MAX_VOL / streamMaxVolume); } void handleVolumeControlChanged( BluetoothDevice device, int groupId, int volume, int flags, boolean mute, boolean isAutonomous) { if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) { LeAudioService leAudioService = mFactory.getLeAudioService(); if (leAudioService == null) { Loading @@ -887,6 +900,34 @@ public class VolumeControlService extends ProfileService { int groupVolume = getGroupVolume(groupId); Boolean groupMute = getGroupMute(groupId); if (isAutonomous && device != null) { Log.i( TAG, ("Initial volume set after connect, volume: " + volume) + (", mute: " + mute) + (", flags: " + flags)); /* We are here, because system has just started and LeAudio device is connected. If * remote device has User Persistent flag set or the volume != 0, Android sets the * volume to local cache and to the audio system. If Reset Flag is set and remote has * volume set to 0, then Android sets to remote devices either cached volume volume * taken from audio manager. Note, to match BR/EDR behavior, don't show volume change in * UI here */ if ((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01 || (volume != 0)) { updateGroupCacheAndAudioSystem(groupId, volume, mute, false); } else { if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { Log.i(TAG, "Setting volume: " + groupVolume + " to the group: " + groupId); setGroupVolume(groupId, groupVolume); } else { int vol = getBleVolumeFromCurrentStream(); Log.i(TAG, "Setting system volume: " + vol + " to the group: " + groupId); setGroupVolume(groupId, getBleVolumeFromCurrentStream()); } } return; } if (Flags.leaudioBroadcastVolumeControlForConnectedDevices()) { Log.i(TAG, "handleVolumeControlChanged: " + device + "; volume: " + volume); if (device == null) { Loading @@ -906,16 +947,6 @@ public class VolumeControlService extends ProfileService { } } if (groupVolume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) { /* We are here, because system was just started and LeAudio device just connected. * In such case, we take Volume stored on remote device and apply it to our cache and * audio system. * Note, to match BR/EDR behavior, don't show volume change in UI here */ updateGroupCacheAndAudioSystem(groupId, volume, mute, false); return; } if (!isAutonomous) { /* If the change is triggered by Android device, the stream is already changed. * However it might be called with isAutonomous, one the first read of after Loading Loading @@ -1119,6 +1150,7 @@ public class VolumeControlService extends ProfileService { stackEvent.device, stackEvent.valueInt1, stackEvent.valueInt2, stackEvent.valueInt3, stackEvent.valueBool1, stackEvent.valueBool2); return; Loading
android/app/src/com/android/bluetooth/vc/VolumeControlStackEvent.java +12 −0 Original line number Diff line number Diff line Loading @@ -40,6 +40,7 @@ public class VolumeControlStackEvent { public BluetoothDevice device; public int valueInt1; public int valueInt2; public int valueInt3; public boolean valueBool1; public boolean valueBool2; public String valueString1; Loading @@ -58,6 +59,7 @@ public class VolumeControlStackEvent { result.append(", device:").append(device); result.append(", valueInt1:").append(eventTypeValue1ToString(type, valueInt1)); result.append(", valueInt2:").append(eventTypeValue2ToString(type, valueInt2)); result.append(", valueInt3:").append(eventTypeValue3ToString(type, valueInt3)); result.append(", valueBool1:").append(eventTypeValueBool1ToString(type, valueBool1)); result.append(", valueBool2:").append(eventTypeValueBool2ToString(type, valueBool2)); result.append(", valueString1:").append(eventTypeString1ToString(type, valueString1)); Loading Loading @@ -138,6 +140,16 @@ public class VolumeControlStackEvent { return Integer.toString(value); } private static String eventTypeValue3ToString(int type, int value) { switch (type) { case EVENT_TYPE_VOLUME_STATE_CHANGED: return "{flags:" + value + "}"; default: break; } return Integer.toString(value); } private static String eventTypeValueBool1ToString(int type, boolean value) { switch (type) { case EVENT_TYPE_VOLUME_STATE_CHANGED: Loading
android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlNativeInterfaceTest.java +2 −1 Original line number Diff line number Diff line Loading @@ -71,10 +71,11 @@ public class VolumeControlNativeInterfaceTest { public void onVolumeStateChanged() { int volume = 3; boolean mute = false; int flags = 1; byte[] address = new byte[] {0x00, 0x01, 0x02, 0x03, 0x04, 0x05}; boolean isAutonomous = false; mNativeInterface.onVolumeStateChanged(volume, mute, address, isAutonomous); mNativeInterface.onVolumeStateChanged(volume, mute, flags, address, isAutonomous); ArgumentCaptor<VolumeControlStackEvent> event = ArgumentCaptor.forClass(VolumeControlStackEvent.class); Loading