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

Commit 7221ec0d authored by Tomoki Yonezawa's avatar Tomoki Yonezawa Committed by Vlad Popa
Browse files

audio: Fix A2DP volume issue with multiple outputs



Issue:
When the user controls the volume using the A2DP device's volume button
during multiple audio outputs, the volume index of the A2DP device does
not change. For example, when audio is output simultaneously from both
the speaker and an A2DP device, the Audio Framework's volume index
does not update even if the user adjusts the volume on the A2DP device.
The expected behavior is that the volume index for the A2DP device
should change.

Root Cause:
The Audio Framework selects a single device for volume updates.
During multiple audio outputs, it may select a device other than
the A2DP device. Then, the Audio Framework does not update the
volume index for the A2DP device.

Solution:
When the user controls the volume using the A2DP device button,
the AudioService ensures that the A2DP device is selected as the target
for volume adjustments.

Remark:
Similar issues may also occur with absolute volume devices.

Bug: 399307939
Flag: EXEMPT bugfix
Test: manual - control the volume using the A2DP device's volume
button during multiple audio outputs

Change-Id: Ib8ad42278eacc2cd5969e78bd02cd43c99ec371d
Signed-off-by: default avatarTomoki Yonezawa <Tomoki.Yonezawa@sony.com>
parent 9ae9bad4
Loading
Loading
Loading
Loading
+59 −16
Original line number Diff line number Diff line
@@ -67,6 +67,7 @@ import static android.provider.Settings.Secure.VOLUME_HUSH_OFF;
import static android.provider.Settings.Secure.VOLUME_HUSH_VIBRATE;
import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
import static com.android.media.audio.Flags.absVolumePrioritizesAbsDevice;
import static com.android.media.audio.Flags.alarmMinVolumeZero;
import static com.android.media.audio.Flags.asDeviceConnectionFailure;
import static com.android.media.audio.Flags.audioserverPermissions;
@@ -4014,6 +4015,11 @@ public class AudioService extends IAudioService.Stub
        return true;
    }
    private static boolean flagsContainsAbsoluteDevices(int flags) {
        return (flags & (AudioManager.FLAG_BLUETOOTH_ABS_VOLUME
                | AudioManager.FLAG_ABSOLUTE_VOLUME)) != 0;
    }
    /** Retain API for unsupported app usage */
    public void adjustStreamVolume(int streamType, int direction, int flags,
            String callingPackage) {
@@ -4103,7 +4109,8 @@ public class AudioService extends IAudioService.Stub
        VolumeStreamState streamState = getVssForStreamOrDefault(streamTypeAlias);
        final AudioDeviceAttributes deviceAttr = getDeviceAttributesForStream(streamTypeAlias);
        final AudioDeviceAttributes deviceAttr = getDeviceAttributesForStream(streamTypeAlias,
                flagsContainsAbsoluteDevices(flags));
        final int deviceType = deviceAttr.getInternalType();
        int aliasIndex = streamState.getIndex(deviceType);
@@ -4457,7 +4464,8 @@ public class AudioService extends IAudioService.Stub
                    // Unmute all aliasted streams
                    muteAliasStreams(streamAlias, false);
                }
                final int device = getDeviceForStream(streamAlias);
                final int device = getDeviceForStream(streamAlias,
                        flagsContainsAbsoluteDevices(flags));
                final int index = streamState.getIndex(device);
                sendVolumeUpdate(streamAlias, index, index, flags, device);
            }
