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

Commit 0c6507d5 authored by Jack He's avatar Jack He
Browse files

Broadcast API adjustment

* Enforce raw bytes vs. structured value consistency in metadata classes
* Enforce required parameters in builder
* Rename getMaximumNumberOfBroadcast to getMaximumNumberOfBroadcasts
* Added utility method to parse and serialize LTV array

Fixes: 218683032
Bug: 218683032
Test: atest BluetoothInstrumentationTests, cts tests
Tag: #feature
Change-Id: Ia10f414bdc958b75e94276d3f645687f8b9635f9
parent 7328d681
Loading
Loading
Loading
Loading
+3 −3
Original line number Diff line number Diff line
@@ -658,7 +658,7 @@ public class LeAudioService extends ProfileService {
     * Get the maximum number of supported simultaneous broadcasts.
     * @return number of supported simultaneous broadcasts
     */
    public int getMaximumNumberOfBroadcast() {
    public int getMaximumNumberOfBroadcasts() {
        /* TODO: This is currently fixed to 1 */
        return 1;
    }
@@ -2067,13 +2067,13 @@ public class LeAudioService extends ProfileService {
        }

        @Override
        public void getMaximumNumberOfBroadcast(AttributionSource source,
        public void getMaximumNumberOfBroadcasts(AttributionSource source,
                SynchronousResultReceiver receiver) {
            try {
                int defaultValue = 0;
                LeAudioService service = getService(source);
                if (service != null) {
                    defaultValue = service.getMaximumNumberOfBroadcast();
                    defaultValue = service.getMaximumNumberOfBroadcasts();
                }
                receiver.send(defaultValue);
            } catch (RuntimeException e) {
+2 −2
Original line number Diff line number Diff line
@@ -154,7 +154,7 @@ public class LeAudioBroadcastServiceTest {

        BluetoothLeAudioContentMetadata.Builder meta_builder =
                new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("EN");
        meta_builder.setLanguage("deu");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();
        mService.createBroadcast(meta, code);
@@ -170,7 +170,7 @@ public class LeAudioBroadcastServiceTest {

        BluetoothLeAudioContentMetadata.Builder meta_builder =
        new BluetoothLeAudioContentMetadata.Builder();
        meta_builder.setLanguage("EN");
        meta_builder.setLanguage("eng");
        meta_builder.setProgramInfo("Public broadcast info");
        BluetoothLeAudioContentMetadata meta = meta_builder.build();
        mService.createBroadcast(meta, code);
+1 −1
Original line number Diff line number Diff line
@@ -378,7 +378,7 @@ package android.bluetooth {

  public final class BluetoothLeBroadcast implements java.lang.AutoCloseable android.bluetooth.BluetoothProfile {
    method @NonNull @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public java.util.List<android.bluetooth.BluetoothLeBroadcastMetadata> getAllBroadcastMetadata();
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getMaximumNumberOfBroadcast();
    method @RequiresPermission(android.Manifest.permission.BLUETOOTH_PRIVILEGED) public int getMaximumNumberOfBroadcasts();
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public boolean isPlaying(int);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.bluetooth.BluetoothLeBroadcast.Callback);
    method @RequiresPermission(allOf={android.Manifest.permission.BLUETOOTH_CONNECT, android.Manifest.permission.BLUETOOTH_PRIVILEGED}) public void startBroadcast(@NonNull android.bluetooth.BluetoothLeAudioContentMetadata, @Nullable byte[]);
+40 −6
Original line number Diff line number Diff line
@@ -18,9 +18,14 @@ package android.bluetooth;

import android.annotation.NonNull;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothUtils.TypeValueEntry;
import android.os.Parcel;
import android.os.Parcelable;

import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.List;

/**
 * A class representing the codec specific config metadata information defined in the Basic Audio
 * Profile.
@@ -29,7 +34,7 @@ import android.os.Parcelable;
 */
@SystemApi
public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable {
    private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
    private static final int AUDIO_CHANNEL_LOCATION_TYPE = 0x03;

    private final long mAudioLocation;
    private final byte[] mRawMetadata;
@@ -137,7 +142,21 @@ public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable {
        if (rawBytes == null) {
            throw new IllegalArgumentException("Raw bytes cannot be null");
        }
        return null;
        List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
        if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
            throw new IllegalArgumentException("No LTV entries are found from rawBytes of size "
                    + rawBytes.length);
        }
        long audioLocation = 0;
        for (TypeValueEntry entry : entries) {
            if (entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE) {
                byte[] bytes = entry.getValue();
                // Get unsigned uint32_t to long
                audioLocation = ((bytes[0] & 0xFF) <<  0) | ((bytes[1] & 0xFF) <<  8)
                        | ((bytes[2] & 0xFF) << 16) | ((long) (bytes[3] & 0xFF) << 24);
            }
        }
        return new BluetoothLeAudioCodecConfigMetadata(audioLocation, rawBytes);
    }

    /**
@@ -146,7 +165,7 @@ public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable {
     */
    @SystemApi
    public static final class Builder {
        private long mAudioLocation = UNKNOWN_VALUE_PLACEHOLDER;
        private long mAudioLocation = 0;
        private byte[] mRawMetadata = null;

        /**
@@ -191,10 +210,25 @@ public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable {
         */
        @SystemApi
        public @NonNull BluetoothLeAudioCodecConfigMetadata build() {
            if (mRawMetadata == null) {
                mRawMetadata = new byte[0];
            List<TypeValueEntry> entries = new ArrayList<>();
            if (mRawMetadata != null) {
                entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata);
                if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) {
                    throw new IllegalArgumentException("No LTV entries are found from rawBytes of"
                            + " size " + mRawMetadata.length + " please check the original object"
                            + " passed to Builder's copy constructor");
                }
            }
            if (mAudioLocation != 0) {
                entries.removeIf(entry -> entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE);
                entries.add(new TypeValueEntry(AUDIO_CHANNEL_LOCATION_TYPE,
                        ByteBuffer.allocate(Long.BYTES).putLong(mAudioLocation).array()));
            }
            byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries);
            if (rawBytes == null) {
                throw new IllegalArgumentException("Failed to serialize entries to bytes");
            }
            return new BluetoothLeAudioCodecConfigMetadata(mAudioLocation, mRawMetadata);
            return new BluetoothLeAudioCodecConfigMetadata(mAudioLocation, rawBytes);
        }
    }
}
+62 −5
Original line number Diff line number Diff line
@@ -19,9 +19,14 @@ package android.bluetooth;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.bluetooth.BluetoothUtils.TypeValueEntry;
import android.os.Parcel;
import android.os.Parcelable;

import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;

/**
 * A class representing the media metadata information defined in the Basic Audio Profile.
 *
@@ -29,6 +34,11 @@ import android.os.Parcelable;
 */
@SystemApi
public final class BluetoothLeAudioContentMetadata implements Parcelable {
    // From Generic Audio assigned numbers
    private static final int PROGRAM_INFO_TYPE = 0x03;
    private static final int LANGUAGE_TYPE = 0x04;
    private static final int LANGUAGE_LENGTH = 0x03;

    private final String mProgramInfo;
    private final String mLanguage;
    private final byte[] mRawMetadata;
@@ -137,7 +147,29 @@ public final class BluetoothLeAudioContentMetadata implements Parcelable {
        if (rawBytes == null) {
            throw new IllegalArgumentException("Raw bytes cannot be null");
        }
        return null;
        List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
        if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
            throw new IllegalArgumentException("No LTV entries are found from rawBytes of size "
                    + rawBytes.length);
        }
        String programInfo = null;
        String language = null;
        for (TypeValueEntry entry : entries) {
            // Only use the first value of each type
            if (programInfo == null && entry.getType() == PROGRAM_INFO_TYPE) {
                byte[] bytes = entry.getValue();
                programInfo = new String(bytes, StandardCharsets.UTF_8);
            } else if (language == null && entry.getType() == LANGUAGE_TYPE) {
                byte[] bytes = entry.getValue();
                if (bytes.length != LANGUAGE_LENGTH) {
                    throw new IllegalArgumentException("Language byte size " + bytes.length
                            + " is less than " + LANGUAGE_LENGTH + ", needed for ISO 639-3");
                }
                // Parse 3 bytes ISO 639-3 only
                language = new String(bytes, 0, LANGUAGE_LENGTH, StandardCharsets.US_ASCII);
            }
        }
        return new BluetoothLeAudioContentMetadata(programInfo, language, rawBytes);
    }

    /**
@@ -189,7 +221,7 @@ public final class BluetoothLeAudioContentMetadata implements Parcelable {
         * Set language of the audio stream in 3-byte, lower case language code as defined in
         * ISO 639-3.
         *
         * @return ISO 639-3 formatted language code, null if this metadata does not exist
         * @return this builder
         * @hide
         */
        @SystemApi
@@ -207,10 +239,35 @@ public final class BluetoothLeAudioContentMetadata implements Parcelable {
         */
        @SystemApi
        public @NonNull BluetoothLeAudioContentMetadata build() {
            if (mRawMetadata == null) {
                mRawMetadata = new byte[0];
            List<TypeValueEntry> entries = new ArrayList<>();
            if (mRawMetadata != null) {
                entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata);
                if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) {
                    throw new IllegalArgumentException("No LTV entries are found from rawBytes of"
                            + " size " + mRawMetadata.length + " please check the original object"
                            + " passed to Builder's copy constructor");
                }
            }
            if (mProgramInfo != null) {
                entries.removeIf(entry -> entry.getType() == PROGRAM_INFO_TYPE);
                entries.add(new TypeValueEntry(PROGRAM_INFO_TYPE,
                        mProgramInfo.getBytes(StandardCharsets.UTF_8)));
            }
            if (mLanguage != null) {
                String cleanedLanguage = mLanguage.toLowerCase().strip();
                byte[] languageBytes = cleanedLanguage.getBytes(StandardCharsets.US_ASCII);
                if (languageBytes.length != LANGUAGE_LENGTH) {
                    throw new IllegalArgumentException("Language byte size " + languageBytes.length
                            + " is less than " + LANGUAGE_LENGTH + ", needed ISO 639-3, to build");
                }
                entries.removeIf(entry -> entry.getType() == LANGUAGE_TYPE);
                entries.add(new TypeValueEntry(LANGUAGE_TYPE, languageBytes));
            }
            byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries);
            if (rawBytes == null) {
                throw new IllegalArgumentException("Failed to serialize entries to bytes");
            }
            return new BluetoothLeAudioContentMetadata(mProgramInfo, mLanguage, mRawMetadata);
            return new BluetoothLeAudioContentMetadata(mProgramInfo, mLanguage, rawBytes);
        }
    }
}
Loading