• 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     public static final @NonNull Parcelable.Creator<BluetoothLeAudioContentMetadata> CREATOR =
133             new Parcelable.Creator<BluetoothLeAudioContentMetadata>() {
134                 public @NonNull BluetoothLeAudioContentMetadata createFromParcel(
135                         @NonNull Parcel in) {
136                     final String programInfo = in.readString();
137                     final String language = in.readString();
138                     final int rawMetadataLength = in.readInt();
139                     byte[] rawMetadata = new byte[rawMetadataLength];
140                     in.readByteArray(rawMetadata);
141                     return new BluetoothLeAudioContentMetadata(programInfo, language, rawMetadata);
142                 }
143 
144                 public @NonNull BluetoothLeAudioContentMetadata[] newArray(int size) {
145                     return new BluetoothLeAudioContentMetadata[size];
146                 }
147             };
148 
149     /**
150      * Construct a {@link BluetoothLeAudioContentMetadata} from raw bytes.
151      *
152      * The byte array will be parsed and values for each getter will be populated
153      *
154      * Raw metadata cannot be set using builder in order to maintain raw bytes and getter value
155      * consistency
156      *
157      * @param rawBytes raw bytes of stream metadata in Bluetooth LTV format
158      * @return parsed {@link BluetoothLeAudioContentMetadata} object
159      * @throws IllegalArgumentException if <var>rawBytes</var> is null or when the raw bytes cannot
160      * be parsed to build the object
161      * @hide
162      */
163     @SystemApi
fromRawBytes(@onNull byte[] rawBytes)164     public static @NonNull BluetoothLeAudioContentMetadata fromRawBytes(@NonNull byte[] rawBytes) {
165         if (rawBytes == null) {
166             throw new IllegalArgumentException("Raw bytes cannot be null");
167         }
168         List<TypeValueEntry> entries = BluetoothUtils.parseLengthTypeValueBytes(rawBytes);
169         if (rawBytes.length > 0 && rawBytes[0] > 0 && entries.isEmpty()) {
170             throw new IllegalArgumentException("No LTV entries are found from rawBytes of size "
171                     + rawBytes.length);
172         }
173         String programInfo = null;
174         String language = null;
175         for (TypeValueEntry entry : entries) {
176             // Only use the first value of each type
177             if (programInfo == null && entry.getType() == PROGRAM_INFO_TYPE) {
178                 byte[] bytes = entry.getValue();
179                 programInfo = new String(bytes, StandardCharsets.UTF_8);
180             } else if (language == null && entry.getType() == LANGUAGE_TYPE) {
181                 byte[] bytes = entry.getValue();
182                 if (bytes.length != LANGUAGE_LENGTH) {
183                     throw new IllegalArgumentException("Language byte size " + bytes.length
184                             + " is less than " + LANGUAGE_LENGTH + ", needed for ISO 639-3");
185                 }
186                 // Parse 3 bytes ISO 639-3 only
187                 language = new String(bytes, 0, LANGUAGE_LENGTH, StandardCharsets.US_ASCII);
188             }
189         }
190         return new BluetoothLeAudioContentMetadata(programInfo, language, rawBytes);
191     }
192 
193     /**
194      * Builder for {@link BluetoothLeAudioContentMetadata}.
195      * @hide
196      */
197     @SystemApi
198     public static final class Builder {
199         private String mProgramInfo = null;
200         private String mLanguage = null;
201         private byte[] mRawMetadata = null;
202 
203         /**
204          * Create an empty builder
205          *
206          * @hide
207          */
208         @SystemApi
Builder()209         public Builder() {}
210 
211         /**
212          * Create a builder with copies of information from original object.
213          *
214          * @param original original object
215          * @hide
216          */
217         @SystemApi
Builder(@onNull BluetoothLeAudioContentMetadata original)218         public Builder(@NonNull BluetoothLeAudioContentMetadata original) {
219             mProgramInfo = original.getProgramInfo();
220             mLanguage = original.getLanguage();
221             mRawMetadata = original.getRawMetadata();
222         }
223 
224         /**
225          * Set the title and/or summary of Audio Stream content in UTF-8 format.
226          *
227          * @param programInfo  title and/or summary of Audio Stream content in UTF-8 format, null
228          *                     if this metadata does not exist
229          * @return this builder
230          * @hide
231          */
232         @SystemApi
setProgramInfo(@ullable String programInfo)233         public @NonNull Builder setProgramInfo(@Nullable String programInfo) {
234             mProgramInfo = programInfo;
235             return this;
236         }
237 
238         /**
239          * Set language of the audio stream in 3-byte, lower case language code as defined in
240          * ISO 639-3.
241          *
242          * @return this builder
243          * @hide
244          */
245         @SystemApi
setLanguage(@ullable String language)246         public @NonNull Builder setLanguage(@Nullable String language) {
247             mLanguage = language;
248             return this;
249         }
250 
251         /**
252          * Build {@link BluetoothLeAudioContentMetadata}.
253          *
254          * @return constructed {@link BluetoothLeAudioContentMetadata}
255          * @throws IllegalArgumentException if the object cannot be built
256          * @hide
257          */
258         @SystemApi
build()259         public @NonNull BluetoothLeAudioContentMetadata build() {
260             List<TypeValueEntry> entries = new ArrayList<>();
261             if (mRawMetadata != null) {
262                 entries = BluetoothUtils.parseLengthTypeValueBytes(mRawMetadata);
263                 if (mRawMetadata.length > 0 && mRawMetadata[0] > 0 && entries.isEmpty()) {
264                     throw new IllegalArgumentException("No LTV entries are found from rawBytes of"
265                             + " size " + mRawMetadata.length + " please check the original object"
266                             + " passed to Builder's copy constructor");
267                 }
268             }
269             if (mProgramInfo != null) {
270                 entries.removeIf(entry -> entry.getType() == PROGRAM_INFO_TYPE);
271                 entries.add(new TypeValueEntry(PROGRAM_INFO_TYPE,
272                         mProgramInfo.getBytes(StandardCharsets.UTF_8)));
273             }
274             if (mLanguage != null) {
275                 String cleanedLanguage = mLanguage.toLowerCase().strip();
276                 byte[] languageBytes = cleanedLanguage.getBytes(StandardCharsets.US_ASCII);
277                 if (languageBytes.length != LANGUAGE_LENGTH) {
278                     throw new IllegalArgumentException("Language byte size " + languageBytes.length
279                             + " is less than " + LANGUAGE_LENGTH + ", needed ISO 639-3, to build");
280                 }
281                 entries.removeIf(entry -> entry.getType() == LANGUAGE_TYPE);
282                 entries.add(new TypeValueEntry(LANGUAGE_TYPE, languageBytes));
283             }
284             byte[] rawBytes = BluetoothUtils.serializeTypeValue(entries);
285             if (rawBytes == null) {
286                 throw new IllegalArgumentException("Failed to serialize entries to bytes");
287             }
288             return new BluetoothLeAudioContentMetadata(mProgramInfo, mLanguage, rawBytes);
289         }
290     }
291 }
292