• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.charset.StandardCharsets;
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 media metadata information defined in the Basic Audio Profile.
34  *
35  * @hide
36  */
37 @SystemApi
38 public final class BluetoothLeAudioContentMetadata implements Parcelable {
39     // From Generic Audio assigned numbers
40     private static final int PROGRAM_INFO_TYPE = 0x03;
41     private static final int LANGUAGE_TYPE = 0x04;
42     private static final int LANGUAGE_LENGTH = 0x03;
43 
44     private final String mProgramInfo;
45     private final String mLanguage;
46     private final byte[] mRawMetadata;
47 
BluetoothLeAudioContentMetadata(String programInfo, String language, byte[] rawMetadata)48     private BluetoothLeAudioContentMetadata(String programInfo, String language,
49             byte[] rawMetadata) {
50         mProgramInfo = programInfo;
51         mLanguage = language;
52         mRawMetadata = rawMetadata;
53     }
54 
55     @Override
equals(@ullable Object o)56     public boolean equals(@Nullable Object o) {
57         if (!(o instanceof BluetoothLeAudioContentMetadata)) {
58             return false;
59         }
60         final BluetoothLeAudioContentMetadata other = (BluetoothLeAudioContentMetadata) o;
61         return Objects.equals(mProgramInfo, other.getProgramInfo())
62                 && Objects.equals(mLanguage, other.getLanguage())
63                 && Arrays.equals(mRawMetadata, other.getRawMetadata());
64     }
65 
66     @Override
hashCode()67     public int hashCode() {
68         return Objects.hash(mProgramInfo, mLanguage, Arrays.hashCode(mRawMetadata));
69     }
70 
71     /**
72      * Get the title and/or summary of Audio Stream content in UTF-8 format.
73      *
74      * @return title and/or summary of Audio Stream content in UTF-8 format, null if this metadata
75      * does not exist
76      * @hide
77      */
78     @SystemApi
getProgramInfo()79     public @Nullable String getProgramInfo() {
80         return mProgramInfo;
81     }
82 
83     /**
84      * Get language of the audio stream in 3-byte, lower case language code as defined in ISO 639-3.
85      *
86      * @return ISO 639-3 formatted language code, null if this metadata does not exist
87      * @hide
88      */
89     @SystemApi
getLanguage()90     public @Nullable String getLanguage() {
91         return mLanguage;
92     }
93 
94     /**
95      * Get the raw bytes of stream metadata in Bluetooth LTV format as defined in the Generic Audio
96      * section of <a href="https://www.bluetooth.com/specifications/assigned-numbers/">Bluetooth Assigned Numbers</a>,
97      * including metadata that was not covered by the getter methods in this class
98      *
99      * @return raw bytes of stream metadata in Bluetooth LTV format
100      */
getRawMetadata()101     public @NonNull byte[] getRawMetadata() {
102         return mRawMetadata;
103     }
104 
105 
106     /**
107      * {@inheritDoc}
108      * @hide
109      */
110     @Override
describeContents()111     public int describeContents() {
112         return 0;
113     }
114 
115     /**
116      * {@inheritDoc}
117      * @hide
118      */
119     @Override
writeToParcel(Parcel out, int flags)120     public void writeToParcel(Parcel out, int flags) {
121         out.writeString(mProgramInfo);
122         out.writeString(mLanguage);
123         out.writeInt(mRawMetadata.length);
124         out.writeByteArray(mRawMetadata);
125     }
126 
127     /**
128      * A {@link Parcelable.Creator} to create {@link BluetoothLeAudioContentMetadata} from parcel.
129      * @hide
130      */
131     @SystemApi
132     @NonNull
133     public static final Creator<BluetoothLeAudioContentMetadata> CREATOR = new Creator<>() {
134         public @NonNull BluetoothLeAudioContentMetadata createFromParcel(@NonNull Parcel in) {
135             final String programInfo = in.readString();
136             final String language = in.readString();
137             final int rawMetadataLength = in.readInt();
138             byte[] rawMetadata = new byte[rawMetadataLength];
139             in.readByteArray(rawMetadata);
140             return new BluetoothLeAudioContentMetadata(programInfo, language, rawMetadata);
141         }
142 
143         public @NonNull BluetoothLeAudioContentMetadata[] newArray(int size) {
144             return new BluetoothLeAudioContentMetadata[size];
145         }
146     };
147 
148     /**
149      * Construct a {@link BluetoothLeAudioContentMetadata} from raw bytes.
150      *
151      * The byte array will be parsed and values for each getter will be populated
152      *
153      * Raw metadata cannot be set using builder in order to maintain raw bytes and getter value
154      * consistency
155      *
156      * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format
157      * @return parsed {@link BluetoothLeAudioContentMetadata} object
158      * @throws IllegalArgumentException if <var>rawBytes</var> is null or when the raw bytes cannot
159      * be parsed to build the object
160      * @hide
161      */
162     @SystemApi
fromRawBytes(@onNull byte[] rawBytes)163     public static @NonNull BluetoothLeAudioContentMetadata fromRawBytes(@NonNull byte[] rawBytes) {
164         if (rawBytes == null) {
165             throw new IllegalArgumentException("Raw bytes cannot be null");
166         }
167         List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
168         if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
169             throw new IllegalArgumentException("No LTV entries are found from rawBytes of size "
170                     + rawBytes.length);
171         }
172         String programInfo = null;
173         String language = null;
174         for (TypeValueEntry entry : entries) {
175             // Only use the first value of each type
176             if (programInfo == null && entry.getType() == PROGRAM_INFO_TYPE) {
177                 byte[] bytes = entry.getValue();
178                 programInfo = new String(bytes, StandardCharsets.UTF_8);
179             } else if (language == null && entry.getType() == LANGUAGE_TYPE) {
180                 byte[] bytes = entry.getValue();
181                 if (bytes.length != LANGUAGE_LENGTH) {
182                     throw new IllegalArgumentException("Language byte size " + bytes.length
183                             + " is less than " + LANGUAGE_LENGTH + ", needed for ISO 639-3");
184                 }
185                 // Parse 3 bytes ISO 639-3 only
186                 language = new String(bytes, 0, LANGUAGE_LENGTH, StandardCharsets.US_ASCII);
187             }
188         }
189         return new BluetoothLeAudioContentMetadata(programInfo, language, rawBytes);
190     }
191 
192     /**
193      * Builder for {@link BluetoothLeAudioContentMetadata}.
194      * @hide
195      */
196     @SystemApi
197     public static final class Builder {
198         private String mProgramInfo = null;
199         private String mLanguage = null;
200         private byte[] mRawMetadata = null;
201 
202         /**
203          * Create an empty builder
204          *
205          * @hide
206          */
207         @SystemApi
Builder()208         public Builder() {}
209 
210         /**
211          * Create a builder with copies of information from original object.
212          *
213          * @param original original object
214          * @hide
215          */
216         @SystemApi
Builder(@onNull BluetoothLeAudioContentMetadata original)217         public Builder(@NonNull BluetoothLeAudioContentMetadata original) {
218             mProgramInfo = original.getProgramInfo();
219             mLanguage = original.getLanguage();
220             mRawMetadata = original.getRawMetadata();
221         }
222 
223         /**
224          * Set the title and/or summary of Audio Stream content in UTF-8 format.
225          *
226          * @param programInfo  title and/or summary of Audio Stream content in UTF-8 format, null
227          *                     if this metadata does not exist
228          * @return this builder
229          * @hide
230          */
231         @SystemApi
setProgramInfo(@ullable String programInfo)232         public @NonNull Builder setProgramInfo(@Nullable String programInfo) {
233             mProgramInfo = programInfo;
234             return this;
235         }
236 
237         /**
238          * Set language of the audio stream in 3-byte, lower case language code as defined in
239          * ISO 639-3.
240          *
241          * @return this builder
242          * @hide
243          */
244         @SystemApi
setLanguage(@ullable String language)245         public @NonNull Builder setLanguage(@Nullable String language) {
246             mLanguage = language;
247             return this;
248         }
249 
250         /**
251          * Build {@link BluetoothLeAudioContentMetadata}.
252          *
253          * @return constructed {@link BluetoothLeAudioContentMetadata}
254          * @throws IllegalArgumentException if the object cannot be built
255          * @hide
256          */
257         @SystemApi
build()258         public @NonNull BluetoothLeAudioContentMetadata build() {
259             List<TypeValueEntry> entries = new ArrayList<>();
260             if (mRawMetadata != null) {
261                 entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata);
262                 if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) {
263                     throw new IllegalArgumentException("No LTV entries are found from rawBytes of"
264                             + " size " + mRawMetadata.length + " please check the original object"
265                             + " passed to Builder's copy constructor");
266                 }
267             }
268             if (mProgramInfo != null) {
269                 entries.removeIf(entry -> entry.getType() == PROGRAM_INFO_TYPE);
270                 entries.add(new TypeValueEntry(PROGRAM_INFO_TYPE,
271                         mProgramInfo.getBytes(StandardCharsets.UTF_8)));
272             }
273             if (mLanguage != null) {
274                 String cleanedLanguage = mLanguage.toLowerCase().strip();
275                 byte[] languageBytes = cleanedLanguage.getBytes(StandardCharsets.US_ASCII);
276                 if (languageBytes.length != LANGUAGE_LENGTH) {
277                     throw new IllegalArgumentException("Language byte size " + languageBytes.length
278                             + " is less than " + LANGUAGE_LENGTH + ", needed ISO 639-3, to build");
279                 }
280                 entries.removeIf(entry -> entry.getType() == LANGUAGE_TYPE);
281                 entries.add(new TypeValueEntry(LANGUAGE_TYPE, languageBytes));
282             }
283             byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries);
284             if (rawBytes == null) {
285                 throw new IllegalArgumentException("Failed to serialize entries to bytes");
286             }
287             return new BluetoothLeAudioContentMetadata(mProgramInfo, mLanguage, rawBytes);
288         }
289     }
290 }
291