1 /* 2 * Copyright 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.os.Parcel; 23 import android.os.Parcelable; 24 25 import java.util.ArrayList; 26 import java.util.List; 27 import java.util.Objects; 28 29 /** 30 * This class contains the subgroup level information as defined in the BASE structure of Basic 31 * Audio profile. 32 * 33 * @hide 34 */ 35 @SystemApi 36 public final class BluetoothLeBroadcastSubgroup implements Parcelable { 37 private final long mCodecId; 38 private final BluetoothLeAudioCodecConfigMetadata mCodecSpecificConfig; 39 private final BluetoothLeAudioContentMetadata mContentMetadata; 40 private final List<BluetoothLeBroadcastChannel> mChannels; 41 BluetoothLeBroadcastSubgroup( long codecId, BluetoothLeAudioCodecConfigMetadata codecSpecificConfig, BluetoothLeAudioContentMetadata contentMetadata, List<BluetoothLeBroadcastChannel> channels)42 private BluetoothLeBroadcastSubgroup( 43 long codecId, 44 BluetoothLeAudioCodecConfigMetadata codecSpecificConfig, 45 BluetoothLeAudioContentMetadata contentMetadata, 46 List<BluetoothLeBroadcastChannel> channels) { 47 mCodecId = codecId; 48 mCodecSpecificConfig = codecSpecificConfig; 49 mContentMetadata = contentMetadata; 50 mChannels = channels; 51 } 52 53 @Override equals(@ullable Object o)54 public boolean equals(@Nullable Object o) { 55 if (!(o instanceof BluetoothLeBroadcastSubgroup)) { 56 return false; 57 } 58 final BluetoothLeBroadcastSubgroup other = (BluetoothLeBroadcastSubgroup) o; 59 return mCodecId == other.getCodecId() 60 && mCodecSpecificConfig.equals(other.getCodecSpecificConfig()) 61 && mContentMetadata.equals(other.getContentMetadata()) 62 && mChannels.equals(other.getChannels()); 63 } 64 65 @Override hashCode()66 public int hashCode() { 67 return Objects.hash(mCodecId, mCodecSpecificConfig, mContentMetadata, mChannels); 68 } 69 70 @Override toString()71 public String toString() { 72 return "BluetoothLeBroadcastSubgroup{" 73 + ("codecId=" + mCodecId) 74 + (", codecSpecificConfig=" + mCodecSpecificConfig) 75 + (", contentMetadata=" + mContentMetadata) 76 + (", channels=" + mChannels) 77 + '}'; 78 } 79 80 /** 81 * Get the codec ID field as defined by the Basic Audio Profile. 82 * 83 * <p>The codec ID field has 5 octets, with - Octet 0: Coding_Format as defined in Bluetooth 84 * Assigned Numbers - Octet 1-2: Company ID as defined in Bluetooth Assigned Numbers Shall be 85 * 0x0000 if octet 0 != 0xFF - Octet 3-4: Vendor-specific codec ID Shall be 0x0000 if octet 0 != 86 * 0xFF 87 * 88 * @return 5-byte codec ID field in Java long format 89 * @hide 90 */ 91 @SystemApi getCodecId()92 public long getCodecId() { 93 return mCodecId; 94 } 95 96 /** 97 * Get codec specific config metadata for this subgroup. 98 * 99 * @return codec specific config metadata for this subgroup 100 * @hide 101 */ 102 @SystemApi 103 @NonNull getCodecSpecificConfig()104 public BluetoothLeAudioCodecConfigMetadata getCodecSpecificConfig() { 105 return mCodecSpecificConfig; 106 } 107 108 /** 109 * Get content metadata for this Broadcast Source subgroup. 110 * 111 * @return content metadata for this Broadcast Source subgroup 112 * @hide 113 */ 114 @SystemApi getContentMetadata()115 public @NonNull BluetoothLeAudioContentMetadata getContentMetadata() { 116 return mContentMetadata; 117 } 118 119 /** 120 * Indicate if Broadcast Sink should have a preferred Broadcast Channel (BIS). 121 * 122 * <p>Only used by Broadcast Assistant and Sink. Ignored by Broadcast Source 123 * 124 * @return true if Broadcast Sink has at least one preferred Broadcast Channel (BIS) as 125 * indicated by {@link BluetoothLeBroadcastChannel#isSelected()} 126 * @hide 127 */ 128 @SystemApi hasChannelPreference()129 public boolean hasChannelPreference() { 130 return mChannels.stream().anyMatch(BluetoothLeBroadcastChannel::isSelected); 131 } 132 133 /** 134 * Get list of Broadcast Channels included in this Broadcast subgroup. 135 * 136 * <p>Each Broadcast Channel represents a Broadcast Isochronous Stream (BIS) 137 * 138 * <p>A Broadcast subgroup should contain at least 1 Broadcast Channel 139 * 140 * @return list of Broadcast Channels included in this Broadcast subgroup 141 * @hide 142 */ 143 @SystemApi getChannels()144 public @NonNull List<BluetoothLeBroadcastChannel> getChannels() { 145 return mChannels; 146 } 147 148 /** 149 * {@inheritDoc} 150 * 151 * @hide 152 */ 153 @Override describeContents()154 public int describeContents() { 155 return 0; 156 } 157 158 /** 159 * {@inheritDoc} 160 * 161 * @hide 162 */ 163 @Override writeToParcel(Parcel out, int flags)164 public void writeToParcel(Parcel out, int flags) { 165 out.writeLong(mCodecId); 166 out.writeTypedObject(mCodecSpecificConfig, 0); 167 out.writeTypedObject(mContentMetadata, 0); 168 out.writeTypedList(mChannels); 169 } 170 171 /** 172 * A {@link Parcelable.Creator} to create {@link BluetoothLeBroadcastSubgroup} from parcel. 173 * 174 * @hide 175 */ 176 @SystemApi @NonNull 177 public static final Creator<BluetoothLeBroadcastSubgroup> CREATOR = 178 new Creator<>() { 179 public @NonNull BluetoothLeBroadcastSubgroup createFromParcel(@NonNull Parcel in) { 180 Builder builder = new Builder(); 181 builder.setCodecId(in.readLong()); 182 builder.setCodecSpecificConfig( 183 in.readTypedObject(BluetoothLeAudioCodecConfigMetadata.CREATOR)); 184 builder.setContentMetadata( 185 in.readTypedObject(BluetoothLeAudioContentMetadata.CREATOR)); 186 List<BluetoothLeBroadcastChannel> channels = new ArrayList<>(); 187 in.readTypedList(channels, BluetoothLeBroadcastChannel.CREATOR); 188 for (BluetoothLeBroadcastChannel channel : channels) { 189 builder.addChannel(channel); 190 } 191 return builder.build(); 192 } 193 194 public @NonNull BluetoothLeBroadcastSubgroup[] newArray(int size) { 195 return new BluetoothLeBroadcastSubgroup[size]; 196 } 197 }; 198 199 private static final int UNKNOWN_VALUE_PLACEHOLDER = -1; 200 201 /** 202 * Builder for {@link BluetoothLeBroadcastSubgroup}. 203 * 204 * @hide 205 */ 206 @SystemApi 207 public static final class Builder { 208 private long mCodecId = UNKNOWN_VALUE_PLACEHOLDER; 209 private BluetoothLeAudioCodecConfigMetadata mCodecSpecificConfig = null; 210 private BluetoothLeAudioContentMetadata mContentMetadata = null; 211 private List<BluetoothLeBroadcastChannel> mChannels = new ArrayList<>(); 212 213 /** 214 * Create an empty constructor. 215 * 216 * @hide 217 */ 218 @SystemApi Builder()219 public Builder() {} 220 221 /** 222 * Create a builder with copies of information from original object. 223 * 224 * @param original original object 225 * @hide 226 */ 227 @SystemApi Builder(@onNull BluetoothLeBroadcastSubgroup original)228 public Builder(@NonNull BluetoothLeBroadcastSubgroup original) { 229 mCodecId = original.getCodecId(); 230 mCodecSpecificConfig = original.getCodecSpecificConfig(); 231 mContentMetadata = original.getContentMetadata(); 232 mChannels = original.getChannels(); 233 } 234 235 /** 236 * Set the codec ID field as defined by the Basic Audio Profile. 237 * 238 * <p>The codec ID field has 5 octets, with - Octet 0: Coding_Format as defined in Bluetooth 239 * Assigned Numbers - Octet 1-2: Company ID as defined in Bluetooth Assigned Numbers Shall 240 * be 0x0000 if octet 0 != 0xFF - Octet 3-4: Vendor-specific codec ID Shall be 0x0000 if 241 * octet 0 != 0xFF 242 * 243 * @param codecId 5-byte codec ID field in Java long format 244 * @return this builder 245 * @hide 246 */ 247 @SystemApi setCodecId(long codecId)248 public @NonNull Builder setCodecId(long codecId) { 249 mCodecId = codecId; 250 return this; 251 } 252 253 /** 254 * Set codec specific config metadata for this subgroup. 255 * 256 * @param codecSpecificConfig codec specific config metadata for this subgroup 257 * @throws NullPointerException if codecSpecificConfig is null 258 * @return this builder 259 * @hide 260 */ 261 @SystemApi 262 @NonNull setCodecSpecificConfig( @onNull BluetoothLeAudioCodecConfigMetadata codecSpecificConfig)263 public Builder setCodecSpecificConfig( 264 @NonNull BluetoothLeAudioCodecConfigMetadata codecSpecificConfig) { 265 Objects.requireNonNull(codecSpecificConfig, "codecSpecificConfig cannot be null"); 266 mCodecSpecificConfig = codecSpecificConfig; 267 return this; 268 } 269 270 /** 271 * Set content metadata for this Broadcast Source subgroup. 272 * 273 * @param contentMetadata content metadata for this Broadcast Source subgroup 274 * @throws NullPointerException if contentMetadata is null 275 * @return this builder 276 * @hide 277 */ 278 @SystemApi 279 @NonNull setContentMetadata( @onNull BluetoothLeAudioContentMetadata contentMetadata)280 public Builder setContentMetadata( 281 @NonNull BluetoothLeAudioContentMetadata contentMetadata) { 282 Objects.requireNonNull(contentMetadata, "contentMetadata cannot be null"); 283 mContentMetadata = contentMetadata; 284 return this; 285 } 286 287 /** 288 * Add a Broadcast Channel to this Broadcast subgroup. 289 * 290 * <p>Each Broadcast Channel represents a Broadcast Isochronous Stream (BIS) 291 * 292 * <p>A Broadcast subgroup should contain at least 1 Broadcast Channel 293 * 294 * @param channel a Broadcast Channel to be added to this Broadcast subgroup 295 * @throws NullPointerException if channel is null 296 * @return this builder 297 * @hide 298 */ 299 @SystemApi addChannel(@onNull BluetoothLeBroadcastChannel channel)300 public @NonNull Builder addChannel(@NonNull BluetoothLeBroadcastChannel channel) { 301 Objects.requireNonNull(channel, "channel cannot be null"); 302 mChannels.add(channel); 303 return this; 304 } 305 306 /** 307 * Clear channel list so that one can reset the builder after create it from an existing 308 * object. 309 * 310 * @return this builder 311 * @hide 312 */ 313 @SystemApi clearChannel()314 public @NonNull Builder clearChannel() { 315 mChannels.clear(); 316 return this; 317 } 318 319 /** 320 * Build {@link BluetoothLeBroadcastSubgroup}. 321 * 322 * @return constructed {@link BluetoothLeBroadcastSubgroup} 323 * @throws NullPointerException if {@link NonNull} items are null 324 * @throws IllegalArgumentException if the object cannot be built 325 * @hide 326 */ 327 @SystemApi build()328 public @NonNull BluetoothLeBroadcastSubgroup build() { 329 Objects.requireNonNull(mCodecSpecificConfig, "CodecSpecificConfig is null"); 330 Objects.requireNonNull(mContentMetadata, "ContentMetadata is null"); 331 if (mChannels.isEmpty()) { 332 throw new IllegalArgumentException("Must have at least one channel"); 333 } 334 return new BluetoothLeBroadcastSubgroup( 335 mCodecId, mCodecSpecificConfig, mContentMetadata, mChannels); 336 } 337 } 338 } 339