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

Commit ee4ec43d authored by Łukasz Rymanowski's avatar Łukasz Rymanowski
Browse files

VolumeControlService: Notify new callback about current offset values

Bug: 298582813
Test: atest VolumeControlService
Tag: #feature
Change-Id: I50b9f3a82e543bcb79f9c0fc0004704fb4e84b32
parent 26922b3f
Loading
Loading
Loading
Loading
+80 −2
Original line number Diff line number Diff line
@@ -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) {
@@ -320,6 +338,7 @@ public class VolumeControlService extends ProfileService {

        if (mCallbacks != null) {
            mCallbacks.kill();
            mCallbacks = null;
        }

        return true;
@@ -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}
     */
@@ -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,
@@ -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);
+83 −6
Original line number Diff line number Diff line
@@ -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);
@@ -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();
@@ -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
     *