@@ -4762,7 +4770,7 @@ public class AudioService extends IAudioService.Stub
            return;
        }
        final int currDev = getDeviceForStream(streamType);
        final int currDev = getDeviceForStream(streamType, /*selectAbsoluteDevices=*/true);
        AudioService.sVolumeLogger.enqueue(
                new DeviceVolumeEvent(streamType, vi.getVolumeIndex(), ada, callingPackage,
@@ -4961,7 +4969,7 @@ public class AudioService extends IAudioService.Stub
        if (ada == null) {
            // call was already logged in setDeviceVolume()
            ada = getDeviceAttributesForStream(streamType);
            ada = getDeviceAttributesForStream(streamType, flagsContainsAbsoluteDevices(flags));
            sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                    index/*val1*/, flags/*val2*/,
                    getStreamVolume(streamType, ada.getInternalType()) /*val3*/,
@@ -5227,6 +5235,8 @@ public class AudioService extends IAudioService.Stub
        pw.println("\tandroid.media.audio.scoManagedByAudio:"
                + scoManagedByAudio());
        pw.println("\tcom.android.media.audio.absVolumeIndexFix - EOL");
        pw.println("\tcom.android.media.audio.absVolumePrioritizesAbsDevice:"
                + absVolumePrioritizesAbsDevice());
        pw.println("\tcom.android.media.audio.vgsVssSyncMuteOrder - EOL");
        pw.println("\tcom.android.media.audio.replaceStreamBtSco:"
                + replaceStreamBtSco());
@@ -5381,7 +5391,7 @@ public class AudioService extends IAudioService.Stub
        }
        final AudioDeviceAttributes deviceAttr = (ada == null)
                ? getDeviceAttributesForStream(streamType)
                ? getDeviceAttributesForStream(streamType, flagsContainsAbsoluteDevices(flags))
                : ada;
        final int deviceType = deviceAttr.getInternalType();
        int oldIndex;
@@ -8197,38 +8207,56 @@ public class AudioService extends IAudioService.Stub
        }
    }
    /** @see AudioService#getDeviceForStream(int, int) */
    public int getDeviceForStream(int stream) {
        return getDeviceForStream(stream, /*selectAbsoluteDevices=*/false);
    }
    /**
     * Returns device associated with the stream volume.
     *
     * Only public for mocking/spying, do not call outside of AudioService.
     * <p>Only public for mocking/spying, do not call outside of AudioService.
     * Device volume aliasing means DEVICE_OUT_SPEAKER may be returned for
     * DEVICE_OUT_SPEAKER_SAFE.
     *
     * @param stream for which to check the device
     * @param selectAbsoluteDevices used to prioritize absolute volume devices if available
     */
    @VisibleForTesting
    public int getDeviceForStream(int stream) {
    public int getDeviceForStream(int stream, boolean selectAbsoluteDevices) {
        stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceForStream");
        return selectOneAudioDevice(getDeviceSetForStream(stream));
        return selectOneAudioDevice(getDeviceSetForStream(stream), selectAbsoluteDevices);
    }
    private AudioDeviceAttributes getDeviceAttributesForStream(int stream) {
        return getDeviceAttributesForStream(stream, /*selectAbsoluteDevices=*/false);
    }
    /**
     * Returns {@link AudioDeviceAttributes} associated with the stream volume.
     *
     * Only public for mocking/spying, do not call outside of AudioService.
     * <p>Only public for mocking/spying, do not call outside of AudioService.
     * Device volume aliasing means DEVICE_OUT_SPEAKER may be returned for
     * DEVICE_OUT_SPEAKER_SAFE.
     *
     * @param stream for which to check the device
     * @param selectAbsoluteDevices used to prioritize absolute volume devices if available
     */
    @VisibleForTesting
    public AudioDeviceAttributes getDeviceAttributesForStream(int stream) {
    public AudioDeviceAttributes getDeviceAttributesForStream(int stream,
            boolean selectAbsoluteDevices) {
        stream = replaceBtScoStreamWithVoiceCall(stream, "getDeviceForStream");
        return selectOneAudioDeviceAttribute(getDeviceSetForStream(stream));
        return selectOneAudioDeviceAttribute(getDeviceSetForStream(stream), selectAbsoluteDevices);
    }
    /*
     * Must match native apm_extract_one_audio_device() used in getDeviceForVolume()
     * or the wrong device volume may be adjusted.
     */
    private static int selectOneAudioDevice(Set<AudioDeviceAttributes> deviceSet) {
        return selectOneAudioDeviceAttribute(deviceSet).getInternalType();
    private int selectOneAudioDevice(Set<AudioDeviceAttributes> deviceSet,
            boolean selectAbsoluteDevices) {
        return selectOneAudioDeviceAttribute(deviceSet, selectAbsoluteDevices).getInternalType();
    }
    /*
@@ -8236,22 +8264,37 @@ public class AudioService extends IAudioService.Stub
     * or the wrong device volume may be adjusted.
     */
    @NonNull
    private static AudioDeviceAttributes selectOneAudioDeviceAttribute(
            Set<AudioDeviceAttributes> deviceSet) {
    private AudioDeviceAttributes selectOneAudioDeviceAttribute(
            Set<AudioDeviceAttributes> deviceSet, boolean selectAbsoluteDevices) {
        if (deviceSet.isEmpty()) {
            return new AudioDeviceAttributes(AudioManager.DEVICE_NONE, /*address=*/"");
        } else if (deviceSet.size() == 1) {
            return deviceSet.iterator().next();
        } else {
            // Multiple device selection is either:
            //  - absolute volume device + one other device: give priority to absolute volume
            // device if absolute volume flag is set
            //  - dock + one other device: give priority to dock in this case.
            //  - speaker + one other device: give priority to speaker in this case.
            //  - one A2DP device + another device: happens with duplicated output. In this case
            // retain the device on the A2DP output as the other must not correspond to an active
            // selection if not the speaker.
            //  - HDMI-CEC system audio mode only output: give priority to available item in order.
            Optional<AudioDeviceAttributes> ada;
            if (absVolumePrioritizesAbsDevice() && selectAbsoluteDevices) {
                ada = deviceSet.stream().filter(
                        device -> isA2dpAbsoluteVolumeDevice(device.getInternalType())
                                || AudioSystem.isLeAudioDeviceType(
                                device.getInternalType())).findFirst();
                if (ada.isPresent()) {
                    return ada.get();
                }
                ada = deviceSet.stream().filter(
                        device -> isAbsoluteVolumeDevice(device.getInternalType())).findFirst();
                if (ada.isPresent()) {
                    return ada.get();
                }
            }
            if ((ada = deviceSet.stream().filter(device -> device.getInternalType()
                    == AudioSystem.DEVICE_OUT_DGTL_DOCK_HEADSET).findFirst()).isPresent()) {
                return ada.get();
+3 −2
Original line number Diff line number Diff line
@@ -111,12 +111,13 @@ public class AbsoluteVolumeBehaviorTest {
                mTestLooper.getLooper(), mock(AppOpsManager.class), mock(PermissionEnforcer.class),
                mock(AudioServerPermissionProvider.class), r -> r.run()) {
            @Override
            public int getDeviceForStream(int stream) {
            public int getDeviceForStream(int stream, boolean selectAbsoluteDevices) {
                return AudioSystem.DEVICE_OUT_SPEAKER;
            }

            @Override
            public AudioDeviceAttributes getDeviceAttributesForStream(int stream) {
            public AudioDeviceAttributes getDeviceAttributesForStream(int stream,
                    boolean selectAbsoluteDevices) {
                return DEVICE_SPEAKER_OUT;
            }
        };
+3 −2
Original line number Diff line number Diff line
@@ -179,7 +179,7 @@ public class VolumeHelperTest {
        }

        @Override
        public int getDeviceForStream(int stream) {
        public int getDeviceForStream(int stream, boolean selectAbsoluteDevices) {
            if (mStreamDevice.indexOfKey(stream) < 0) {
                return DEVICE_OUT_SPEAKER;
            }
@@ -187,7 +187,8 @@ public class VolumeHelperTest {
        }

        @Override
        public AudioDeviceAttributes getDeviceAttributesForStream(int stream) {
        public AudioDeviceAttributes getDeviceAttributesForStream(int stream,
                boolean selectAbsoluteDevices) {
            if (mStreamDevice.indexOfKey(stream) < 0) {
                return new AudioDeviceAttributes(DEVICE_OUT_SPEAKER, "speaker");
            }