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

Commit 82b01636 authored by Michal Belusiak's avatar Michal Belusiak
Browse files

VCS: Refactor Device Volume API for generic usage

Refactors the Device Volume API to be fully independent of the Group
Volume API, allowing both APIs to coexist without breaking existing logic.

Enhance setDeviceVolume to include mute state control.

Update device volume and mute state in group operations to reflect
any changes in group volume or mute state.

Modify getDeviceVolume to fall back to group volume if device-specific
volume data does not exist (e.g., when a device is newly added to a group).

Modify getGroupVolume to fall back to device volume if group-specific
volume data does not exist (e.g., when group volume has not been set before).

Modify getMute to fall back to group mute state if device-specific
mute state does not exist.

Modify getGroupMute to fall back to device mute state if device-specific
mute state does not exist.

Remove the case for non-autonomous device volume changes from
handleVolumeControlChanged, as no action is required in such scenarios.

Remove group volume restoration in connectionStateChanged#Connected,
as it is already handled in handleVolumeControlChanged.

Bug: 339760224
Bug: 381507732
Test: atest VolumeControlServiceTest
Change-Id: I4ebb08d671025f2ffcce13fc782b56a06a605efe
parent 1f940c59
Loading
Loading
Loading
Loading
+221 −35
Original line number Diff line number Diff line
@@ -31,6 +31,9 @@ import static android.bluetooth.IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVAL
import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
import static android.bluetooth.IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;

import static com.android.bluetooth.flags.Flags.leaudioBroadcastVolumeControlPrimaryGroupOnly;
import static com.android.bluetooth.flags.Flags.vcpDeviceVolumeApiImprovements;

import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElseGet;

