1 /* 2 * Copyright (C) 2022 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.bluetooth; 18 19 import android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.annotation.SystemApi; 22 import android.bluetooth.BluetoothUtils.TypeValueEntry; 23 import android.os.Parcel; 24 import android.os.Parcelable; 25 26 import java.nio.ByteBuffer; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 import java.util.Objects; 31 32 /** 33 * A class representing the codec specific config metadata information defined in the Basic Audio 34 * Profile. 35 * 36 * @hide 37 */ 38 @SystemApi 39 public final class BluetoothLeAudioCodecConfigMetadata implements Parcelable { 40 private static final int AUDIO_CHANNEL_LOCATION_TYPE = 0x03; 41 42 private final long mAudioLocation; 43 private final byte[] mRawMetadata; 44 BluetoothLeAudioCodecConfigMetadata(long audioLocation, byte[] rawMetadata)45 private BluetoothLeAudioCodecConfigMetadata(long audioLocation, byte[] rawMetadata) { 46 mAudioLocation = audioLocation; 47 mRawMetadata = rawMetadata; 48 } 49 50 @Override equals(@ullable Object o)51 public boolean equals(@Nullable Object o) { 52 if (o != null && o instanceof BluetoothLeAudioCodecConfigMetadata) { 53 final BluetoothLeAudioCodecConfigMetadata oth = (BluetoothLeAudioCodecConfigMetadata) o; 54 return mAudioLocation == oth.getAudioLocation() 55 && Arrays.equals(mRawMetadata, oth.getRawMetadata()); 56 } 57 return false; 58 } 59 60 @Override hashCode()61 public int hashCode() { 62 return Objects.hash(mAudioLocation, Arrays.hashCode(mRawMetadata)); 63 } 64 65 /** 66 * Get the audio location information as defined in the Generic Audio section of Bluetooth 67 * Assigned numbers. 68 * 69 * @return configured audio location, -1 if this metadata does not exist 70 * @hide 71 */ 72 @SystemApi getAudioLocation()73 public long getAudioLocation() { 74 return mAudioLocation; 75 } 76 77 /** 78 * Get the raw bytes of stream metadata in Bluetooth LTV format. 79 * 80 * Bluetooth LTV format for stream metadata is defined in the Generic Audio 81 * section of <a href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned Numbers</a>, 82 * including metadata that was not covered by the getter methods in this class. 83 * 84 * @return raw bytes of stream metadata in Bluetooth LTV format 85 * @hide 86 */ 87 @SystemApi getRawMetadata()88 public @NonNull byte[] getRawMetadata() { 89 return mRawMetadata; 90 } 91 92 /** 93 * {@inheritDoc} 94 * @hide 95 */ 96 @Override describeContents()97 public int describeContents() { 98 return 0; 99 } 100 101 /** 102 * {@inheritDoc} 103 * @hide 104 */ 105 @Override writeToParcel(Parcel out, int flags)106 public void writeToParcel(Parcel out, int flags) { 107 out.writeLong(mAudioLocation); 108 if (mRawMetadata != null) { 109 out.writeInt(mRawMetadata.length); 110 out.writeByteArray(mRawMetadata); 111 } else { 112 out.writeInt(-1); 113 } 114 } 115 116 /** 117 * A {@link Parcelable.Creator} to create {@link BluetoothLeAudioCodecConfigMetadata} from 118 * parcel. 119 * @hide 120 */ 121 @SystemApi 122 public static final @NonNull Parcelable.Creator<BluetoothLeAudioCodecConfigMetadata> CREATOR = 123 new Parcelable.Creator<BluetoothLeAudioCodecConfigMetadata>() { 124 @NonNull 125 public BluetoothLeAudioCodecConfigMetadata createFromParcel(@NonNull Parcel in) { 126 long audioLocation = in.readLong(); 127 int rawMetadataLen = in.readInt(); 128 byte[] rawMetadata; 129 if (rawMetadataLen != -1) { 130 rawMetadata = new byte[rawMetadataLen]; 131 in.readByteArray(rawMetadata); 132 } else { 133 rawMetadata = new byte[0]; 134 } 135 return new BluetoothLeAudioCodecConfigMetadata(audioLocation, rawMetadata); 136 } 137 138 public @NonNull BluetoothLeAudioCodecConfigMetadata[] newArray(int size) { 139 return new BluetoothLeAudioCodecConfigMetadata[size]; 140 } 141 }; 142 143 /** 144 * Construct a {@link BluetoothLeAudioCodecConfigMetadata} from raw bytes. 145 * 146 * The byte array will be parsed and values for each getter will be populated 147 * 148 * Raw metadata cannot be set using builder in order to maintain raw bytes and getter value 149 * consistency 150 * 151 * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format 152 * @return parsed {@link BluetoothLeAudioCodecConfigMetadata} object 153 * @throws IllegalArgumentException if <var>rawBytes</var> is null or when the raw bytes cannot 154 * be parsed to build the object 155 * @hide 156 */ 157 @SystemApi fromRawBytes( @onNull byte[] rawBytes)158 public static @NonNull BluetoothLeAudioCodecConfigMetadata fromRawBytes( 159 @NonNull byte[] rawBytes) { 160 if (rawBytes == null) { 161 throw new IllegalArgumentException("Raw bytes cannot be null"); 162 } 163 List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes); 164 if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) { 165 throw new IllegalArgumentException("No LTV entries are found from rawBytes of size " 166 + rawBytes.length); 167 } 168 long audioLocation = 0; 169 for (TypeValueEntry entry : entries) { 170 if (entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE) { 171 byte[] bytes = entry.getValue(); 172 // Get unsigned uint32_t to long 173 audioLocation = ((bytes[0] & 0xFF) << 0) | ((bytes[1] & 0xFF) << 8) 174 | ((bytes[2] & 0xFF) << 16) | ((long) (bytes[3] & 0xFF) << 24); 175 } 176 } 177 return new BluetoothLeAudioCodecConfigMetadata(audioLocation, rawBytes); 178 } 179 180 /** 181 * Builder for {@link BluetoothLeAudioCodecConfigMetadata}. 182 * @hide 183 */ 184 @SystemApi 185 public static final class Builder { 186 private long mAudioLocation = 0; 187 private byte[] mRawMetadata = null; 188 189 /** 190 * Create an empty builder. 191 * @hide 192 */ 193 @SystemApi Builder()194 public Builder() {} 195 196 /** 197 * Create a builder with copies of information from original object. 198 * 199 * @param original original object 200 * @hide 201 */ 202 @SystemApi Builder(@onNull BluetoothLeAudioCodecConfigMetadata original)203 public Builder(@NonNull BluetoothLeAudioCodecConfigMetadata original) { 204 mAudioLocation = original.getAudioLocation(); 205 mRawMetadata = original.getRawMetadata(); 206 } 207 208 /** 209 * Set the audio location information as defined in the Generic Audio section of Bluetooth 210 * Assigned numbers. 211 * 212 * @param audioLocation configured audio location, -1 if does not exist 213 * @return this builder 214 * @hide 215 */ 216 @SystemApi setAudioLocation(long audioLocation)217 public @NonNull Builder setAudioLocation(long audioLocation) { 218 mAudioLocation = audioLocation; 219 return this; 220 } 221 222 /** 223 * Build {@link BluetoothLeAudioCodecConfigMetadata}. 224 * 225 * @return constructed {@link BluetoothLeAudioCodecConfigMetadata} 226 * @throws IllegalArgumentException if the object cannot be built 227 * @hide 228 */ 229 @SystemApi build()230 public @NonNull BluetoothLeAudioCodecConfigMetadata build() { 231 List<TypeValueEntry> entries = new ArrayList<>(); 232 if (mRawMetadata != null) { 233 entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata); 234 if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) { 235 throw new IllegalArgumentException("No LTV entries are found from rawBytes of" 236 + " size " + mRawMetadata.length + " please check the original object" 237 + " passed to Builder's copy constructor"); 238 } 239 } 240 if (mAudioLocation != 0) { 241 entries.removeIf(entry -> entry.getType() == AUDIO_CHANNEL_LOCATION_TYPE); 242 entries.add(new TypeValueEntry(AUDIO_CHANNEL_LOCATION_TYPE, 243 ByteBuffer.allocate(Long.BYTES).putLong(mAudioLocation).array())); 244 } 245 byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries); 246 if (rawBytes == null) { 247 throw new IllegalArgumentException("Failed to serialize entries to bytes"); 248 } 249 return new BluetoothLeAudioCodecConfigMetadata(mAudioLocation, rawBytes); 250 } 251 } 252 } 253