Loading android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java +18 −0 Original line number Diff line number Diff line Loading @@ -597,6 +597,24 @@ public class CsipSetCoordinatorService extends ProfileService { .collect(Collectors.toList()); } /** * Get group ID for a given device and UUID * @param device potential group member * @param uuid profile context UUID * @return group ID */ public Integer getGroupId(BluetoothDevice device, ParcelUuid uuid) { Map<Integer, Integer> device_groups = mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>()); return mGroupIdToUuidMap.entrySet() .stream() .filter(e -> (device_groups.containsKey(e.getKey()) && e.getValue().equals(uuid))) .map(Map.Entry::getKey) .findFirst() .orElse(IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID); } /** * Get device's groups/ * @param device group member device Loading android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +7 −0 Original line number Diff line number Diff line Loading @@ -1933,6 +1933,13 @@ public class LeAudioService extends ProfileService { } private void notifyGroupNodeAdded(BluetoothDevice device, int groupId) { if (mVolumeControlService == null) { mVolumeControlService = mServiceFactory.getVolumeControlService(); } if (mVolumeControlService != null) { mVolumeControlService.handleGroupNodeAdded(groupId, device); } if (mLeAudioCallbacks != null) { int n = mLeAudioCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { Loading android/app/src/com/android/bluetooth/vc/VolumeControlService.java +38 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.BluetoothVolumeControl; import android.bluetooth.IBluetoothCsipSetCoordinator; import android.bluetooth.IBluetoothLeAudio; import android.bluetooth.IBluetoothVolumeControl; import android.bluetooth.IBluetoothVolumeControlCallback; Loading @@ -47,6 +48,7 @@ import com.android.bluetooth.btservice.AdapterService; 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.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; Loading Loading @@ -193,7 +195,8 @@ public class VolumeControlService extends ProfileService { private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; private final ServiceFactory mFactory = new ServiceFactory(); @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public static boolean isEnabled() { return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false); Loading Loading @@ -632,6 +635,29 @@ public class VolumeControlService extends ProfileService { mVolumeControlNativeInterface.unmuteGroup(groupId); } /** * {@hide} */ public void handleGroupNodeAdded(int groupId, BluetoothDevice device) { // Ignore disconnected device, its volume will be set once it connects synchronized (mStateMachines) { VolumeControlStateMachine sm = mStateMachines.get(device); if (sm == null) { return; } if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { return; } } // 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) { mVolumeControlNativeInterface.setVolume(device, groupVolume); } } void handleVolumeControlChanged(BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous) { Loading Loading @@ -974,6 +1000,17 @@ public class VolumeControlService extends ProfileService { } removeStateMachine(device); } } else if (toState == BluetoothProfile.STATE_CONNECTED) { // Restore the group volume if it was changed while the device was not yet connected. CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 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) { mVolumeControlNativeInterface.setVolume(device, groupVolume); } } } } Loading android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +96 −0 Original line number Diff line number Diff line Loading @@ -41,7 +41,9 @@ import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.csip.CsipSetCoordinatorService; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver; Loading Loading @@ -73,6 +75,7 @@ public class VolumeControlServiceTest { private VolumeControlService mService; private VolumeControlService.BluetoothVolumeControlBinder mServiceBinder; private BluetoothDevice mDevice; private BluetoothDevice mDeviceTwo; private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap; private static final int TIMEOUT_MS = 1000; private static final int BT_LE_AUDIO_MAX_VOL = 255; Loading @@ -87,6 +90,8 @@ public class VolumeControlServiceTest { @Mock private DatabaseManager mDatabaseManager; @Mock private VolumeControlNativeInterface mNativeInterface; @Mock private AudioManager mAudioManager; @Mock private ServiceFactory mServiceFactory; @Mock private CsipSetCoordinatorService mCsipService; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading Loading @@ -119,9 +124,12 @@ public class VolumeControlServiceTest { startService(); mService.mVolumeControlNativeInterface = mNativeInterface; mService.mAudioManager = mAudioManager; mService.mFactory = mServiceFactory; mServiceBinder = (VolumeControlService.BluetoothVolumeControlBinder) mService.initBinder(); mServiceBinder.mIsTesting = true; doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService(); // Override the timeout value to speed up the test VolumeControlStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s Loading @@ -134,8 +142,10 @@ public class VolumeControlServiceTest { // Get a device for testing mDevice = TestUtils.getTestDevice(mAdapter, 0); mDeviceTwo = TestUtils.getTestDevice(mAdapter, 1); mDeviceQueueMap = new HashMap<>(); mDeviceQueueMap.put(mDevice, new LinkedBlockingQueue<>()); mDeviceQueueMap.put(mDeviceTwo, new LinkedBlockingQueue<>()); doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) .getBondState(any(BluetoothDevice.class)); doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService) Loading Loading @@ -631,6 +641,92 @@ public class VolumeControlServiceTest { Assert.assertEquals(volume, mService.getGroupVolume(groupId)); } /** * Test setting volume for a group member who connects after the volume level * for a group was already changed and cached. */ @Test public void testLateConnectingDevice() throws Exception { int groupId = 1; int groupVolume = 56; // Both devices are in the same group when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager .getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); mService.setGroupVolume(groupId, groupVolume); verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(groupVolume)); verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); // Verify that second device gets the proper group volume level when connected generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); } /** * Test setting volume for a new group member who is discovered after the volume level * for a group was already changed and cached. */ @Test public void testLateDiscoveredGroupMember() throws Exception { int groupId = 1; int groupVolume = 56; // For now only one device is in the group when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1); // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager .getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); // Set the group volume mService.setGroupVolume(groupId, groupVolume); // Verify that second device will not get the group volume level if it is not a group member generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); // But gets the volume when it becomes the group member when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); mService.handleGroupNodeAdded(groupId, mDeviceTwo); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); } @Test public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception { final SynchronousResultReceiver<List<BluetoothDevice>> recv = Loading system/bta/vc/vc.cc +35 −20 Original line number Diff line number Diff line Loading @@ -743,13 +743,23 @@ class VolumeControlImpl : public VolumeControl { int group_id, bool is_autonomous, uint8_t opcode, std::vector<uint8_t>& arguments) { DLOG(INFO) << __func__ << " num of devices: " << devices.size() << " group_id: " << group_id << " is_autonomous: " << is_autonomous << " opcode: " << +opcode << " arg size: " << arguments.size(); LOG_DEBUG( "num of devices: %zu, group_id: %d, is_autonomous: %s opcode: %d, arg " "size: %zu", devices.size(), group_id, is_autonomous ? "true" : "false", +opcode, arguments.size()); if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(), [opcode, &arguments](const VolumeOperation& op) { return (op.opcode_ == opcode) && std::equal(op.arguments_.begin(), op.arguments_.end(), arguments.begin()); }) == ongoing_operations_.end()) { ongoing_operations_.emplace_back(latest_operation_id_++, group_id, is_autonomous, opcode, arguments, devices); is_autonomous, opcode, arguments, devices); } } void MuteUnmute(std::variant<RawAddress, int> addr_or_group_id, bool mute) { Loading @@ -760,11 +770,13 @@ class VolumeControlImpl : public VolumeControl { if (std::holds_alternative<RawAddress>(addr_or_group_id)) { LOG_DEBUG("Address: %s: ", (std::get<RawAddress>(addr_or_group_id)).ToString().c_str()); std::vector<RawAddress> devices = { std::get<RawAddress>(addr_or_group_id)}; VolumeControlDevice* dev = volume_control_devices_.FindByAddress( std::get<RawAddress>(addr_or_group_id)); if (dev && dev->IsConnected()) { std::vector<RawAddress> devices = {dev->address}; PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false, opcode, arg); } } else { /* Handle group change */ auto group_id = std::get<int>(addr_or_group_id); Loading Loading @@ -815,14 +827,17 @@ class VolumeControlImpl : public VolumeControl { uint8_t opcode = kControlPointOpcodeSetAbsoluteVolume; if (std::holds_alternative<RawAddress>(addr_or_group_id)) { DLOG(INFO) << __func__ << " " << std::get<RawAddress>(addr_or_group_id); std::vector<RawAddress> devices = { std::get<RawAddress>(addr_or_group_id)}; LOG_DEBUG("Address: %s: ", std::get<RawAddress>(addr_or_group_id).ToString().c_str()); VolumeControlDevice* dev = volume_control_devices_.FindByAddress( std::get<RawAddress>(addr_or_group_id)); if (dev && dev->IsConnected() && (dev->volume != volume)) { std::vector<RawAddress> devices = {dev->address}; RemovePendingVolumeControlOperations(devices, bluetooth::groups::kGroupUnknown); PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false, opcode, arg); } } else { /* Handle group change */ auto group_id = std::get<int>(addr_or_group_id); Loading Loading
android/app/src/com/android/bluetooth/csip/CsipSetCoordinatorService.java +18 −0 Original line number Diff line number Diff line Loading @@ -597,6 +597,24 @@ public class CsipSetCoordinatorService extends ProfileService { .collect(Collectors.toList()); } /** * Get group ID for a given device and UUID * @param device potential group member * @param uuid profile context UUID * @return group ID */ public Integer getGroupId(BluetoothDevice device, ParcelUuid uuid) { Map<Integer, Integer> device_groups = mDeviceGroupIdRankMap.getOrDefault(device, new HashMap<>()); return mGroupIdToUuidMap.entrySet() .stream() .filter(e -> (device_groups.containsKey(e.getKey()) && e.getValue().equals(uuid))) .map(Map.Entry::getKey) .findFirst() .orElse(IBluetoothCsipSetCoordinator.CSIS_GROUP_ID_INVALID); } /** * Get device's groups/ * @param device group member device Loading
android/app/src/com/android/bluetooth/le_audio/LeAudioService.java +7 −0 Original line number Diff line number Diff line Loading @@ -1933,6 +1933,13 @@ public class LeAudioService extends ProfileService { } private void notifyGroupNodeAdded(BluetoothDevice device, int groupId) { if (mVolumeControlService == null) { mVolumeControlService = mServiceFactory.getVolumeControlService(); } if (mVolumeControlService != null) { mVolumeControlService.handleGroupNodeAdded(groupId, device); } if (mLeAudioCallbacks != null) { int n = mLeAudioCallbacks.beginBroadcast(); for (int i = 0; i < n; i++) { Loading
android/app/src/com/android/bluetooth/vc/VolumeControlService.java +38 −1 Original line number Diff line number Diff line Loading @@ -26,6 +26,7 @@ import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothProfile; import android.bluetooth.BluetoothUuid; import android.bluetooth.BluetoothVolumeControl; import android.bluetooth.IBluetoothCsipSetCoordinator; import android.bluetooth.IBluetoothLeAudio; import android.bluetooth.IBluetoothVolumeControl; import android.bluetooth.IBluetoothVolumeControlCallback; Loading @@ -47,6 +48,7 @@ import com.android.bluetooth.btservice.AdapterService; 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.le_audio.LeAudioService; import com.android.internal.annotations.VisibleForTesting; import com.android.modules.utils.SynchronousResultReceiver; Loading Loading @@ -193,7 +195,8 @@ public class VolumeControlService extends ProfileService { private BroadcastReceiver mBondStateChangedReceiver; private BroadcastReceiver mConnectionStateChangedReceiver; private final ServiceFactory mFactory = new ServiceFactory(); @VisibleForTesting ServiceFactory mFactory = new ServiceFactory(); public static boolean isEnabled() { return BluetoothProperties.isProfileVcpControllerEnabled().orElse(false); Loading Loading @@ -632,6 +635,29 @@ public class VolumeControlService extends ProfileService { mVolumeControlNativeInterface.unmuteGroup(groupId); } /** * {@hide} */ public void handleGroupNodeAdded(int groupId, BluetoothDevice device) { // Ignore disconnected device, its volume will be set once it connects synchronized (mStateMachines) { VolumeControlStateMachine sm = mStateMachines.get(device); if (sm == null) { return; } if (sm.getConnectionState() != BluetoothProfile.STATE_CONNECTED) { return; } } // 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) { mVolumeControlNativeInterface.setVolume(device, groupVolume); } } void handleVolumeControlChanged(BluetoothDevice device, int groupId, int volume, boolean mute, boolean isAutonomous) { Loading Loading @@ -974,6 +1000,17 @@ public class VolumeControlService extends ProfileService { } removeStateMachine(device); } } else if (toState == BluetoothProfile.STATE_CONNECTED) { // Restore the group volume if it was changed while the device was not yet connected. CsipSetCoordinatorService csipClient = mFactory.getCsipSetCoordinatorService(); 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) { mVolumeControlNativeInterface.setVolume(device, groupVolume); } } } } Loading
android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +96 −0 Original line number Diff line number Diff line Loading @@ -41,7 +41,9 @@ import androidx.test.rule.ServiceTestRule; import androidx.test.runner.AndroidJUnit4; import com.android.bluetooth.btservice.AdapterService; import com.android.bluetooth.btservice.ServiceFactory; import com.android.bluetooth.btservice.storage.DatabaseManager; import com.android.bluetooth.csip.CsipSetCoordinatorService; import com.android.bluetooth.R; import com.android.bluetooth.TestUtils; import com.android.bluetooth.x.com.android.modules.utils.SynchronousResultReceiver; Loading Loading @@ -73,6 +75,7 @@ public class VolumeControlServiceTest { private VolumeControlService mService; private VolumeControlService.BluetoothVolumeControlBinder mServiceBinder; private BluetoothDevice mDevice; private BluetoothDevice mDeviceTwo; private HashMap<BluetoothDevice, LinkedBlockingQueue<Intent>> mDeviceQueueMap; private static final int TIMEOUT_MS = 1000; private static final int BT_LE_AUDIO_MAX_VOL = 255; Loading @@ -87,6 +90,8 @@ public class VolumeControlServiceTest { @Mock private DatabaseManager mDatabaseManager; @Mock private VolumeControlNativeInterface mNativeInterface; @Mock private AudioManager mAudioManager; @Mock private ServiceFactory mServiceFactory; @Mock private CsipSetCoordinatorService mCsipService; @Rule public final ServiceTestRule mServiceRule = new ServiceTestRule(); Loading Loading @@ -119,9 +124,12 @@ public class VolumeControlServiceTest { startService(); mService.mVolumeControlNativeInterface = mNativeInterface; mService.mAudioManager = mAudioManager; mService.mFactory = mServiceFactory; mServiceBinder = (VolumeControlService.BluetoothVolumeControlBinder) mService.initBinder(); mServiceBinder.mIsTesting = true; doReturn(mCsipService).when(mServiceFactory).getCsipSetCoordinatorService(); // Override the timeout value to speed up the test VolumeControlStateMachine.sConnectTimeoutMs = TIMEOUT_MS; // 1s Loading @@ -134,8 +142,10 @@ public class VolumeControlServiceTest { // Get a device for testing mDevice = TestUtils.getTestDevice(mAdapter, 0); mDeviceTwo = TestUtils.getTestDevice(mAdapter, 1); mDeviceQueueMap = new HashMap<>(); mDeviceQueueMap.put(mDevice, new LinkedBlockingQueue<>()); mDeviceQueueMap.put(mDeviceTwo, new LinkedBlockingQueue<>()); doReturn(BluetoothDevice.BOND_BONDED).when(mAdapterService) .getBondState(any(BluetoothDevice.class)); doReturn(new ParcelUuid[]{BluetoothUuid.VOLUME_CONTROL}).when(mAdapterService) Loading Loading @@ -631,6 +641,92 @@ public class VolumeControlServiceTest { Assert.assertEquals(volume, mService.getGroupVolume(groupId)); } /** * Test setting volume for a group member who connects after the volume level * for a group was already changed and cached. */ @Test public void testLateConnectingDevice() throws Exception { int groupId = 1; int groupVolume = 56; // Both devices are in the same group when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager .getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); mService.setGroupVolume(groupId, groupVolume); verify(mNativeInterface, times(1)).setGroupVolume(eq(groupId), eq(groupVolume)); verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); // Verify that second device gets the proper group volume level when connected generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); } /** * Test setting volume for a new group member who is discovered after the volume level * for a group was already changed and cached. */ @Test public void testLateDiscoveredGroupMember() throws Exception { int groupId = 1; int groupVolume = 56; // For now only one device is in the group when(mCsipService.getGroupId(mDevice, BluetoothUuid.CAP)).thenReturn(groupId); when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(-1); // Update the device policy so okToConnect() returns true when(mAdapterService.getDatabase()).thenReturn(mDatabaseManager); when(mDatabaseManager .getProfileConnectionPolicy(any(BluetoothDevice.class), eq(BluetoothProfile.VOLUME_CONTROL))) .thenReturn(BluetoothProfile.CONNECTION_POLICY_ALLOWED); doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class)); doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class)); generateConnectionMessageFromNative(mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice)); Assert.assertTrue(mService.getDevices().contains(mDevice)); // Set the group volume mService.setGroupVolume(groupId, groupVolume); // Verify that second device will not get the group volume level if it is not a group member generateConnectionMessageFromNative(mDeviceTwo, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED); Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDeviceTwo)); Assert.assertTrue(mService.getDevices().contains(mDeviceTwo)); verify(mNativeInterface, times(0)).setVolume(eq(mDeviceTwo), eq(groupVolume)); // But gets the volume when it becomes the group member when(mCsipService.getGroupId(mDeviceTwo, BluetoothUuid.CAP)).thenReturn(groupId); mService.handleGroupNodeAdded(groupId, mDeviceTwo); verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume)); } @Test public void testServiceBinderGetDevicesMatchingConnectionStates() throws Exception { final SynchronousResultReceiver<List<BluetoothDevice>> recv = Loading
system/bta/vc/vc.cc +35 −20 Original line number Diff line number Diff line Loading @@ -743,13 +743,23 @@ class VolumeControlImpl : public VolumeControl { int group_id, bool is_autonomous, uint8_t opcode, std::vector<uint8_t>& arguments) { DLOG(INFO) << __func__ << " num of devices: " << devices.size() << " group_id: " << group_id << " is_autonomous: " << is_autonomous << " opcode: " << +opcode << " arg size: " << arguments.size(); LOG_DEBUG( "num of devices: %zu, group_id: %d, is_autonomous: %s opcode: %d, arg " "size: %zu", devices.size(), group_id, is_autonomous ? "true" : "false", +opcode, arguments.size()); if (std::find_if(ongoing_operations_.begin(), ongoing_operations_.end(), [opcode, &arguments](const VolumeOperation& op) { return (op.opcode_ == opcode) && std::equal(op.arguments_.begin(), op.arguments_.end(), arguments.begin()); }) == ongoing_operations_.end()) { ongoing_operations_.emplace_back(latest_operation_id_++, group_id, is_autonomous, opcode, arguments, devices); is_autonomous, opcode, arguments, devices); } } void MuteUnmute(std::variant<RawAddress, int> addr_or_group_id, bool mute) { Loading @@ -760,11 +770,13 @@ class VolumeControlImpl : public VolumeControl { if (std::holds_alternative<RawAddress>(addr_or_group_id)) { LOG_DEBUG("Address: %s: ", (std::get<RawAddress>(addr_or_group_id)).ToString().c_str()); std::vector<RawAddress> devices = { std::get<RawAddress>(addr_or_group_id)}; VolumeControlDevice* dev = volume_control_devices_.FindByAddress( std::get<RawAddress>(addr_or_group_id)); if (dev && dev->IsConnected()) { std::vector<RawAddress> devices = {dev->address}; PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false, opcode, arg); } } else { /* Handle group change */ auto group_id = std::get<int>(addr_or_group_id); Loading Loading @@ -815,14 +827,17 @@ class VolumeControlImpl : public VolumeControl { uint8_t opcode = kControlPointOpcodeSetAbsoluteVolume; if (std::holds_alternative<RawAddress>(addr_or_group_id)) { DLOG(INFO) << __func__ << " " << std::get<RawAddress>(addr_or_group_id); std::vector<RawAddress> devices = { std::get<RawAddress>(addr_or_group_id)}; LOG_DEBUG("Address: %s: ", std::get<RawAddress>(addr_or_group_id).ToString().c_str()); VolumeControlDevice* dev = volume_control_devices_.FindByAddress( std::get<RawAddress>(addr_or_group_id)); if (dev && dev->IsConnected() && (dev->volume != volume)) { std::vector<RawAddress> devices = {dev->address}; RemovePendingVolumeControlOperations(devices, bluetooth::groups::kGroupUnknown); PrepareVolumeControlOperation(devices, bluetooth::groups::kGroupUnknown, false, opcode, arg); } } else { /* Handle group change */ auto group_id = std::get<int>(addr_or_group_id); Loading