@@ -64,7 +67,6 @@ import com.android.bluetooth.btservice.ProfileService;
import com.android.bluetooth.btservice.ServiceFactory;
import com.android.bluetooth.btservice.storage.DatabaseManager;
import com.android.bluetooth.csip.CsipSetCoordinatorService;
import com.android.bluetooth.flags.Flags;
import com.android.bluetooth.le_audio.LeAudioService;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -117,9 +119,10 @@ public class VolumeControlService extends ProfileService {
            new HashMap<>();
    private final Map<BluetoothDevice, VolumeControlInputDescriptor> mAudioInputs =
            new ConcurrentHashMap<>();
    private final Map<Integer, Integer> mGroupVolumeCache = new HashMap<>();
    private final Map<Integer, Boolean> mGroupMuteCache = new HashMap<>();
    private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new HashMap<>();
    private final Map<Integer, Integer> mGroupVolumeCache = new ConcurrentHashMap<>();
    private final Map<Integer, Boolean> mGroupMuteCache = new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, Integer> mDeviceVolumeCache = new ConcurrentHashMap<>();
    private final Map<BluetoothDevice, Boolean> mDeviceMuteCache = new ConcurrentHashMap<>();

    @VisibleForTesting ServiceFactory mFactory = new ServiceFactory();

@@ -198,6 +201,7 @@ public class VolumeControlService extends ProfileService {
        mGroupVolumeCache.clear();
        mGroupMuteCache.clear();
        mDeviceVolumeCache.clear();
        mDeviceMuteCache.clear();

        synchronized (mCallbacks) {
            mCallbacks.kill();
@@ -529,7 +533,7 @@ public class VolumeControlService extends ProfileService {
        mNativeInterface.setExtAudioOutVolumeOffset(device, instanceId, volumeOffset);
    }

    void setDeviceVolume(BluetoothDevice device, int volume, boolean isGroupOp) {
    public void setDeviceVolume(BluetoothDevice device, int volume, boolean isGroupOp) {
        Log.d(
                TAG,
                "setDeviceVolume: " + device + ", volume: " + volume + ", isGroupOp: " + isGroupOp);
@@ -551,6 +555,35 @@ public class VolumeControlService extends ProfileService {
            Log.i(TAG, "Setting individual device volume");
            mDeviceVolumeCache.put(device, volume);
            mNativeInterface.setVolume(device, volume);

            if (vcpDeviceVolumeApiImprovements()) {
                // We only receive the volume change and mute state needs to be acquired manually
                Boolean isStreamMute =
                        mAudioManager.isStreamMute(getBluetoothContextualVolumeStream());
                adjustDeviceMute(device, volume, isStreamMute);
            }
        }
    }

    private void adjustDeviceMute(BluetoothDevice device, int volume, Boolean isStreamMute) {
        Boolean isMute = getMute(device);
        if (!isMute.equals(isStreamMute)) {
            Log.w(
                    TAG,
                    "Mute state mismatch, stream mute: "
                            + isStreamMute
                            + ", device mute: "
                            + isMute
                            + ", new volume: "
                            + volume);
            if (isStreamMute) {
                Log.i(TAG, "Mute the device " + device);
                mute(device);
            }
            if (!isStreamMute && (volume > 0)) {
                Log.i(TAG, "Unmute the device " + device);
                unmute(device);
            }
        }
    }

@@ -562,7 +595,14 @@ public class VolumeControlService extends ProfileService {
            return;
        }

        synchronized (mDeviceVolumeCache) {
            mGroupVolumeCache.put(groupId, volume);
            if (vcpDeviceVolumeApiImprovements()) {
                for (BluetoothDevice dev : getGroupDevices(groupId)) {
                    mDeviceVolumeCache.put(dev, volume);
                }
            }
        }
        mNativeInterface.setGroupVolume(groupId, volume);

        // We only receive the volume change and mute state needs to be acquired manually
@@ -594,22 +634,61 @@ public class VolumeControlService extends ProfileService {
                Log.i(TAG, "Unmute the group " + groupId);
                unmuteGroup(groupId);
            }
        } else if (vcpDeviceVolumeApiImprovements()) {
            for (BluetoothDevice device : getGroupDevices(groupId)) {
                adjustDeviceMute(device, volume, isStreamMute);
            }
        }
    }

    /**
     * Get group cached volume. If not cached, then try to read from any device from this group.
     *
     * @param groupId the group identifier
     * @return the cached volume
     */
    public int getGroupVolume(int groupId) {
        if (vcpDeviceVolumeApiImprovements()) {
            synchronized (mDeviceVolumeCache) {
                Integer volume = mGroupVolumeCache.get(groupId);
                if (volume != null) {
                    return volume;
                }
                Log.w(TAG, "No group volume available");
                for (BluetoothDevice device : getGroupDevices(groupId)) {
                    volume = mDeviceVolumeCache.get(device);
                    if (volume != null) {
                        Log.w(TAG, "Volume taken from device: " + device);
                        return volume;
                    }
                }
                return VOLUME_CONTROL_UNKNOWN_VOLUME;
            }
        } else {
            return mGroupVolumeCache.getOrDefault(groupId, VOLUME_CONTROL_UNKNOWN_VOLUME);
        }
    }

    /**
     * Get device cached volume.
     * Get device cached volume. If not cached, then try to read from its group.
     *
     * @param device the device
     * @return the cached volume
     */
    public int getDeviceVolume(BluetoothDevice device) {
        if (vcpDeviceVolumeApiImprovements()) {
            synchronized (mDeviceVolumeCache) {
                Integer volume = mDeviceVolumeCache.get(device);
                if (volume != null) {
                    return volume;
                }
                return mGroupVolumeCache.getOrDefault(
                        getGroupId(device), VOLUME_CONTROL_UNKNOWN_VOLUME);
            }
        } else {
            return mDeviceVolumeCache.getOrDefault(device, VOLUME_CONTROL_UNKNOWN_VOLUME);
        }
    }

    /**
     * This should be called by LeAudioService when LE Audio group change it active state.
@@ -634,27 +713,75 @@ public class VolumeControlService extends ProfileService {
    }

    /**
     * Get device cached mute status. If not cached, then try to read from its group.
     *
     * @param device the device
     * @return mute status
     */
    public Boolean getMute(BluetoothDevice device) {
        synchronized (mDeviceMuteCache) {
            Boolean isMute = mDeviceMuteCache.get(device);
            if (isMute != null) {
                return isMute;
            }
            return mGroupMuteCache.getOrDefault(getGroupId(device), false);
        }
    }

    /**
     * Get group cached mute status. If not cached, then try to read from any device from this
     * group.
     *
     * @param groupId the group identifier
     * @return mute status
     */
    public Boolean getGroupMute(int groupId) {
        if (vcpDeviceVolumeApiImprovements()) {
            synchronized (mDeviceMuteCache) {
                Boolean isMute = mGroupMuteCache.get(groupId);
                if (isMute != null) {
                    return isMute;
                }
                for (BluetoothDevice device : getGroupDevices(groupId)) {
                    isMute = mDeviceMuteCache.get(device);
                    if (isMute != null) {
                        return isMute;
                    }
                }
                return false;
            }
        } else {
            return mGroupMuteCache.getOrDefault(groupId, false);
        }
    }

    public void mute(BluetoothDevice device) {
        mDeviceMuteCache.put(device, true);
        mNativeInterface.mute(device);
    }

    public void muteGroup(int groupId) {
        synchronized (mDeviceMuteCache) {
            mGroupMuteCache.put(groupId, true);
            for (BluetoothDevice dev : getGroupDevices(groupId)) {
                mDeviceMuteCache.put(dev, true);
            }
        }
        mNativeInterface.muteGroup(groupId);
    }

    public void unmute(BluetoothDevice device) {
        mDeviceMuteCache.put(device, false);
        mNativeInterface.unmute(device);
    }

    public void unmuteGroup(int groupId) {
        synchronized (mDeviceMuteCache) {
            mGroupMuteCache.put(groupId, false);
            for (BluetoothDevice dev : getGroupDevices(groupId)) {
                mDeviceMuteCache.put(dev, false);
            }
        }
        mNativeInterface.unmuteGroup(groupId);
    }

@@ -737,6 +864,20 @@ public class VolumeControlService extends ProfileService {
        }

        // If group volume has already changed, the new group member should set it
        if (vcpDeviceVolumeApiImprovements()) {
            int volume = getDeviceVolume(device);
            if (volume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(TAG, "Setting device/group volume:" + volume + " to the device:" + device);
                setDeviceVolume(device, volume, false);
                Boolean isDeviceMuted = getMute(device);
                Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
                if (isDeviceMuted) {
                    mute(device);
                } else {
                    unmute(device);
                }
            }
        } else {
            Integer groupVolume = getGroupVolume(groupId);
            if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
@@ -751,6 +892,7 @@ public class VolumeControlService extends ProfileService {
                mNativeInterface.unmute(device);
            }
        }
    }

    void updateGroupCacheAndAudioSystem(int groupId, int volume, boolean mute, boolean showInUI) {
        Log.d(
@@ -769,14 +911,22 @@ public class VolumeControlService extends ProfileService {
            return;
        }

        synchronized (mDeviceVolumeCache) {
            mGroupVolumeCache.put(groupId, volume);
            mGroupMuteCache.put(groupId, mute);
            if (vcpDeviceVolumeApiImprovements()) {
                for (BluetoothDevice dev : getGroupDevices(groupId)) {
                    mDeviceVolumeCache.put(dev, volume);
                    mDeviceMuteCache.put(dev, mute);
                }
            }
        }

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService != null) {
            int currentlyActiveGroupId = leAudioService.getActiveGroupId();
            if (currentlyActiveGroupId == GROUP_ID_INVALID || groupId != currentlyActiveGroupId) {
                if (!Flags.leaudioBroadcastVolumeControlPrimaryGroupOnly()) {
                if (!leaudioBroadcastVolumeControlPrimaryGroupOnly()) {
                    Log.i(
                            TAG,
                            "Skip updating to audio system if not updating volume for current"
@@ -838,9 +988,6 @@ public class VolumeControlService extends ProfileService {
            return;
        }

        int groupVolume = getGroupVolume(groupId);
        Boolean groupMute = getGroupMute(groupId);

        if (isAutonomous && device != null) {
            Log.i(
                    TAG,
@@ -863,9 +1010,27 @@ public class VolumeControlService extends ProfileService {
            }

            // Reset flag is used
            if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
            int groupVolume = getGroupVolume(groupId);
            int deviceVolume = getDeviceVolume(device);
            if (!vcpDeviceVolumeApiImprovements() && groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(TAG, "Setting group volume: " + groupVolume + " to the device: " + device);
                setGroupVolume(groupId, groupVolume);
            } else if (vcpDeviceVolumeApiImprovements()
                    && deviceVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(
                        TAG,
                        "Setting device/group volume: "
                                + deviceVolume
                                + " to the device: "
                                + device);
                setDeviceVolume(device, deviceVolume, false);
                Boolean isDeviceMuted = getMute(device);
                Log.i(TAG, "Setting mute:" + isDeviceMuted + " to " + device);
                if (isDeviceMuted) {
                    mute(device);
                } else {
                    unmute(device);
                }
            } else {
                int systemVolume = getBleVolumeFromCurrentStream();
                Log.i(TAG, "Setting system volume: " + systemVolume + " to the group: " + groupId);
@@ -894,13 +1059,16 @@ public class VolumeControlService extends ProfileService {
            }
        }

        if (!isAutonomous) {
        if (!vcpDeviceVolumeApiImprovements() && !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
             * reconnection. Make sure device has group volume. Also it might happen that
             * remote side send us wrong value - lets check it.
             */

            int groupVolume = getGroupVolume(groupId);
            Boolean groupMute = getGroupMute(groupId);

            if ((groupVolume == volume) && (groupMute == mute)) {
                Log.i(TAG, " Volume:" + volume + ", mute:" + mute + " confirmed by remote side.");
                return;
@@ -936,7 +1104,9 @@ public class VolumeControlService extends ProfileService {
                                + " expected volume: "
                                + groupVolume);
            }
        } else {
        }

        if (isAutonomous && device == null) {
            /* Received group notification for autonomous change. Update cache and audio system. */
            updateGroupCacheAndAudioSystem(groupId, volume, mute, true);
        }
@@ -1375,10 +1545,13 @@ public class VolumeControlService extends ProfileService {
            int broadcastVolume = VOLUME_CONTROL_UNKNOWN_VOLUME;
            if (volume.isPresent()) {
                broadcastVolume = volume.get();
                if (!vcpDeviceVolumeApiImprovements()) {
                    mDeviceVolumeCache.put(dev, broadcastVolume);
                }
            } else {
                broadcastVolume = getDeviceVolume(dev);
                if (broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) {
                if (!vcpDeviceVolumeApiImprovements()
                        && broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) {
                    broadcastVolume = getGroupVolume(getGroupId(dev));
                }
            }
@@ -1474,7 +1647,7 @@ public class VolumeControlService extends ProfileService {
                Log.d(TAG, device + " is unbond. Remove state machine");
                removeStateMachine(device);
            }
        } else if (toState == STATE_CONNECTED) {
        } else if (!vcpDeviceVolumeApiImprovements() && toState == STATE_CONNECTED) {
            // Restore the group volume if it was changed while the device was not yet connected.
            Integer groupId = getGroupId(device);
            if (groupId != GROUP_ID_INVALID) {
@@ -2025,5 +2198,18 @@ public class VolumeControlService extends ProfileService {
                            + ", mute: "
                            + getGroupMute(entry.getKey()));
        }

        if (vcpDeviceVolumeApiImprovements()) {
            for (Map.Entry<BluetoothDevice, Integer> entry : mDeviceVolumeCache.entrySet()) {
                ProfileService.println(
                        sb,
                        "    Device: "
                                + entry.getKey()
                                + " volume: "
                                + entry.getValue()
                                + ", mute: "
                                + getMute(entry.getKey()));
            }
        }
    }
}
+140 −11
Original line number Diff line number Diff line
@@ -58,6 +58,7 @@ import android.media.AudioManager;
import android.os.Binder;
import android.os.ParcelUuid;
import android.os.test.TestLooper;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
import android.platform.test.flag.junit.SetFlagsRule;

@@ -461,7 +462,8 @@ public class VolumeControlServiceTest {
    }

    @Test
    public void volumeCache() {
    @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
    public void volumeCacheDeprecated() {
        int groupId = 1;
        int volume = 6;

@@ -477,6 +479,56 @@ public class VolumeControlServiceTest {
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(volume);
    }

    @Test
    @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
    public void volumeCache() {
        int groupId = 1;
        int groupVolume = 6;
        int devOneVolume = 20;
        int devTwoVolume = 30;

        // Both devices are in the same group
        when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
        when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
        when(mCsipService.getGroupDevicesOrdered(groupId))
                .thenReturn(Arrays.asList(mDevice, mDeviceTwo));

        assertThat(mService.getGroupVolume(groupId)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(VOLUME_CONTROL_UNKNOWN_VOLUME);

        // Set group volume
        mService.setGroupVolume(groupId, groupVolume);
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume);

        // Send autonomous volume change.
        int autonomousVolume = 10;
        generateVolumeStateChanged(null, groupId, autonomousVolume, 0, false, true);
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(autonomousVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(autonomousVolume);

        // Set first device volume
        mService.setDeviceVolume(mDevice, devOneVolume, false);
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(devOneVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(autonomousVolume);

        // Set second device volume
        mService.setDeviceVolume(mDeviceTwo, devTwoVolume, false);
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(autonomousVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(devOneVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(devTwoVolume);

        // Set group volume again
        mService.setGroupVolume(groupId, groupVolume);
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume);
    }

    @Test
    public void activeGroupChange() {
        int groupId_1 = 1;
@@ -509,7 +561,8 @@ public class VolumeControlServiceTest {
    }

    @Test
    public void muteCache() {
    @DisableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
    public void muteCacheDeprecated() {
        int groupId = 1;
        int volume = 6;

@@ -531,6 +584,61 @@ public class VolumeControlServiceTest {
        assertThat(mService.getGroupMute(groupId)).isFalse();
    }

    @Test
    @EnableFlags(Flags.FLAG_VCP_DEVICE_VOLUME_API_IMPROVEMENTS)
    public void muteCache() {
        int groupId = 1;
        int groupVolume = 6;

        // Both devices are in the same group
        when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId);
        when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId);
        when(mCsipService.getGroupDevicesOrdered(groupId))
                .thenReturn(Arrays.asList(mDevice, mDeviceTwo));

        assertThat(mService.getGroupMute(groupId)).isFalse();
        assertThat(mService.getMute(mDevice)).isFalse();
        assertThat(mService.getMute(mDeviceTwo)).isFalse();

        // Send autonomous volume change
        generateVolumeStateChanged(null, groupId, groupVolume, 0, false, true);

        // Mute
        mService.muteGroup(groupId);
        assertThat(mService.getGroupMute(groupId)).isTrue();
        assertThat(mService.getMute(mDevice)).isTrue();
        assertThat(mService.getMute(mDeviceTwo)).isTrue();

        // Make sure the volume is kept even when muted
        assertThat(mService.getGroupVolume(groupId)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDevice)).isEqualTo(groupVolume);
        assertThat(mService.getDeviceVolume(mDeviceTwo)).isEqualTo(groupVolume);

        // Send autonomous unmute
        generateVolumeStateChanged(null, groupId, groupVolume, 0, false, true);
        assertThat(mService.getGroupMute(groupId)).isFalse();
        assertThat(mService.getMute(mDevice)).isFalse();
        assertThat(mService.getMute(mDeviceTwo)).isFalse();

        // Mute first device
        mService.mute(mDevice);
        assertThat(mService.getGroupMute(groupId)).isFalse();
        assertThat(mService.getMute(mDevice)).isTrue();
        assertThat(mService.getMute(mDeviceTwo)).isFalse();

        // Mute second device
        mService.mute(mDeviceTwo);
        assertThat(mService.getGroupMute(groupId)).isFalse();
        assertThat(mService.getMute(mDevice)).isTrue();
        assertThat(mService.getMute(mDeviceTwo)).isTrue();

        // Unmute group should unmute devices even if group is unmuted
        mService.unmuteGroup(groupId);
        assertThat(mService.getGroupMute(groupId)).isFalse();
        assertThat(mService.getMute(mDevice)).isFalse();
        assertThat(mService.getMute(mDeviceTwo)).isFalse();
    }

    /** Test Volume Control with muted stream. */
    @Test
    public void volumeChangeWhileMuted() {
@@ -634,8 +742,12 @@ public class VolumeControlServiceTest {
                initialAutonomousFlag);

        inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
        if (Flags.vcpDeviceVolumeApiImprovements()) {
            verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volumeDevice));
        } else {
            verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volumeDevice));
        }
    }

    private void testConnectedDeviceWithResetFlag(
            int resetVolumeDeviceOne, int resetVolumeDeviceTwo) {
@@ -698,8 +810,12 @@ public class VolumeControlServiceTest {
                initialAutonomousFlag);

        inOrderAudio.verify(mAudioManager, never()).setStreamVolume(anyInt(), anyInt(), anyInt());
        if (Flags.vcpDeviceVolumeApiImprovements()) {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(expectedAfVol));
        } else {
            inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(expectedAfVol));
        }
    }

    /** Test if phone will set volume which is read from the buds */
    @Test
@@ -742,9 +858,13 @@ public class VolumeControlServiceTest {
        assertThat(mService.getDevices()).contains(mDeviceTwo);
        generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true);

        if (Flags.vcpDeviceVolumeApiImprovements()) {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
        } else {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
            inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume));
        }
    }

    /**
     * Test setting volume for a new group member who is discovered after the volume level for a
@@ -817,10 +937,15 @@ public class VolumeControlServiceTest {
        generateVolumeStateChanged(mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, volume_2, 0, false, true);

        // Check if new device was muted
        if (Flags.vcpDeviceVolumeApiImprovements()) {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume));
            inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo));
        } else {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(volume));
            inOrderNative.verify(mNativeInterface).mute(eq(mDeviceTwo));
            inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(volume));
        }
    }

    /**
     * Test setting volume to 0 for a new group member who is discovered after the volume level for
@@ -1064,8 +1189,12 @@ public class VolumeControlServiceTest {
        generateVolumeStateChanged(
                mDeviceTwo, LE_AUDIO_GROUP_ID_INVALID, groupVolume, 0, false, true);

        if (Flags.vcpDeviceVolumeApiImprovements()) {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
        } else {
            inOrderNative.verify(mNativeInterface).setVolume(eq(mDeviceTwo), eq(groupVolume));
            inOrderNative.verify(mNativeInterface).setGroupVolume(eq(groupId), eq(groupVolume));
        }

        // Generate events for both devices
        generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100);