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

Commit aecd8441 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski Committed by Łukasz Rymanowski
Browse files

VolumeControl: Fix restoring earbuds volume

If we stream to one earbud and the second earbud connects with a
different volume level and a flag saying that it is a User-set volume is
set, we should realign its volume to the existing stream volume that was
probably already adjusted to the users liking.

We should apply this new volume to the system only if this is the first
earbud that has connected.

To make sure there is no race between connection and volume state change
events, we need to pass the volume event via the state machine just like
the connection event is. Otherwise the order of events arrival in the
service may differ from the order they were sent.

Bug: 373377157
Test: atest GoogleBluetoothInstrumentationTests
Flag: EXEMPT; Regression covered with unit test
Change-Id: Ifec0ae193a32019e091380dcc7b23f5fe097211f
parent d29d3c5d
Loading
Loading
Loading
Loading
+24 −7
Original line number Diff line number Diff line
@@ -785,12 +785,13 @@ public class VolumeControlService extends ProfileService {
                            + (", flags: " + flags));
            /* We are here, because system has just started and LeAudio device is connected. If
             * remote device has User Persistent flag set, Android sets the volume to local cache
             * and to the audio system.
             * and to the audio system if not already streaming to other devices.
             * If Reset Flag is set, 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) {
            if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01)
                    && (getConnectedDevices().size() == 1)) {
                updateGroupCacheAndAudioSystem(groupId, volume, mute, false);
                return;
            }
@@ -1138,12 +1139,12 @@ public class VolumeControlService extends ProfileService {
        input.setPropSettings(id, unit, min, max);
    }

    void messageFromNative(VolumeControlStackEvent stackEvent) {
    void handleStackEvent(VolumeControlStackEvent stackEvent) {
        if (!isAvailable()) {
            Log.e(TAG, "Event ignored, service not available: " + stackEvent);
            return;
        }
        Log.d(TAG, "messageFromNative: " + stackEvent);
        Log.d(TAG, "handleStackEvent: " + stackEvent);

        if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) {
            handleVolumeControlChanged(
@@ -1182,20 +1183,36 @@ public class VolumeControlService extends ProfileService {
            return;
        }

        Log.e(TAG, "Unhandled event: " + stackEvent);
    }

    void messageFromNative(VolumeControlStackEvent stackEvent) {
        Log.d(TAG, "messageFromNative: " + stackEvent);

        // Group events should be handled here directly
        boolean isGroupEvent = (stackEvent.device == null);
        if (isGroupEvent) {
            handleStackEvent(stackEvent);
            return;
        }

        // Other device events should be serialized via their state machines so they are processed
        // in the same order they were sent from the native code.
        synchronized (mStateMachines) {
            VolumeControlStateMachine sm = mStateMachines.get(device);
            VolumeControlStateMachine sm = mStateMachines.get(stackEvent.device);
            if (sm == null) {
                if (stackEvent.type
                        == VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED) {
                    switch (stackEvent.valueInt1) {
                        case STATE_CONNECTED, STATE_CONNECTING -> {
                            sm = getOrCreateStateMachine(device);
                            sm = getOrCreateStateMachine(stackEvent.device);
                        }
                    }
                }
            }
            if (sm == null) {
                Log.e(TAG, "Cannot process stack event: no state machine: " + stackEvent);
                Log.w(TAG, "Cannot forward stack event: no state machine: " + stackEvent);
                handleStackEvent(stackEvent);
                return;
            }
            sm.sendMessage(VolumeControlStateMachine.MESSAGE_STACK_EVENT, stackEvent);
+0 −2
Original line number Diff line number Diff line
@@ -102,8 +102,6 @@ public class VolumeControlStackEvent {

    private static String eventTypeValue2ToString(int type, int value) {
        switch (type) {
            case EVENT_TYPE_CONNECTION_STATE_CHANGED:
                return BluetoothProfile.getConnectionStateName(value);
            case EVENT_TYPE_VOLUME_STATE_CHANGED:
                return "{volume:" + value + "}";
            case EVENT_TYPE_DEVICE_AVAILABLE:
+18 −4
Original line number Diff line number Diff line
@@ -157,7 +157,10 @@ class VolumeControlStateMachine extends StateMachine {
                        case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> {
                            processConnectionEvent(event.valueInt1);
                        }
                        default -> Log.e(TAG, "Disconnected: ignoring stack event: " + event);
                        default -> {
                            Log.e(TAG, "Disconnected: forwarding stack event: " + event);
                            mService.handleStackEvent(event);
                        }
                    }
                }
                default -> {
@@ -262,7 +265,14 @@ class VolumeControlStateMachine extends StateMachine {
                        case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> {
                            processConnectionEvent(event.valueInt1);
                        }
                        default -> Log.e(TAG, "Connecting: ignoring stack event: " + event);
                        case VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED -> {
                            Log.w(TAG, "Defer volume change received while connecting: " + mDevice);
                            deferMessage(message);
                        }
                        default -> {
                            Log.e(TAG, "Connecting: forwarding stack event: " + event);
                            mService.handleStackEvent(event);
                        }
                    }
                }
                default -> {
@@ -355,7 +365,10 @@ class VolumeControlStateMachine extends StateMachine {
                        case VolumeControlStackEvent.EVENT_TYPE_CONNECTION_STATE_CHANGED -> {
                            processConnectionEvent(event.valueInt1);
                        }
                        default -> Log.e(TAG, "Disconnecting: ignoring stack event: " + event);
                        default -> {
                            Log.e(TAG, "Disconnecting: forwarding stack event: " + event);
                            mService.handleStackEvent(event);
                        }
                    }
                }
                default -> {
@@ -452,7 +465,8 @@ class VolumeControlStateMachine extends StateMachine {
                            processConnectionEvent(event.valueInt1);
                        }
                        default -> {
                            Log.e(TAG, "Connected: ignoring stack event: " + event);
                            Log.e(TAG, "Connected: forwarding stack event: " + event);
                            mService.handleStackEvent(event);
                        }
                    }
                }
+11 −4
Original line number Diff line number Diff line
@@ -610,8 +610,8 @@ public class VolumeControlServiceTest {
                (int) Math.round((double) (volumeDevice * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL);
        verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt());

        // Connect second device and read different volume. Expect it will be set to AF and to
        // another set member
        // Connect second device and read different volume. Expect it will NOT be set to AF
        // and to another set member, but the existing volume gets applied to it
        generateDeviceAvailableMessageFromNative(mDeviceTwo, 1);
        generateConnectionMessageFromNative(mDeviceTwo, STATE_CONNECTED, STATE_DISCONNECTED);
        assertThat(mService.getConnectionState(mDeviceTwo)).isEqualTo(STATE_CONNECTED);
@@ -625,9 +625,12 @@ public class VolumeControlServiceTest {
                flags,
                initialMuteState,
                initialAutonomousFlag);
        expectedAfVol =

        expectedAfVol = volumeDevice;
        int unexpectedAfVol =
                (int) Math.round((double) (volumeDeviceTwo * MEDIA_MAX_VOL) / BT_LE_AUDIO_MAX_VOL);
        verify(mAudioManager).setStreamVolume(anyInt(), eq(expectedAfVol), anyInt());
        verify(mAudioManager, times(0)).setStreamVolume(anyInt(), eq(unexpectedAfVol), anyInt());
        verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol));
    }

    private void testConnectedDeviceWithResetFlag(
@@ -1276,6 +1279,7 @@ public class VolumeControlServiceTest {
        stackEvent.valueBool1 = mute;
        stackEvent.valueBool2 = isAutonomous;
        mService.messageFromNative(stackEvent);
        mLooper.dispatchAll();
    }

    private void generateDeviceOffsetChangedMessageFromNative(
@@ -1288,6 +1292,7 @@ public class VolumeControlServiceTest {
        event.valueInt1 = extOffsetIndex; // external output index
        event.valueInt2 = offset; // offset value
        mService.messageFromNative(event);
        mLooper.dispatchAll();
    }

    private void generateDeviceLocationChangedMessageFromNative(
@@ -1300,6 +1305,7 @@ public class VolumeControlServiceTest {
        event.valueInt1 = extOffsetIndex; // external output index
        event.valueInt2 = location; // location
        mService.messageFromNative(event);
        mLooper.dispatchAll();
    }

    private void generateDeviceDescriptionChangedMessageFromNative(
@@ -1312,6 +1318,7 @@ public class VolumeControlServiceTest {
        event.valueInt1 = extOffsetIndex; // external output index
        event.valueString1 = description; // description
        mService.messageFromNative(event);
        mLooper.dispatchAll();
    }

    @SafeVarargs