CREATOR =
new Creator<>() {
public @NonNull BluetoothLeAudioContentMetadata createFromParcel(
@NonNull Parcel in) {
final String programInfo = in.readString();
final String language = in.readString();
final int rawMetadataLength = in.readInt();
byte[] rawMetadata = new byte[rawMetadataLength];
in.readByteArray(rawMetadata);
return new BluetoothLeAudioContentMetadata(programInfo, language, rawMetadata);
}
public @NonNull BluetoothLeAudioContentMetadata[] newArray(int size) {
return new BluetoothLeAudioContentMetadata[size];
}
};
/**
* Construct a {@link BluetoothLeAudioContentMetadata} from raw bytes.
*
* The byte array will be parsed and values for each getter will be populated
*
*
Raw metadata cannot be set using builder in order to maintain raw bytes and getter value
* consistency
*
* @param rawBytes raw bytes of stream metadata in Bluetooth LTV format
* @return parsed {@link BluetoothLeAudioContentMetadata} object
* @throws IllegalArgumentException if rawBytes is null or when the raw bytes cannot
* be parsed to build the object
* @hide
*/
@SystemApi
public static @NonNull BluetoothLeAudioContentMetadata fromRawBytes(@NonNull byte[] rawBytes) {
if (rawBytes == null) {
throw new IllegalArgumentException("Raw bytes cannot be null");
}
List 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);
}
/**
* Builder for {@link BluetoothLeAudioContentMetadata}.
*
* @hide
*/
@SystemApi
public static final class Builder {
private String mProgramInfo = null;
private String mLanguage = null;
private byte[] mRawMetadata = null;
/**
* Create an empty builder
*
* @hide
*/
@SystemApi
public Builder() {}
/**
* Create a builder with copies of information from original object.
*
* @param original original object
* @hide
*/
@SystemApi
public Builder(@NonNull BluetoothLeAudioContentMetadata original) {
mProgramInfo = original.getProgramInfo();
mLanguage = original.getLanguage();
mRawMetadata = original.getRawMetadata();
}
/**
* Set the title and/or summary of Audio Stream content in UTF-8 format.
*
* @param programInfo title and/or summary of Audio Stream content in UTF-8 format, null if
* this metadata does not exist
* @return this builder
* @hide
*/
@SystemApi
public @NonNull Builder setProgramInfo(@Nullable String programInfo) {
mProgramInfo = programInfo;
return this;
}
/**
* Set language of the audio stream in 3-byte, lower case language code as defined in ISO
* 639-3.
*
* @return this builder
* @hide
*/
@SystemApi
public @NonNull Builder setLanguage(@Nullable String language) {
mLanguage = language;
return this;
}
/**
* Build {@link BluetoothLeAudioContentMetadata}.
*
* @return constructed {@link BluetoothLeAudioContentMetadata}
* @throws IllegalArgumentException if the object cannot be built
* @hide
*/
@SystemApi
public @NonNull BluetoothLeAudioContentMetadata build() {
List 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(Locale.US).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, rawBytes);
}
}
}