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

Commit c31210df authored by Michal Belusiak's avatar Michal Belusiak Committed by Jack He
Browse files

BluetoothVolumeControl: Improve Java API for Volume Control Offset

Add argument for selecting a VOCS instance in setVolumeOffset.
Add instance id argument for onVolumeOffsetChanged callback.
Add callbacks for audio location and audio description change.
Add method to read number of VOCS instances.

Bug: 322779934
Bug: 323156655
Tag: #feature
Test: atest VolumeControlServiceTest BluetoothVolumeControlTest
Change-Id: I080bfb20112ce37c1f6560cd254b4a0c9433b1c1
parent 2b941e91
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -51,7 +51,9 @@ oneway interface IBluetoothVolumeControl {
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void isVolumeOffsetAvailable(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setVolumeOffset(in BluetoothDevice device, int volumeOffset, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    void getNumberOfVolumeOffsetInstances(in BluetoothDevice device, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT,android.Manifest.permission.BLUETOOTH_PRIVILEGED})")
    void setVolumeOffset(in BluetoothDevice device, int instanceId, int volumeOffset, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
    void setGroupVolume(int group_id, int volume, in AttributionSource attributionSource, in SynchronousResultReceiver receiver);
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT)")
+3 −1
Original line number Diff line number Diff line
@@ -26,6 +26,8 @@ import java.util.List;
 * @hide
 */
oneway interface IBluetoothVolumeControlCallback {
    void onVolumeOffsetChanged(in BluetoothDevice device, in int volumeOffset);
    void onVolumeOffsetChanged(in BluetoothDevice device, in int instanceId, in int volumeOffset);
    void onVolumeOffsetAudioLocationChanged(in BluetoothDevice device, in int instanceId, in int audioLocation);
    void onVolumeOffsetAudioDescriptionChanged(in BluetoothDevice device, in int instanceId, in String audioDescription);
    void onDeviceVolumeChanged(in BluetoothDevice device, in int volume);
}
+145 −45
Original line number Diff line number Diff line
@@ -124,24 +124,6 @@ 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) {
@@ -576,21 +558,46 @@ public class VolumeControlService extends ProfileService {
        return true;
    }

    void setVolumeOffset(BluetoothDevice device, int volumeOffset) {
    int getNumberOfVolumeOffsetInstances(BluetoothDevice device) {
        VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
        if (offsets == null) {
            Log.i(TAG, " There is no offset service for device: " + device);
            return 0;
        }

        int numberOfInstances = offsets.size();

        Log.i(TAG, "Number of VOCS: " + numberOfInstances + ", for device: " + device);
        return numberOfInstances;
    }

    void setVolumeOffset(BluetoothDevice device, int instanceId, int volumeOffset) {
        VolumeControlOffsetDescriptor offsets = mAudioOffsets.get(device);
        if (offsets == null) {
            Log.e(TAG, " There is no offset service for device: " + device);
            return;
        }

        /* Use first offset always */
        int value = offsets.getValue(1);
        int numberOfInstances = offsets.size();
        if (instanceId > numberOfInstances) {
            Log.e(
                    TAG,
                    "Selected VOCS instance ID: "
                            + instanceId
                            + ", exceed available IDs: "
                            + numberOfInstances
                            + ", for device: "
                            + device);
            return;
        }

        int value = offsets.getValue(instanceId);
        if (value == volumeOffset) {
            /* Nothing to do - offset already applied */
            return;
        }

        mVolumeControlNativeInterface.setExtAudioOutVolumeOffset(device, 1, volumeOffset);
        mVolumeControlNativeInterface.setExtAudioOutVolumeOffset(device, instanceId, volumeOffset);
    }

    void setDeviceVolume(BluetoothDevice device, int volume, boolean isGroupOp) {
@@ -775,23 +782,51 @@ public class VolumeControlService extends ProfileService {
            for (Map.Entry<BluetoothDevice, VolumeControlOffsetDescriptor> entry :
                    mAudioOffsets.entrySet()) {
                VolumeControlOffsetDescriptor descriptor = entry.getValue();
                if (descriptor.size() == 0) {
                    continue;
                }

                for (int id = 1; id <= descriptor.size(); id++) {
                    BluetoothDevice device = entry.getKey();
                int offset = descriptor.getFirstOffsetValue();
                    int offset = descriptor.getValue(id);
                    int location = descriptor.getLocation(id);
                    String description = descriptor.getDescription(id);

                    if (DBG) {
                        Log.d(
                                TAG,
                            "notifyNewCallbackOfKnownVolumeInfo offset: " + device + ", " + offset);
                                "notifyNewCallbackOfKnownVolumeInfo, device: "
                                        + device
                                        + ", id: "
                                        + id
                                        + ", offset: "
                                        + offset
                                        + ", location: "
                                        + location
                                        + ", description: "
                                        + description);
                    }

                    try {
                    tempCallbackList.getBroadcastItem(i).onVolumeOffsetChanged(device, offset);
                        tempCallbackList
                                .getBroadcastItem(i)
                                .onVolumeOffsetChanged(device, id, offset);
                    } catch (RemoteException e) {
                    continue;
                        // Not every callback had to be defined; just continue
                    }
                    if (mFeatureFlags.leaudioMultipleVocsInstancesApi()) {
                        try {
                            tempCallbackList
                                    .getBroadcastItem(i)
                                    .onVolumeOffsetAudioLocationChanged(device, id, location);
                        } catch (RemoteException e) {
                            // Not every callback had to be defined; just continue
                        }
                        try {
                            tempCallbackList
                                    .getBroadcastItem(i)
                                    .onVolumeOffsetAudioDescriptionChanged(device, id, description);
                        } catch (RemoteException e) {
                            // Not every callback had to be defined; just continue
                        }
                    }
                }
            }
        }
@@ -1076,6 +1111,7 @@ public class VolumeControlService extends ProfileService {
        for (int i = 1; i <= numberOfExternalOutputs; i++) {
            offsets.add(i);
            mVolumeControlNativeInterface.getExtAudioOutVolumeOffset(device, i);
            mVolumeControlNativeInterface.getExtAudioOutLocation(device, i);
            mVolumeControlNativeInterface.getExtAudioOutDescription(device, i);
        }
    }
@@ -1098,7 +1134,7 @@ public class VolumeControlService extends ProfileService {
        int n = mCallbacks.beginBroadcast();
        for (int i = 0; i < n; i++) {
            try {
                mCallbacks.getBroadcastItem(i).onVolumeOffsetChanged(device, value);
                mCallbacks.getBroadcastItem(i).onVolumeOffsetChanged(device, id, value);
            } catch (RemoteException e) {
                continue;
            }
@@ -1118,10 +1154,28 @@ public class VolumeControlService extends ProfileService {
            return;
        }
        offsets.setLocation(id, location);

        if (mFeatureFlags.leaudioMultipleVocsInstancesApi()) {
            if (mCallbacks == null) {
                return;
            }

            int n = mCallbacks.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    mCallbacks
                            .getBroadcastItem(i)
                            .onVolumeOffsetAudioLocationChanged(device, id, location);
                } catch (RemoteException e) {
                    continue;
                }
            }
            mCallbacks.finishBroadcast();
        }
    }

    void handleDeviceExtAudioDescriptionChanged(BluetoothDevice device, int id,
                                                String description) {
    void handleDeviceExtAudioDescriptionChanged(
            BluetoothDevice device, int id, String description) {
        if (DBG) {
            Log.d(TAG, " device: " + device + " offset_id: "
                    + id + " description: " + description);
@@ -1133,6 +1187,24 @@ public class VolumeControlService extends ProfileService {
            return;
        }
        offsets.setDescription(id, description);

        if (mFeatureFlags.leaudioMultipleVocsInstancesApi()) {
            if (mCallbacks == null) {
                return;
            }

            int n = mCallbacks.beginBroadcast();
            for (int i = 0; i < n; i++) {
                try {
                    mCallbacks
                            .getBroadcastItem(i)
                            .onVolumeOffsetAudioDescriptionChanged(device, id, description);
                } catch (RemoteException e) {
                    continue;
                }
            }
            mCallbacks.finishBroadcast();
        }
    }

    void messageFromNative(VolumeControlStackEvent stackEvent) {
@@ -1569,8 +1641,10 @@ public class VolumeControlService extends ProfileService {
        }

        @Override
        public void isVolumeOffsetAvailable(BluetoothDevice device,
                AttributionSource source, SynchronousResultReceiver receiver) {
        public void isVolumeOffsetAvailable(
                BluetoothDevice device,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(device, "device cannot be null");
                Objects.requireNonNull(source, "source cannot be null");
@@ -1589,8 +1663,34 @@ public class VolumeControlService extends ProfileService {
        }

        @Override
        public void setVolumeOffset(BluetoothDevice device, int volumeOffset,
                AttributionSource source, SynchronousResultReceiver receiver) {
        public void getNumberOfVolumeOffsetInstances(
                BluetoothDevice device,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(device, "device cannot be null");
                Objects.requireNonNull(source, "source cannot be null");
                Objects.requireNonNull(receiver, "receiver cannot be null");

                int defaultValue = 0;
                VolumeControlService service = getService(source);
                if (service != null) {
                    enforceBluetoothPrivilegedPermission(service);
                    defaultValue = service.getNumberOfVolumeOffsetInstances(device);
                }
                receiver.send(defaultValue);
            } catch (RuntimeException e) {
                receiver.propagateException(e);
            }
        }

        @Override
        public void setVolumeOffset(
                BluetoothDevice device,
                int instanceId,
                int volumeOffset,
                AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                Objects.requireNonNull(device, "device cannot be null");
                Objects.requireNonNull(source, "source cannot be null");
@@ -1599,7 +1699,7 @@ public class VolumeControlService extends ProfileService {
                VolumeControlService service = getService(source);
                if (service != null) {
                    enforceBluetoothPrivilegedPermission(service);
                    service.setVolumeOffset(device, volumeOffset);
                    service.setVolumeOffset(device, instanceId, volumeOffset);
                }
                receiver.send(null);
            } catch (RuntimeException e) {
+80 −12
Original line number Diff line number Diff line
@@ -126,6 +126,7 @@ public class VolumeControlServiceTest {
        mFakeFlagsImpl = new FakeFeatureFlagsImpl();
        mFakeFlagsImpl.setFlag(
                Flags.FLAG_LEAUDIO_BROADCAST_VOLUME_CONTROL_FOR_CONNECTED_DEVICES, false);
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_MULTIPLE_VOCS_INSTANCES_API, false);

        VolumeControlNativeInterface.setInstance(mNativeInterface);
        mService = new VolumeControlService(mTargetContext, mFakeFlagsImpl);
@@ -1012,17 +1013,28 @@ public class VolumeControlServiceTest {
    public void testServiceBinderVolumeOffsetMethods() throws Exception {
        // Send a message to trigger connection completed
        generateDeviceAvailableMessageFromNative(mDevice, 2);

        final SynchronousResultReceiver<Boolean> boolRecv = SynchronousResultReceiver.get();
        boolean defaultRecvValue = false;
        boolean defaultAvailability = false;
        mServiceBinder.isVolumeOffsetAvailable(mDevice, mAttributionSource, boolRecv);
        Assert.assertTrue(boolRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
                .getValue(defaultRecvValue));
        Assert.assertTrue(
                boolRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
                        .getValue(defaultAvailability));

        final SynchronousResultReceiver<Integer> intRecv = SynchronousResultReceiver.get();
        int defaultNumberOfInstances = 0;
        mServiceBinder.getNumberOfVolumeOffsetInstances(mDevice, mAttributionSource, intRecv);
        int numberOfInstances =
                intRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS))
                        .getValue(defaultNumberOfInstances);
        Assert.assertEquals(2, numberOfInstances);

        int id = 1;
        int volumeOffset = 100;
        final SynchronousResultReceiver<Void> voidRecv = SynchronousResultReceiver.get();
        mServiceBinder.setVolumeOffset(mDevice, volumeOffset, mAttributionSource, voidRecv);
        mServiceBinder.setVolumeOffset(mDevice, id, volumeOffset, mAttributionSource, voidRecv);
        voidRecv.awaitResultNoInterrupt(Duration.ofMillis(TIMEOUT_MS));
        verify(mNativeInterface).setExtAudioOutVolumeOffset(mDevice, 1, volumeOffset);
        verify(mNativeInterface).setExtAudioOutVolumeOffset(mDevice, id, volumeOffset);
    }

    @Test
@@ -1097,6 +1109,8 @@ public class VolumeControlServiceTest {

    @Test
    public void testServiceBinderRegisterCallbackWhenDeviceAlreadyConnected() throws Exception {
        mFakeFlagsImpl.setFlag(Flags.FLAG_LEAUDIO_MULTIPLE_VOCS_INSTANCES_API, true);

        int groupId = 1;
        int groupVolume = 56;

@@ -1112,7 +1126,7 @@ public class VolumeControlServiceTest {
        doReturn(true).when(mNativeInterface).connectVolumeControl(any(BluetoothDevice.class));
        doReturn(true).when(mNativeInterface).disconnectVolumeControl(any(BluetoothDevice.class));

        generateDeviceAvailableMessageFromNative(mDevice, 1);
        generateDeviceAvailableMessageFromNative(mDevice, 2);
        generateConnectionMessageFromNative(
                mDevice, BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED);
        Assert.assertEquals(BluetoothProfile.STATE_CONNECTED, mService.getConnectionState(mDevice));
@@ -1131,9 +1145,21 @@ public class VolumeControlServiceTest {
        Assert.assertTrue(mService.getDevices().contains(mDeviceTwo));
        verify(mNativeInterface, times(1)).setVolume(eq(mDeviceTwo), eq(groupVolume));

        // Set different offset to both devices
        // Generate events for both devices
        generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 100);
        generateDeviceOffsetChangedMessageFromNative(mDeviceTwo, 1, 200);
        generateDeviceLocationChangedMessageFromNative(mDevice, 1, 1);
        final String testDevice1Desc1 = "testDevice1Desc1";
        generateDeviceDescriptionChangedMessageFromNative(mDevice, 1, testDevice1Desc1);

        generateDeviceOffsetChangedMessageFromNative(mDevice, 2, 200);
        generateDeviceLocationChangedMessageFromNative(mDevice, 2, 2);
        final String testDevice1Desc2 = "testDevice1Desc2";
        generateDeviceDescriptionChangedMessageFromNative(mDevice, 2, testDevice1Desc2);

        generateDeviceOffsetChangedMessageFromNative(mDeviceTwo, 1, 250);
        generateDeviceLocationChangedMessageFromNative(mDeviceTwo, 1, 3);
        final String testDevice2Desc = "testDevice2Desc";
        generateDeviceDescriptionChangedMessageFromNative(mDeviceTwo, 1, testDevice2Desc);

        // Register callback and verify it is called with known devices
        IBluetoothVolumeControlCallback callback =
@@ -1147,12 +1173,30 @@ public class VolumeControlServiceTest {
        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));
        verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(1), eq(100));
        verify(callback).onVolumeOffsetAudioLocationChanged(eq(mDevice), eq(1), eq(1));
        verify(callback)
                .onVolumeOffsetAudioDescriptionChanged(eq(mDevice), eq(1), eq(testDevice1Desc1));

        generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 50);
        verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(2), eq(200));
        verify(callback).onVolumeOffsetAudioLocationChanged(eq(mDevice), eq(2), eq(2));
        verify(callback)
                .onVolumeOffsetAudioDescriptionChanged(eq(mDevice), eq(2), eq(testDevice1Desc2));

        verify(callback).onVolumeOffsetChanged(eq(mDeviceTwo), eq(1), eq(250));
        verify(callback).onVolumeOffsetAudioLocationChanged(eq(mDeviceTwo), eq(1), eq(3));
        verify(callback)
                .onVolumeOffsetAudioDescriptionChanged(eq(mDeviceTwo), eq(1), eq(testDevice2Desc));

        verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(50));
        generateDeviceOffsetChangedMessageFromNative(mDevice, 1, 50);
        generateDeviceLocationChangedMessageFromNative(mDevice, 1, 0);
        final String testDevice1Desc3 = "testDevice1Desc3";
        generateDeviceDescriptionChangedMessageFromNative(mDevice, 1, testDevice1Desc3);

        verify(callback).onVolumeOffsetChanged(eq(mDevice), eq(1), eq(50));
        verify(callback).onVolumeOffsetAudioLocationChanged(eq(mDevice), eq(1), eq(0));
        verify(callback)
                .onVolumeOffsetAudioDescriptionChanged(eq(mDevice), eq(1), eq(testDevice1Desc3));
    }

    @Test
@@ -1512,6 +1556,30 @@ public class VolumeControlServiceTest {
        mService.messageFromNative(event);
    }

    private void generateDeviceLocationChangedMessageFromNative(
            BluetoothDevice device, int extOffsetIndex, int location) {
        // Send a message to trigger connection completed
        VolumeControlStackEvent event =
                new VolumeControlStackEvent(
                        VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_LOCATION_CHANGED);
        event.device = device;
        event.valueInt1 = extOffsetIndex; // external output index
        event.valueInt2 = location; // location
        mService.messageFromNative(event);
    }

    private void generateDeviceDescriptionChangedMessageFromNative(
            BluetoothDevice device, int extOffsetIndex, String description) {
        // Send a message to trigger connection completed
        VolumeControlStackEvent event =
                new VolumeControlStackEvent(
                        VolumeControlStackEvent.EVENT_TYPE_EXT_AUDIO_OUT_DESCRIPTION_CHANGED);
        event.device = device;
        event.valueInt1 = extOffsetIndex; // external output index
        event.valueString1 = description; // description
        mService.messageFromNative(event);
    }

    /**
     *  Helper function to test okToConnect() method
     *
+7 −2
Original line number Diff line number Diff line
@@ -1107,18 +1107,23 @@ package android.bluetooth {
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getConnectionPolicy(@NonNull android.bluetooth.BluetoothDevice);
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public int getConnectionState(android.bluetooth.BluetoothDevice);
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_CONNECT) public java.util.List<android.bluetooth.BluetoothDevice> getDevicesMatchingConnectionStates(int[]);
    method @FlaggedApi("com.android.bluetooth.flags.leaudio_multiple_vocs_instances_api") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public int getNumberOfVolumeOffsetInstances(@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 @FlaggedApi("com.android.bluetooth.flags.leaudio_broadcast_volume_control_for_connected_devices") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setDeviceVolume(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int, boolean);
    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 @Deprecated @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 @FlaggedApi("com.android.bluetooth.flags.leaudio_multiple_vocs_instances_api") @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void setVolumeOffset(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=1, to=255) int, @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 @FlaggedApi("com.android.bluetooth.flags.leaudio_broadcast_volume_control_for_connected_devices") public default void onDeviceVolumeChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0, to=255) int);
    method public void onVolumeOffsetChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0xffffff01, to=255) int);
    method @FlaggedApi("com.android.bluetooth.flags.leaudio_multiple_vocs_instances_api") public default void onVolumeOffsetAudioDescriptionChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=1, to=255) int, @NonNull String);
    method @FlaggedApi("com.android.bluetooth.flags.leaudio_multiple_vocs_instances_api") public default void onVolumeOffsetAudioLocationChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=1, to=255) int, @IntRange(from=0xffffff01, to=255) int);
    method @Deprecated public void onVolumeOffsetChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=0xffffff01, to=255) int);
    method @FlaggedApi("com.android.bluetooth.flags.leaudio_multiple_vocs_instances_api") public default void onVolumeOffsetChanged(@NonNull android.bluetooth.BluetoothDevice, @IntRange(from=1, to=255) int, @IntRange(from=0xffffff01, to=255) int);
  }

  public final class BufferConstraint implements android.os.Parcelable {
Loading