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

Commit 676a0a01 authored by Rongxuan Liu's avatar Rongxuan Liu Committed by Gerrit Code Review
Browse files

Merge "VolumeControl: Fix registering more callbacks" into main

parents 9560404c c4b54f2f
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -75,4 +75,6 @@ oneway interface IBluetoothVolumeControl {
    void registerCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void unregisterCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void notifyNewRegisteredCallback(in IBluetoothVolumeControlCallback callback, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
}
+58 −13
Original line number Diff line number Diff line
@@ -807,25 +807,35 @@ public class VolumeControlService extends ProfileService {
                    continue;
                }
            }
            // notify volume level for all vc devices
            if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) {
                notifyDevicesVolumeChanged(getDevices(), Optional.empty());
            }
        }

        tempCallbackList.finishBroadcast();

        if (mFeatureFlags.leaudioBroadcastVolumeControlForConnectedDevices()) {
            notifyDevicesVolumeChanged(tempCallbackList, getDevices(), Optional.empty());
        }

        /* User is notified, remove callback from temporary list */
        tempCallbackList.unregister(callback);
    }

    void registerCallback(IBluetoothVolumeControlCallback callback) {
        if (DBG) {
            Log.d(TAG, "registerCallback: " + callback);
        }
        /* Here we keep all the user callbacks */
        mCallbacks.register(callback);

        notifyNewCallbackOfKnownVolumeInfo(callback);
    }

    void notifyNewRegisteredCallback(IBluetoothVolumeControlCallback callback) {
        if (DBG) {
            Log.d(TAG, "notifyNewRegisteredCallback: " + callback);
        }
        notifyNewCallbackOfKnownVolumeInfo(callback);
    }

    /**
     * {@hide}
     */
@@ -942,13 +952,15 @@ public class VolumeControlService extends ProfileService {
                LeAudioService leAudioService = mFactory.getLeAudioService();
                if (leAudioService != null) {
                    notifyDevicesVolumeChanged(
                            leAudioService.getGroupDevices(groupId), Optional.of(volume));
                            mCallbacks,
                            leAudioService.getGroupDevices(groupId),
                            Optional.of(volume));
                } else {
                    Log.w(TAG, "leAudioService not available");
                }
            } else {
                // notify device volume changed
                notifyDevicesVolumeChanged(Arrays.asList(device), Optional.of(volume));
                notifyDevicesVolumeChanged(mCallbacks, Arrays.asList(device), Optional.of(volume));
            }
        }

@@ -1230,13 +1242,16 @@ public class VolumeControlService extends ProfileService {
     * newly registered callback, volume level is unknown from caller, notify the clients with
     * cached volume level from either device or group.
     *
     * @param callbacks list of callbacks
     * @param devices list of devices to notify volume changed
     * @param volume volume level
     */
    private void notifyDevicesVolumeChanged(
            List<BluetoothDevice> devices, Optional<Integer> volume) {
        if (mCallbacks == null) {
            Log.e(TAG, "mCallbacks is null");
            RemoteCallbackList<IBluetoothVolumeControlCallback> callbacks,
            List<BluetoothDevice> devices,
            Optional<Integer> volume) {
        if (callbacks == null) {
            Log.e(TAG, "callbacks is null");
            return;
        }

@@ -1260,20 +1275,20 @@ public class VolumeControlService extends ProfileService {
                    cachedVolume = getGroupVolume(groupId);
                }
            }
            int n = mCallbacks.beginBroadcast();
            int n = callbacks.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    if (!volume.isPresent()) {
                        mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume);
                        callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, cachedVolume);
                    } else {
                        mDeviceVolumeCache.put(dev, volume.get());
                        mCallbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get());
                        callbacks.getBroadcastItem(i).onDeviceVolumeChanged(dev, volume.get());
                    }
                } catch (RemoteException e) {
                    continue;
                }
            }
            mCallbacks.finishBroadcast();
            callbacks.finishBroadcast();
        }
    }

