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