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