@@ -1763,6 +1778,36 @@ public class VolumeControlService extends ProfileService {
            }
        }

        @Override
        public void notifyNewRegisteredCallback(
                IBluetoothVolumeControlCallback callback,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(callback, "callback cannot be null");
                Objects.requireNonNull(source, "source cannot be null");
                Objects.requireNonNull(receiver, "receiver cannot be null");

                VolumeControlService service = getService(source);
                if (service == null) {
                    throw new IllegalStateException("Service is unavailable");
                }

                enforceBluetoothPrivilegedPermission(service);
                service.mHandler.post(
                        () -> {
                            try {
                                service.notifyNewRegisteredCallback(callback);
                                receiver.send(null);
                            } catch (RuntimeException e) {
                                receiver.propagateException(e);
                            }
                        });
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void unregisterCallback(IBluetoothVolumeControlCallback callback,
                AttributionSource source, SynchronousResultReceiver receiver) {
+71 −0
Original line number Diff line number Diff line
@@ -1219,6 +1219,77 @@ public class VolumeControlServiceTest {
        verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume));
    }

    @Test
    public void testServiceBinderTestNotifyNewRegisteredCallback() throws Exception {
        mFakeFlagsImpl.setFlag(
                Flags.FLAG_LEAUDIO_BROADCAST_VOLUME_CONTROL_FOR_CONNECTED_DEVICES, true);
        int groupId = 1;
        int deviceOneVolume = 46;
        int deviceTwoVolume = 36;

        // 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.setDeviceVolume(mDevice, deviceOneVolume, false);
        verify(mNativeInterface, times(1)).setVolume(eq(mDevice), eq(deviceOneVolume));

        // 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));
        mService.setDeviceVolume(mDeviceTwo, deviceTwoVolume, false);
        verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(deviceTwoVolume));

        // Both devices are in the same group
        when(mLeAudioService.getGroupId(mDevice)).thenReturn(groupId);
        when(mLeAudioService.getGroupId(mDeviceTwo)).thenReturn(groupId);

        // 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());

        IBluetoothVolumeControlCallback callback_new_client =
                Mockito.mock(IBluetoothVolumeControlCallback.class);
        Binder binder_new_client = Mockito.mock(Binder.class);
        when(callback_new_client.asBinder()).thenReturn(binder_new_client);

        recv = SynchronousResultReceiver.get();
        mServiceBinder.notifyNewRegisteredCallback(callback_new_client, mAttributionSource, recv);
        recv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS)).getValue(null);
        Assert.assertEquals(size + 1, mService.mCallbacks.getRegisteredCallbackCount());

        // This shall be done only once after mServiceBinder.registerCallback
        verify(callback, times(1)).onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume));
        verify(callback, times(1)).onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume));

        // This shall be done only once after mServiceBinder.updateNewRegistedCallback
        verify(callback_new_client, times(1))
                .onDeviceVolumeChanged(eq(mDevice), eq(deviceOneVolume));
        verify(callback_new_client, times(1))
                .onDeviceVolumeChanged(eq(mDeviceTwo), eq(deviceTwoVolume));
    }

    @Test
    public void testServiceBinderMuteMethods() throws Exception {
        SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
+36 −24
Original line number Diff line number Diff line
@@ -361,8 +361,6 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
        Objects.requireNonNull(callback, "callback cannot be null");
        if (DBG) log("registerCallback");
        synchronized (mCallbackExecutorMap) {
            // If the callback map is empty, we register the service-to-app callback
            if (mCallbackExecutorMap.isEmpty()) {
            if (!mAdapter.isEnabled()) {
                /* If Bluetooth is off, just store callback and it will be registered
                 * when Bluetooth is on
@@ -370,28 +368,42 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
                mCallbackExecutorMap.put(callback, executor);
                return;
            }

            // Adds the passed in callback to our map of callbacks to executors
            if (mCallbackExecutorMap.containsKey(callback)) {
                throw new IllegalArgumentException("This callback has already been registered");
            }

            try {
                final IBluetoothVolumeControl service = getService();
                if (service != null) {
                        final SynchronousResultReceiver<Integer> recv =
                                SynchronousResultReceiver.get();
                    final SynchronousResultReceiver<Integer> recv = SynchronousResultReceiver.get();

                    /* If the callback map is empty, we register the service-to-app callback.
                     *  Otherwise, callback is registered in mCallbackExecutorMap and we just notify
                     *  user over callback with current values.
                     */
                    boolean isRegisterCallbackRequired = mCallbackExecutorMap.isEmpty();
                    mCallbackExecutorMap.put(callback, executor);

                    if (isRegisterCallbackRequired) {
                        service.registerCallback(mCallback, mAttributionSource, recv);
                    } else {
                        service.notifyNewRegisteredCallback(
                                (IBluetoothVolumeControlCallback) (callback),
                                mAttributionSource,
                                recv);
                    }
                    recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
                }
            } catch (RemoteException e) {
                mCallbackExecutorMap.remove(callback);
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
                throw e.rethrowAsRuntimeException();
            } catch (TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
            }
        }

            // Adds the passed in callback to our map of callbacks to executors
            if (mCallbackExecutorMap.containsKey(callback)) {
                throw new IllegalArgumentException("This callback has already been registered");
            }
            mCallbackExecutorMap.put(callback, executor);
        }
    }

    /**