Loading android/app/src/com/android/bluetooth/vc/VolumeControlService.java +80 −2 Original line number Diff line number Diff line Loading @@ -121,6 +121,24 @@ public class VolumeControlService extends ProfileService { return true; } int getFirstOffsetValue() { if (size() == 0) { return 0; } Descriptor[] descriptors = mVolumeOffsets.values().toArray(new Descriptor[size()]); if (DBG) { Log.d( TAG, "Number of offsets: " + size() + ", first offset value: " + descriptors[0].mValue); } return descriptors[0].mValue; } int getValue(int id) { Descriptor d = mVolumeOffsets.get(id); if (d == null) { Loading Loading @@ -320,6 +338,7 @@ public class VolumeControlService extends ProfileService { if (mCallbacks != null) { mCallbacks.kill(); mCallbacks = null; } return true; Loading Loading @@ -699,6 +718,63 @@ public class VolumeControlService extends ProfileService { mVolumeControlNativeInterface.unmuteGroup(groupId); } void notifyNewCallbackOfKnownVolumeOffsets(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "notifyNewCallbackOfKnownVolumeOffsets"); } RemoteCallbackList<IBluetoothVolumeControlCallback> tempCallbackList = new RemoteCallbackList<>(); if (tempCallbackList == null) { Log.w(TAG, "notifyNewCallbackOfKnownVolumeOffsets: tempCallbackList not available"); return; } /* Register callback on temporary list just to execute it now. */ tempCallbackList.register(callback); int n = tempCallbackList.beginBroadcast(); if (n != 1) { /* There should be only one calback in this place. */ Log.e(TAG, "notifyNewCallbackOfKnownVolumeOffsets: Shall be 1 but it is " + n); } for (int i = 0; i < n; i++) { for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry : mAudioOffsets.entrySet()) { VolumeControlOffsetDescriptor descriptor = entry.getValue(); if (descriptor.size() == 0) { continue; } BluetoothDevice device = entry.getKey(); int offset = descriptor.getFirstOffsetValue(); if (DBG) { Log.d(TAG, "notifyNewCallbackOfKnownVolumeOffsets: " + device + ", " + offset); } try { tempCallbackList.getBroadcastItem(i).onVolumeOffsetChanged(device, offset); } catch (RemoteException e) { continue; } } } tempCallbackList.finishBroadcast(); /* User is notified, remove callback from temporary list */ tempCallbackList.unregister(callback); } void registerCallback(IBluetoothVolumeControlCallback callback) { /* Here we keep all the user callbacks */ mCallbacks.register(callback); notifyNewCallbackOfKnownVolumeOffsets(callback); } /** * {@hide} */ Loading Loading @@ -963,6 +1039,9 @@ public class VolumeControlService extends ProfileService { } void messageFromNative(VolumeControlStackEvent stackEvent) { if (DBG) { Log.d(TAG, "messageFromNative: " + stackEvent); } if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) { handleVolumeControlChanged(stackEvent.device, stackEvent.valueInt1, Loading Loading @@ -1511,8 +1590,7 @@ public class VolumeControlService extends ProfileService { } enforceBluetoothPrivilegedPermission(service); service.mCallbacks.register(callback); service.registerCallback(callback); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); Loading android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +83 −6 Original line number Diff line number Diff line Loading @@ -1021,12 +1021,7 @@ public class VolumeControlServiceTest { @Test public void testServiceBinderVolumeOffsetMethods() throws Exception { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); event.device = mDevice; event.valueInt1 = 2; // number of external outputs mService.messageFromNative(event); generateDeviceAvailableMessageFromNative(mDevice, 2); final SynchronousResultReceiver<Boolean> boolRecv = SynchronousResultReceiver.get(); boolean defaultRecvValue = false; mServiceBinder.isVolumeOffsetAvailable(mDevice, mAttributionSource, boolRecv); Loading Loading @@ -1059,6 +1054,66 @@ public class VolumeControlServiceTest { Assert.assertEquals(size, mService.mCallbacks.getRegisteredCallbackCount()); } @Test public void testServiceBinderRegisterCallbackWhenDeviceAlreadyConnected() 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)); generateDeviceAvailableMessageFromNative(mDevice, 1); 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 generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); 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)); // Set different offset to both devices generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100); generateDeviceOffsetChangedMessageFromNative(mDeviceTwo, 1, 200); // Register callback and verify it is called with known devices IBluetoothVolumeControlCallback callback = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder = Mockito.mock(Binder.class); when(callback.asBinder()).thenReturn(binder); int size = mService.mCallbacks.getRegisteredCallbackCount(); SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get(); mServiceBinder.registerCallback(callback, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); verify(callback).onVolumeOffsetChanged(eq(mDeviceTwo), eq(200)); verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(100)); generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 50); verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(50)); } @Test public void testServiceBinderMuteMethods() throws Exception { SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); Loading Loading @@ -1203,6 +1258,28 @@ public class VolumeControlServiceTest { verifyNoConnectionStateIntent(TIMEOUT_MS, device); } private void generateDeviceAvailableMessageFromNative( BluetoothDevice device, int numberOfExtOffsets) { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent(VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); event.device = device; event.valueInt1 = numberOfExtOffsets; // number of external outputs mService.messageFromNative(event); } private void generateDeviceOffsetChangedMessageFromNative( BluetoothDevice device, int extOffsetIndex, int offset) { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED); event.device = device; event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = offset; // offset value mService.messageFromNative(event); } /** * Helper function to test okToConnect() method * Loading Loading
android/app/src/com/android/bluetooth/vc/VolumeControlService.java +80 −2 Original line number Diff line number Diff line Loading @@ -121,6 +121,24 @@ public class VolumeControlService extends ProfileService { return true; } int getFirstOffsetValue() { if (size() == 0) { return 0; } Descriptor[] descriptors = mVolumeOffsets.values().toArray(new Descriptor[size()]); if (DBG) { Log.d( TAG, "Number of offsets: " + size() + ", first offset value: " + descriptors[0].mValue); } return descriptors[0].mValue; } int getValue(int id) { Descriptor d = mVolumeOffsets.get(id); if (d == null) { Loading Loading @@ -320,6 +338,7 @@ public class VolumeControlService extends ProfileService { if (mCallbacks != null) { mCallbacks.kill(); mCallbacks = null; } return true; Loading Loading @@ -699,6 +718,63 @@ public class VolumeControlService extends ProfileService { mVolumeControlNativeInterface.unmuteGroup(groupId); } void notifyNewCallbackOfKnownVolumeOffsets(IBluetoothVolumeControlCallback callback) { if (DBG) { Log.d(TAG, "notifyNewCallbackOfKnownVolumeOffsets"); } RemoteCallbackList<IBluetoothVolumeControlCallback> tempCallbackList = new RemoteCallbackList<>(); if (tempCallbackList == null) { Log.w(TAG, "notifyNewCallbackOfKnownVolumeOffsets: tempCallbackList not available"); return; } /* Register callback on temporary list just to execute it now. */ tempCallbackList.register(callback); int n = tempCallbackList.beginBroadcast(); if (n != 1) { /* There should be only one calback in this place. */ Log.e(TAG, "notifyNewCallbackOfKnownVolumeOffsets: Shall be 1 but it is " + n); } for (int i = 0; i < n; i++) { for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry : mAudioOffsets.entrySet()) { VolumeControlOffsetDescriptor descriptor = entry.getValue(); if (descriptor.size() == 0) { continue; } BluetoothDevice device = entry.getKey(); int offset = descriptor.getFirstOffsetValue(); if (DBG) { Log.d(TAG, "notifyNewCallbackOfKnownVolumeOffsets: " + device + ", " + offset); } try { tempCallbackList.getBroadcastItem(i).onVolumeOffsetChanged(device, offset); } catch (RemoteException e) { continue; } } } tempCallbackList.finishBroadcast(); /* User is notified, remove callback from temporary list */ tempCallbackList.unregister(callback); } void registerCallback(IBluetoothVolumeControlCallback callback) { /* Here we keep all the user callbacks */ mCallbacks.register(callback); notifyNewCallbackOfKnownVolumeOffsets(callback); } /** * {@hide} */ Loading Loading @@ -963,6 +1039,9 @@ public class VolumeControlService extends ProfileService { } void messageFromNative(VolumeControlStackEvent stackEvent) { if (DBG) { Log.d(TAG, "messageFromNative: " + stackEvent); } if (stackEvent.type == VolumeControlStackEvent.EVENT_TYPE_VOLUME_STATE_CHANGED) { handleVolumeControlChanged(stackEvent.device, stackEvent.valueInt1, Loading Loading @@ -1511,8 +1590,7 @@ public class VolumeControlService extends ProfileService { } enforceBluetoothPrivilegedPermission(service); service.mCallbacks.register(callback); service.registerCallback(callback); receiver.send(null); } catch (RuntimeException e) { receiver.propagateException(e); Loading
android/app/tests/unit/src/com/android/bluetooth/vc/VolumeControlServiceTest.java +83 −6 Original line number Diff line number Diff line Loading @@ -1021,12 +1021,7 @@ public class VolumeControlServiceTest { @Test public void testServiceBinderVolumeOffsetMethods() throws Exception { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); event.device = mDevice; event.valueInt1 = 2; // number of external outputs mService.messageFromNative(event); generateDeviceAvailableMessageFromNative(mDevice, 2); final SynchronousResultReceiver<Boolean> boolRecv = SynchronousResultReceiver.get(); boolean defaultRecvValue = false; mServiceBinder.isVolumeOffsetAvailable(mDevice, mAttributionSource, boolRecv); Loading Loading @@ -1059,6 +1054,66 @@ public class VolumeControlServiceTest { Assert.assertEquals(size, mService.mCallbacks.getRegisteredCallbackCount()); } @Test public void testServiceBinderRegisterCallbackWhenDeviceAlreadyConnected() 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)); generateDeviceAvailableMessageFromNative(mDevice, 1); 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 generateDeviceAvailableMessageFromNative(mDeviceTwo, 1); 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)); // Set different offset to both devices generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100); generateDeviceOffsetChangedMessageFromNative(mDeviceTwo, 1, 200); // Register callback and verify it is called with known devices IBluetoothVolumeControlCallback callback = Mockito.mock(IBluetoothVolumeControlCallback.class); Binder binder = Mockito.mock(Binder.class); when(callback.asBinder()).thenReturn(binder); int size = mService.mCallbacks.getRegisteredCallbackCount(); SynchronousResultReceiver<Void> recv = SynchronousResultReceiver.get(); mServiceBinder.registerCallback(callback, mAttributionSource, recv); recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null); Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount()); verify(callback).onVolumeOffsetChanged(eq(mDeviceTwo), eq(200)); verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(100)); generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 50); verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(50)); } @Test public void testServiceBinderMuteMethods() throws Exception { SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get(); Loading Loading @@ -1203,6 +1258,28 @@ public class VolumeControlServiceTest { verifyNoConnectionStateIntent(TIMEOUT_MS, device); } private void generateDeviceAvailableMessageFromNative( BluetoothDevice device, int numberOfExtOffsets) { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent(VolumeControlStackEvent.EVENT_TYPE_DEVICE_AVAILABLE); event.device = device; event.valueInt1 = numberOfExtOffsets; // number of external outputs mService.messageFromNative(event); } private void generateDeviceOffsetChangedMessageFromNative( BluetoothDevice device, int extOffsetIndex, int offset) { // Send a message to trigger connection completed VolumeControlStackEvent event = new VolumeControlStackEvent( VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_VOL_OFFSET_CHANGED); event.device = device; event.valueInt1 = extOffsetIndex; // external output index event.valueInt2 = offset; // offset value mService.messageFromNative(event); } /** * Helper function to test okToConnect() method * Loading