Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit b5702e6c authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Łukasz Rymanowski (xWF)
Browse files

vc: Fix handling volume setup after reconnect

Before the patch, after reconnection the Android always tried to set the
volume which is present on the earbuds dispite the Persistent Flag.
However, there was a use case, where value was not set properly  i.e
when after reconnection, Phone had aleady a valid cache volume.

To fix it properly there is a need to provide Persistent Flag to the
Java layer, so Android can make a proper decision.

With this patch we make sure volume of the remote device always will be
applied to the system after reconnection unless Volume Persist Flag is
set to Reset (0x00) and volume on the remote device is 0.

Bug: 352647510
Flag: Exempt, corner case, regressions verified with unit tests, new test added
Test: atest bluetooth_vc_test VolumeControlServiceTest
VolumeControlNativeTest

Change-Id: I30d36e188574a7ab5c12ee19a0587030fd793c25
parent b5caa4c4
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -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("");

@@ -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,
@@ -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},
+3 −1
Original line number Diff line number Diff line
@@ -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;

+48 −16
Original line number Diff line number Diff line
@@ -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) {
@@ -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) {
@@ -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) {
@@ -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
@@ -1119,6 +1150,7 @@ public class VolumeControlService extends ProfileService {
                    stackEvent.device,
                    stackEvent.valueInt1,
                    stackEvent.valueInt2,
                    stackEvent.valueInt3,
                    stackEvent.valueBool1,
                    stackEvent.valueBool2);
            return;
+12 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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));
@@ -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:
+2 −1
Original line number Diff line number Diff line
@@ -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