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

Commit 1628a6d3 authored by Jakub Tyszkowski's avatar Jakub Tyszkowski
Browse files

LeAudio: Fix volume issues

This fixes not setting the absolute volume mode for the BLE Audio use
cases. It also adjusts the volume calculation from BLE volume level
to volume index to match the same formula used in BTHelper.java
for the reverse calculations (one can be easily derived from the other).

Since BLE Audio device has more granular volume control, If an earbud
changes its volume by a tiny step, multiple BLE volume values will be
translated into the same volume index and it may look like the volume
slider didn't move. And on the other hand if the audio FW changes the
volume by a whole index (or by 10 indices at once) the BLE device volume
will increment by a much bigger step and the volume difference will be
noticable on each step.

Bug: 238587620
Bug: 241501978
Test: atest BluetoothInstrumentationTests --no-bazel-mode
Tag: #feature
Change-Id: I54a73080b84bb9cbb3e2d799aff9fe7d5fa71e84
parent f3fcc9ea
Loading
Loading
Loading
Loading
+3 −27
Original line number Diff line number Diff line
@@ -34,8 +34,6 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioDeviceAttributes;
import android.media.AudioDeviceInfo;
import android.media.AudioManager;
import android.os.HandlerThread;
import android.os.ParcelUuid;
@@ -170,11 +168,6 @@ public class VolumeControlService extends ProfileService {
        Map<Integer, Descriptor> mVolumeOffsets;
    }

    private int mMusicMaxVolume = 0;
    private int mMusicMinVolume = 0;
    private int mVoiceCallMaxVolume = 0;
    private int mVoiceCallMinVolume = 0;

    @VisibleForTesting
    VolumeControlNativeInterface mVolumeControlNativeInterface;
    @VisibleForTesting
@@ -228,11 +221,6 @@ public class VolumeControlService extends ProfileService {
        Objects.requireNonNull(mAudioManager,
                "AudioManager cannot be null when VolumeControlService starts");

        mMusicMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC);
        mMusicMinVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_MUSIC);
        mVoiceCallMaxVolume = mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL);
        mVoiceCallMinVolume = mAudioManager.getStreamMinVolume(AudioManager.STREAM_VOICE_CALL);

        // Start handler thread for state machines
        mStateMachines.clear();
        mStateMachinesThread = new HandlerThread("VolumeControlService.StateMachines");
@@ -684,15 +672,11 @@ public class VolumeControlService extends ProfileService {
    }

    int getDeviceVolume(int streamType, int bleVolume) {
        int bleMaxVolume = 255; // min volume is zero
        int deviceMaxVolume = (streamType == AudioManager.STREAM_VOICE_CALL)
                ? mVoiceCallMaxVolume : mMusicMaxVolume;
        int deviceMinVolume = (streamType == AudioManager.STREAM_VOICE_CALL)
                ? mVoiceCallMinVolume : mMusicMinVolume;
        int deviceMaxVolume = mAudioManager.getStreamMaxVolume(streamType);

        // TODO: Investigate what happens in classic BT when BT volume is changed to zero.
        return (int) Math.floor(
                (double) bleVolume * (deviceMaxVolume - deviceMinVolume) / bleMaxVolume);
        double deviceVolume = (double) (bleVolume * deviceMaxVolume) / LE_AUDIO_MAX_VOL;
        return (int) Math.round(deviceVolume);
    }

    // Copied from AudioService.getBluetoothContextualVolumeStream() and modified it.
@@ -881,14 +865,6 @@ public class VolumeControlService extends ProfileService {
            sm = VolumeControlStateMachine.make(device, this,
                    mVolumeControlNativeInterface, mStateMachinesThread.getLooper());
            mStateMachines.put(device, sm);

            mAudioManager.setDeviceVolumeBehavior(
                    new AudioDeviceAttributes(
                            AudioDeviceAttributes.ROLE_OUTPUT,
                            // Currently, TYPE_BLUETOOTH_A2DP is the only thing that works.
                            AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
                            ""),
                    AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE);
            return sm;
        }
    }
+50 −0
Original line number Diff line number Diff line
@@ -56,6 +56,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeoutException;
import java.util.stream.IntStream;

@MediumTest
@RunWith(AndroidJUnit4.class)
@@ -66,6 +67,11 @@ public class VolumeControlServiceTest {
    private BluetoothDevice mDevice;
    private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap;
    private static final int TIMEOUT_MS = 1000;
    private static final int BT_LE_AUDIO_MAX_VOL = 255;
    private static final int MEDIA_MIN_VOL = 0;
    private static final int MEDIA_MAX_VOL = 25;
    private static final int CALL_MIN_VOL = 1;
    private static final int CALL_MAX_VOL = 8;

    private BroadcastReceiver mVolumeControlIntentReceiver;

@@ -92,6 +98,15 @@ public class VolumeControlServiceTest {

        mAdapter = BluetoothAdapter.getDefaultAdapter();

        doReturn(MEDIA_MIN_VOL).when(mAudioManager)
                .getStreamMinVolume(eq(AudioManager.STREAM_MUSIC));
        doReturn(MEDIA_MAX_VOL).when(mAudioManager)
                .getStreamMaxVolume(eq(AudioManager.STREAM_MUSIC));
        doReturn(CALL_MIN_VOL).when(mAudioManager)
                .getStreamMinVolume(eq(AudioManager.STREAM_VOICE_CALL));
        doReturn(CALL_MAX_VOL).when(mAudioManager)
                .getStreamMaxVolume(eq(AudioManager.STREAM_VOICE_CALL));

        startService();
        mService.mVolumeControlNativeInterface = mNativeInterface;
        mService.mAudioManager = mAudioManager;
@@ -514,6 +529,41 @@ public class VolumeControlServiceTest {
        mService.messageFromNative(stackEvent);
    }

    int getLeAudioVolume(int index, int minIndex, int maxIndex, int streamType) {
        // Note: This has to be the same as mBtHelper.setLeAudioVolume()
        return (int) Math.round((double) index * BT_LE_AUDIO_MAX_VOL / maxIndex);
    }

    void testVolumeCalculations(int streamType, int minIdx, int maxIdx) {
        // Send a message to trigger volume state changed broadcast
        final VolumeControlStackEvent stackEvent = new VolumeControlStackEvent(
                VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED);
        stackEvent.device = null;
        stackEvent.valueInt1 = 1;       // groupId
        stackEvent.valueBool1 = false;  // isMuted
        stackEvent.valueBool2 = true;   // isAutonomous

        IntStream.range(minIdx, maxIdx).forEach(idx -> {
            // Given the reference volume index, set the LeAudio Volume
            stackEvent.valueInt2 = getLeAudioVolume(idx,
                            mAudioManager.getStreamMinVolume(streamType),
                            mAudioManager.getStreamMaxVolume(streamType), streamType);
            mService.messageFromNative(stackEvent);

            // Verify that setting LeAudio Volume, sets the original volume index to Audio FW
            verify(mAudioManager, times(1)).setStreamVolume(eq(streamType), eq(idx), anyInt());
        });
    }

    @Test
    public void testAutonomousVolumeStateChange() {
        doReturn(AudioManager.MODE_IN_CALL).when(mAudioManager).getMode();
        testVolumeCalculations(AudioManager.STREAM_VOICE_CALL, CALL_MIN_VOL, CALL_MAX_VOL);

        doReturn(AudioManager.MODE_NORMAL).when(mAudioManager).getMode();
        testVolumeCalculations(AudioManager.STREAM_MUSIC, MEDIA_MIN_VOL, MEDIA_MAX_VOL);
    }

    /**
     * Test Volume Control cache.
     */