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

Commit b99f7ece authored by Łukasz Rymanowski's avatar Łukasz Rymanowski Committed by Jack He
Browse files

leaudio: Improve Volume Control API

On the Le Audio device absolute volume is set via
BluetoothLeAudio.setVolume API. LeAudioService knows which group is the
active group and using BluetoothVolumeControl.setVolumeGroup() API apply
absolute volume.
Each LeAudio device is a part of the group.
In order to support audio balance between e.g. left and right earbud
setVolumeOffset should be use per each device.

This change also allows user to register for the callback which will
be invoked when the volume offset changes on the remote device.

Bug: 150670922
Tag: #feature
Sponsor: @jpawlowski
Test: atest BluetoothInstrumentationTests
Change-Id: I3692e45d285d55d946ae0de187aa1d5261f825b8
Merged-In: I3692e45d285d55d946ae0de187aa1d5261f825b8
(cherry picked from commit 470384a0)
parent 599bf26c
Loading
Loading
Loading
Loading
+5 −5
Original line number Diff line number Diff line
@@ -410,8 +410,8 @@ public class VolumeControlService extends ProfileService {
                .getProfileConnectionPolicy(device, BluetoothProfile.VOLUME_CONTROL);
    }

    void setVolume(BluetoothDevice device, int volume) {
        mVolumeControlNativeInterface.setVolume(device, volume);
    void setVolumeOffset(BluetoothDevice device, int volumeOffset) {
        // TODO Implement
    }

    /**
@@ -728,12 +728,12 @@ public class VolumeControlService extends ProfileService {
        }

        @Override
        public void setVolume(BluetoothDevice device, int volume, AttributionSource source,
                SynchronousResultReceiver receiver) {
        public void setVolumeOffset(BluetoothDevice device, int volumeOffset,
                AttributionSource source, SynchronousResultReceiver receiver) {
            try {
                VolumeControlService service = getService(source);
                if (service != null) {
                    service.setVolume(device, volume);
                    service.setVolumeOffset(device, volumeOffset);
                }
                receiver.send(null);
            } catch (RuntimeException e) {
+8 −1
Original line number Diff line number Diff line
@@ -316,11 +316,18 @@ package android.bluetooth {
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) protected void finalize();
    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothDevice> getConnectedDevices();
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isVolumeOffsetAvailable(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothVolumeControl.Callback);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean setConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice, int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setVolume(@Nullable android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setVolumeOffset(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0xffffff01, to=255) int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void unregisterCallback(@NonNull android.bluetooth.BluetoothVolumeControl.Callback);
    field @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public static final String ACTION_CONNECTION_STATE_CHANGED = "android.bluetooth.volume-control.profile.action.CONNECTION_STATE_CHANGED";
  }

  public static interface BluetoothVolumeControl.Callback {
    method public void onVolumeOffsetChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0xffffff01, to=255) int);
  }

  public final class BufferConstraint implements android.os.Parcelable {
    ctor public BufferConstraint(int, int, int);
    method public int describeContents();
+116 −7
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ package android.bluetooth;
import static android.bluetooth.BluetoothUtils.getSyncTimeout;

import android.Manifest;
import android.annotation.CallbackExecutor;
import android.annotation.IntRange;
import android.annotation.NonNull;
import android.annotation.Nullable;
@@ -40,6 +41,7 @@ import com.android.modules.utils.SynchronousResultReceiver;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;

/**
@@ -58,6 +60,33 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose

    private CloseGuard mCloseGuard;

    /**
     * This class provides a callback that is invoked when volume offset value changes on
     * the remote device.
     *
     * <p> In order to balance volume on the group of Le Audio devices,
     * Volume Offset Control Service (VOCS) shall be used. User can verify
     * if the remote device supports VOCS by calling {@link #isVolumeOffsetAvailable(device)}.
     *
     * @hide
     */
    @SystemApi
    public interface Callback {
        /**
         * Callback invoked when callback is registered and when volume offset
         * changes on the remote device. Change can be triggered autonomously by the remote device
         * or after volume offset change on the user request done by calling
         * {@link #setVolumeOffset(device, volumeOffset)}
         *
         * @param device remote device whose volume offset changed
         * @param volumeOffset latest volume offset for this device
         * @hide
         */
        @SystemApi
        void onVolumeOffsetChanged(@NonNull BluetoothDevice device,
                                   @IntRange(from = -255, to = 255) int volumeOffset);
    }

    /**
     * Intent used to broadcast the change in connection state of the Volume Control
     * profile.
@@ -217,10 +246,17 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
    }

    /**
     * Tells remote device to set an absolute volume.
     * Register a {@link Callback} that will be invoked during the
     * operation of this profile.
     *
     * Repeated registration of the same <var>callback</var> object will have no effect after
     * the first call to this method, even when the <var>executor</var> is different. API caller
     * would have to call {@link #unregisterCallback(Callback)} with
     * the same callback object before registering it again.
     *
     * @param volume Absolute volume to be set on remote device.
     *               Minimum value is 0 and maximum value is 255
     * @param executor an {@link Executor} to execute given callback
     * @param callback user implementation of the {@link Callback}
     * @throws IllegalArgumentException if a null executor, sink, or callback is given
     * @hide
     */
    @SystemApi
@@ -229,9 +265,60 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public void setVolume(@Nullable BluetoothDevice device,
            @IntRange(from = 0, to = 255) int volume) {
        if (DBG) log("setVolume(" + volume + ")");
    public void registerCallback(@NonNull @CallbackExecutor Executor executor,
            @NonNull Callback callback) {
        if (executor == null) {
            throw new IllegalArgumentException("executor cannot be null");
        }
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        if (DBG) log("registerCallback");
        throw new UnsupportedOperationException("Not Implemented");
    }

    /**
     * Unregister the specified {@link Callback}.
     * <p>The same {@link Callback} object used when calling
     * {@link #registerCallback(Executor, Callback)} must be used.
     *
     * <p>Callbacks are automatically unregistered when application process goes away
     *
     * @param callback user implementation of the {@link Callback}
     * @throws IllegalArgumentException when callback is null or when no callback is registered
     * @hide
     */
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public void unregisterCallback(@NonNull Callback callback) {
        if (callback == null) {
            throw new IllegalArgumentException("callback cannot be null");
        }
        if (DBG) log("unregisterCallback");
        throw new UnsupportedOperationException("Not Implemented");
    }

    /**
     * Tells the remote device to set a volume offset to the absolute volume.
     *
     * @param device {@link BluetoothDevice} representing the remote device
     * @param volumeOffset volume offset to be set on the remote device
     *
     * @hide
     */
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public void setVolumeOffset(@NonNull BluetoothDevice device,
            @IntRange(from = -255, to = 255) int volumeOffset) {
        if (DBG) log("setVolumeOffset(" + device  + " volumeOffset: " + volumeOffset + ")");
        final IBluetoothVolumeControl service = getService();
        if (service == null) {
            Log.w(TAG, "Proxy not attached to service");
@@ -239,7 +326,7 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
        } else if (isEnabled()) {
            try {
                final SynchronousResultReceiver recv = new SynchronousResultReceiver();
                service.setVolume(device, volume, mAttributionSource, recv);
                service.setVolumeOffset(device, volumeOffset, mAttributionSource, recv);
                recv.awaitResultNoInterrupt(getSyncTimeout()).getValue(null);
            } catch (RemoteException | TimeoutException e) {
                Log.e(TAG, e.toString() + "\n" + Log.getStackTraceString(new Throwable()));
@@ -247,6 +334,28 @@ public final class BluetoothVolumeControl implements BluetoothProfile, AutoClose
        }
    }

    /**
     * Provides information about the possibility to set volume offset on the remote device.
     * If the remote device supports Volume Offset Control Service, it is automatically
     * connected.
     *
     * @param device {@link BluetoothDevice} representing the remote device
     * @return {@code true} if volume offset function is supported and available to use on the
     *         remote device. When Bluetooth is off, the return value should always be
     *         {@code false}.
     * @hide
     */
    @SystemApi
    @RequiresBluetoothConnectPermission
    @RequiresPermission(allOf = {
            android.Manifest.permission.BLUETOOTH_CONNECT,
            android.Manifest.permission.BLUETOOTH_PRIVILEGED,
    })
    public boolean isVolumeOffsetAvailable(@NonNull BluetoothDevice device) {
        if (DBG) log("isVolumeOffsetAvailable(" + device + ")");
        return false;
    }

    /**
     * Set connection policy of the profile
     *
+1 −1
Original line number Diff line number Diff line
@@ -45,7 +45,7 @@ oneway interface IBluetoothVolumeControl {
    void getConnectionPolicy(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);

    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
    void setVolume(in BluetoothDevice device, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    void setVolumeOffset(in BluetoothDevice device, int volumeOffset, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
    void setVolumeGroup(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
}