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

Commit 471022cf authored by Michal Belusiak's avatar Michal Belusiak
Browse files

VCS: Add wrappers for getGroupId and getGroupDevices

Add wrappers for getGroupId and getGroupDevices to simplify code and
ensure consistent data retrieval from the CSIP or LeAudioService.

Read groups from CSIP first as in phone policy it is connected first.
LeAudioService get group IDs from it if available.

Improve some unit tests by use inOrder.
Use service instead of binder if test is not focused on it.
Add to unit test generateVolumeStateChanged after connection as it
always happen in current code base.
Add new unit tests for wrappers.

Bug: 373377157
Flag: EXEMPT; Refactor covered with unit tests
Test: atest VolumeControlServiceTest
Change-Id: Ia131e035d2e2681c0ed52e6760f6bfd94ec3de77
parent 6b667006
Loading
Loading
Loading
Loading
+120 −101
Original line number Diff line number Diff line
@@ -27,6 +27,9 @@ import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_UNKNOWN;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTED;
import static android.bluetooth.BluetoothProfile.STATE_CONNECTING;
import static android.bluetooth.BluetoothProfile.STATE_DISCONNECTED;
import static android.bluetooth.IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID;
import static android.bluetooth.IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID;
import static android.bluetooth.IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;

import static java.util.Objects.requireNonNull;
import static java.util.Objects.requireNonNullElseGet;
@@ -41,8 +44,6 @@ import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothUuid;
import android.bluetooth.IAudioInputCallback;
import android.bluetooth.IBluetoothCsipSetCoordinator;
import android.bluetooth.IBluetoothLeAudio;
import android.bluetooth.IBluetoothVolumeControl;
import android.bluetooth.IBluetoothVolumeControlCallback;
import android.content.AttributionSource;
@@ -94,6 +95,8 @@ public class VolumeControlService extends ProfileService {
     * User Set Volume Setting means that remote keeps volume in its cache. */
    @VisibleForTesting static final int VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK = 0x01;

    private static final int GROUP_ID_INVALID = -1;

    private static VolumeControlService sVolumeControlService;

    @VisibleForTesting
@@ -268,6 +271,64 @@ public class VolumeControlService extends ProfileService {
        return true;
    }

    private int getGroupId(BluetoothDevice device) {
        if (device == null) {
            return GROUP_ID_INVALID;
        }

        CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
        if (csipClient != null) {
            int groupId = csipClient.getGroupId(device, BluetoothUuid.CAP);
            if (groupId != CSIS_GROUP_ID_INVALID) {
                return groupId;
            }
        } else {
            Log.w(TAG, "CSIP not available");
        }

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService != null) {
            int groupId = leAudioService.getGroupId(device);
            if (groupId != LE_AUDIO_GROUP_ID_INVALID) {
                return groupId;
            }
        } else {
            Log.w(TAG, "leAudioService not available");
        }

        return GROUP_ID_INVALID;
    }

    private List<BluetoothDevice> getGroupDevices(int groupId) {
        List<BluetoothDevice> result = new ArrayList<>();

        if (groupId == GROUP_ID_INVALID) {
            return result;
        }

        CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
        if (csipClient != null) {
            result = csipClient.getGroupDevicesOrdered(groupId);
            if (!result.isEmpty()) {
                return result;
            }
        } else {
            Log.w(TAG, "CSIP not available");
        }

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService != null) {
            result = leAudioService.getGroupDevices(groupId);
            if (!result.isEmpty()) {
                return result;
            }
        } else {
            Log.w(TAG, "leAudioService not available");
        }

        return result;
    }

    public List<BluetoothDevice> getConnectedDevices() {
        List<BluetoothDevice> devices = new ArrayList<>();
        synchronized (mStateMachines) {
@@ -282,16 +343,11 @@ public class VolumeControlService extends ProfileService {

    private List<BluetoothDevice> getConnectedDevices(int groupId) {
        List<BluetoothDevice> devices = new ArrayList<>();
        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService == null) {
            Log.e(TAG, "leAudioService not available");
            return devices;
        }
        synchronized (mStateMachines) {
            for (BluetoothDevice dev : leAudioService.getGroupDevices(groupId)) {
            for (BluetoothDevice dev : getGroupDevices(groupId)) {
                VolumeControlStateMachine sm = mStateMachines.get(dev);
                if (sm != null && sm.isConnected()) {
                    devices.add(sm.getDevice());
                    devices.add(dev);
                }
            }
        }
@@ -478,15 +534,14 @@ public class VolumeControlService extends ProfileService {
                TAG,
                "setDeviceVolume: " + device + ", volume: " + volume + ", isGroupOp: " + isGroupOp);

        if (isGroupOp) {
            LeAudioService leAudioService = mFactory.getLeAudioService();
            if (leAudioService == null) {
                Log.e(TAG, "leAudioService not available");
        if (volume < 0) {
            Log.w(TAG, "Tried to set invalid volume " + volume + ". Ignored.");
            return;
        }

            int groupId = leAudioService.getGroupId(device);
            if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
        if (isGroupOp) {
            int groupId = getGroupId(device);
            if (groupId == GROUP_ID_INVALID) {
                Log.e(TAG, "Device not a part of a group");
                return;
            }
@@ -511,7 +566,7 @@ public class VolumeControlService extends ProfileService {
        mNativeInterface.setGroupVolume(groupId, volume);

        // We only receive the volume change and mute state needs to be acquired manually
        Boolean isGroupMute = mGroupMuteCache.getOrDefault(groupId, false);
        Boolean isGroupMute = getGroupMute(groupId);
        Boolean isStreamMute = mAudioManager.isStreamMute(getBluetoothContextualVolumeStream());

        /* Note: AudioService keeps volume levels for each stream and for each device type,
@@ -543,8 +598,7 @@ public class VolumeControlService extends ProfileService {
    }

    public int getGroupVolume(int groupId) {
        return mGroupVolumeCache.getOrDefault(
                groupId, IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
        return mGroupVolumeCache.getOrDefault(groupId, VOLUME_CONTROL_UNKNOWN_VOLUME);
    }

    /**
@@ -554,8 +608,7 @@ public class VolumeControlService extends ProfileService {
     * @return the cached volume
     */
    public int getDeviceVolume(BluetoothDevice device) {
        return mDeviceVolumeCache.getOrDefault(
                device, IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
        return mDeviceVolumeCache.getOrDefault(device, VOLUME_CONTROL_UNKNOWN_VOLUME);
    }

    /**
@@ -574,7 +627,7 @@ public class VolumeControlService extends ProfileService {
        int groupVolume = getGroupVolume(groupId);
        Boolean groupMute = getGroupMute(groupId);

        if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
        if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
            /* Don't need to show volume when activating known device. */
            updateGroupCacheAndAudioSystem(groupId, groupVolume, groupMute, /* showInUI*/ false);
        }
@@ -684,15 +737,13 @@ public class VolumeControlService extends ProfileService {
        }

        // If group volume has already changed, the new group member should set it
        Integer groupVolume =
                mGroupVolumeCache.getOrDefault(
                        groupId, IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
        if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
        Integer groupVolume = getGroupVolume(groupId);
        if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
            Log.i(TAG, "Setting value:" + groupVolume + " to " + device);
            mNativeInterface.setVolume(device, groupVolume);
        }

        Boolean isGroupMuted = mGroupMuteCache.getOrDefault(groupId, false);
        Boolean isGroupMuted = getGroupMute(groupId);
        Log.i(TAG, "Setting mute:" + isGroupMuted + " to " + device);
        if (isGroupMuted) {
            mNativeInterface.mute(device);
@@ -713,14 +764,18 @@ public class VolumeControlService extends ProfileService {
                        + ", showInUI"
                        + showInUI);

        if (volume < 0) {
            Log.w(TAG, "Tried to update invalid volume " + volume + ". Ignored.");
            return;
        }

        mGroupVolumeCache.put(groupId, volume);
        mGroupMuteCache.put(groupId, mute);

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService != null) {
            int currentlyActiveGroupId = leAudioService.getActiveGroupId();
            if (currentlyActiveGroupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID
                    || groupId != currentlyActiveGroupId) {
            if (currentlyActiveGroupId == GROUP_ID_INVALID || groupId != currentlyActiveGroupId) {
                if (!Flags.leaudioBroadcastVolumeControlPrimaryGroupOnly()) {
                    Log.i(
                            TAG,
@@ -731,7 +786,7 @@ public class VolumeControlService extends ProfileService {
                BassClientService bassClientService = mFactory.getBassClientService();
                if (bassClientService == null
                        || bassClientService.getSyncedBroadcastSinks().stream()
                                .map(dev -> leAudioService.getGroupId(dev))
                                .map(dev -> getGroupId(dev))
                                .noneMatch(
                                        id -> id == groupId && leAudioService.isPrimaryGroup(id))) {
                    Log.i(
@@ -775,16 +830,10 @@ public class VolumeControlService extends ProfileService {
            int flags,
            boolean mute,
            boolean isAutonomous) {
        if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
            LeAudioService leAudioService = mFactory.getLeAudioService();
            if (leAudioService == null) {
                Log.e(TAG, "leAudioService not available");
                return;
        if (groupId == GROUP_ID_INVALID) {
            groupId = getGroupId(device);
        }
            groupId = leAudioService.getGroupId(device);
        }

        if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
        if (groupId == GROUP_ID_INVALID) {
            Log.e(TAG, "Device not a part of the group");
            return;
        }
@@ -797,7 +846,8 @@ public class VolumeControlService extends ProfileService {
                    TAG,
                    ("Initial volume set after connect, volume: " + volume)
                            + (", mute: " + mute)
                            + (", flags: " + flags));
                            + (", flags: " + flags)
                            + (", device: " + device));
            /* 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 if not already streaming to other devices.
@@ -807,18 +857,19 @@ public class VolumeControlService extends ProfileService {
             */
            if (((flags & VOLUME_FLAGS_PERSISTED_USER_SET_VOLUME_MASK) == 0x01)
                    && (getConnectedDevices(groupId).size() == 1)) {
                Log.i(TAG, "Setting device: " + device + " volume: " + volume + " to the system");
                updateGroupCacheAndAudioSystem(groupId, volume, mute, false);
                return;
            }

            // Reset flag is used
            if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(TAG, "Setting volume: " + groupVolume + " to the group: " + groupId);
            if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                Log.i(TAG, "Setting group volume: " + groupVolume + " to the device: " + device);
                setGroupVolume(groupId, groupVolume);
            } else {
                int vol = getBleVolumeFromCurrentStream();
                Log.i(TAG, "Setting system volume: " + vol + " to the group: " + groupId);
                setGroupVolume(groupId, getBleVolumeFromCurrentStream());
                int systemVolume = getBleVolumeFromCurrentStream();
                Log.i(TAG, "Setting system volume: " + systemVolume + " to the group: " + groupId);
                setGroupVolume(groupId, systemVolume);
            }

            return;
@@ -832,16 +883,9 @@ public class VolumeControlService extends ProfileService {
                        + (", volume: " + volume));
        if (device == null) {
            // notify group devices volume changed
            LeAudioService leAudioService = mFactory.getLeAudioService();
            if (leAudioService != null) {
            synchronized (mCallbacks) {
                notifyDevicesVolumeChanged(
                            mCallbacks,
                            leAudioService.getGroupDevices(groupId),
                            Optional.of(volume));
                }
            } else {
                Log.w(TAG, "leAudioService not available");
                        mCallbacks, getGroupDevices(groupId), Optional.of(volume));
            }
        } else {
            // notify device volume changed
@@ -909,7 +953,7 @@ public class VolumeControlService extends ProfileService {
            volume = 0;
        }

        if (volume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) return -1;
        if (volume == VOLUME_CONTROL_UNKNOWN_VOLUME) return -1;
        return getAudioDeviceVolume(getBluetoothContextualVolumeStream(), volume);
    }

@@ -925,7 +969,7 @@ public class VolumeControlService extends ProfileService {
    int getBluetoothContextualVolumeStream() {
        int mode = mAudioManager.getMode();

        Log.d(TAG, "Volume mode: " + mode + "0: normal, 1: ring, 2,3: call");
        Log.d(TAG, "Volume mode:" + mode + "; Description: 0:normal, 1:ring, 2,3:call");

        return switch (mode) {
            case AudioManager.MODE_IN_CALL, AudioManager.MODE_IN_COMMUNICATION -> {
@@ -1327,30 +1371,16 @@ public class VolumeControlService extends ProfileService {
            return;
        }

        LeAudioService leAudioService = mFactory.getLeAudioService();
        if (leAudioService == null) {
            Log.e(TAG, "leAudioService not available");
            return;
        }

        for (BluetoothDevice dev : devices) {
            int cachedVolume = IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME;
            if (!volume.isPresent()) {
                int groupId = leAudioService.getGroupId(dev);
                if (groupId == IBluetoothLeAudio.LE_AUDIO_GROUP_ID_INVALID) {
                    Log.e(TAG, "Device not a part of a group");
                    continue;
                }
                // if device volume is available, notify with device volume, otherwise group volume
                cachedVolume = getDeviceVolume(dev);
                if (cachedVolume == IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
                    cachedVolume = getGroupVolume(groupId);
                }
            }
            int broadcastVolume = cachedVolume;
            int broadcastVolume = VOLUME_CONTROL_UNKNOWN_VOLUME;
            if (volume.isPresent()) {
                broadcastVolume = volume.get();
                mDeviceVolumeCache.put(dev, broadcastVolume);
            } else {
                broadcastVolume = getDeviceVolume(dev);
                if (broadcastVolume == VOLUME_CONTROL_UNKNOWN_VOLUME) {
                    broadcastVolume = getGroupVolume(getGroupId(dev));
                }
            }
            int n = callbacks.beginBroadcast();
            for (int i = 0; i < n; i++) {
@@ -1446,30 +1476,20 @@ public class VolumeControlService extends ProfileService {
            }
        } else if (toState == STATE_CONNECTED) {
            // Restore the group volume if it was changed while the device was not yet connected.
            CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService();
            if (csipClient != null) {
                Integer groupId = csipClient.getGroupId(device, BluetoothUuid.CAP);
                if (groupId != IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID) {
                    Integer groupVolume =
                            mGroupVolumeCache.getOrDefault(
                                    groupId, IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME);
                    if (groupVolume != IBluetoothVolumeControl.VOLUME_CONTROL_UNKNOWN_VOLUME) {
            Integer groupId = getGroupId(device);
            if (groupId != GROUP_ID_INVALID) {
                Integer groupVolume = getGroupVolume(groupId);
                if (groupVolume != VOLUME_CONTROL_UNKNOWN_VOLUME) {
                    mNativeInterface.setVolume(device, groupVolume);
                }

                    Boolean groupMute = mGroupMuteCache.getOrDefault(groupId, false);
                Boolean groupMute = getGroupMute(groupId);
                if (groupMute) {
                    mNativeInterface.mute(device);
                } else {
                    mNativeInterface.unmute(device);
                }
            }
            } else {
                /* It could happen when Bluetooth is stopping while VC is getting
                 * connection event
                 */
                Log.w(TAG, "CSIP is not available");
            }
        }
        mAdapterService.handleProfileConnectionStateChange(
                BluetoothProfile.VOLUME_CONTROL, device, fromState, toState);
@@ -1996,7 +2016,6 @@ public class VolumeControlService extends ProfileService {
        }

        for (Map.Entry<Integer, Integer> entry : mGroupVolumeCache.entrySet()) {
            Boolean isMute = mGroupMuteCache.getOrDefault(entry.getKey(), false);
            ProfileService.println(
                    sb,
                    "    GroupId: "
@@ -2004,7 +2023,7 @@ public class VolumeControlService extends ProfileService {
                            + " volume: "
                            + entry.getValue()
                            + ", mute: "
                            + isMute);
                            + getGroupMute(entry.getKey()));
        }
    }
}
+193 −91

File changed.

Preview size limit exceeded, changes collapsed.