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