1 /*
2  * Copyright (C) 2014 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.le;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.os.Parcel;
22 import android.os.ParcelUuid;
23 import android.os.Parcelable;
24 import android.util.ArrayMap;
25 import android.util.SparseArray;
26 
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.Objects;
32 
33 /**
34  * Advertise data packet container for Bluetooth LE advertising. This represents the data to be
35  * advertised as well as the scan response data for active scans.
36  *
37  * <p>Use {@link AdvertiseData.Builder} to create an instance of {@link AdvertiseData} to be
38  * advertised.
39  *
40  * @see BluetoothLeAdvertiser
41  * @see ScanRecord
42  */
43 public final class AdvertiseData implements Parcelable {
44 
45     @Nullable private final List<ParcelUuid> mServiceUuids;
46 
47     @NonNull private final List<ParcelUuid> mServiceSolicitationUuids;
48 
49     @Nullable private final List<TransportDiscoveryData> mTransportDiscoveryData;
50 
51     private final SparseArray<byte[]> mManufacturerSpecificData;
52     private final Map<ParcelUuid, byte[]> mServiceData;
53     private final boolean mIncludeTxPowerLevel;
54     private final boolean mIncludeDeviceName;
55 
AdvertiseData( List<ParcelUuid> serviceUuids, List<ParcelUuid> serviceSolicitationUuids, List<TransportDiscoveryData> transportDiscoveryData, SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData, boolean includeTxPowerLevel, boolean includeDeviceName)56     private AdvertiseData(
57             List<ParcelUuid> serviceUuids,
58             List<ParcelUuid> serviceSolicitationUuids,
59             List<TransportDiscoveryData> transportDiscoveryData,
60             SparseArray<byte[]> manufacturerData,
61             Map<ParcelUuid, byte[]> serviceData,
62             boolean includeTxPowerLevel,
63             boolean includeDeviceName) {
64         mServiceUuids = serviceUuids;
65         mServiceSolicitationUuids = serviceSolicitationUuids;
66         mTransportDiscoveryData = transportDiscoveryData;
67         mManufacturerSpecificData = manufacturerData;
68         mServiceData = serviceData;
69         mIncludeTxPowerLevel = includeTxPowerLevel;
70         mIncludeDeviceName = includeDeviceName;
71     }
72 
73     /**
74      * Returns a list of service UUIDs within the advertisement that are used to identify the
75      * Bluetooth GATT services.
76      */
getServiceUuids()77     public List<ParcelUuid> getServiceUuids() {
78         return mServiceUuids;
79     }
80 
81     /**
82      * Returns a list of service solicitation UUIDs within the advertisement that we invite to
83      * connect.
84      */
85     @NonNull
getServiceSolicitationUuids()86     public List<ParcelUuid> getServiceSolicitationUuids() {
87         return mServiceSolicitationUuids;
88     }
89 
90     /** Returns a list of {@link TransportDiscoveryData} within the advertisement. */
91     @NonNull
getTransportDiscoveryData()92     public List<TransportDiscoveryData> getTransportDiscoveryData() {
93         if (mTransportDiscoveryData == null) {
94             return Collections.emptyList();
95         }
96         return mTransportDiscoveryData;
97     }
98 
99     /**
100      * Returns an array of manufacturer Id and the corresponding manufacturer specific data. The
101      * manufacturer id is a non-negative number assigned by Bluetooth SIG.
102      */
getManufacturerSpecificData()103     public SparseArray<byte[]> getManufacturerSpecificData() {
104         return mManufacturerSpecificData;
105     }
106 
107     /** Returns a map of 16-bit UUID and its corresponding service data. */
getServiceData()108     public Map<ParcelUuid, byte[]> getServiceData() {
109         return mServiceData;
110     }
111 
112     /** Whether the transmission power level will be included in the advertisement packet. */
getIncludeTxPowerLevel()113     public boolean getIncludeTxPowerLevel() {
114         return mIncludeTxPowerLevel;
115     }
116 
117     /** Whether the device name will be included in the advertisement packet. */
getIncludeDeviceName()118     public boolean getIncludeDeviceName() {
119         return mIncludeDeviceName;
120     }
121 
122     /** @hide */
123     @Override
hashCode()124     public int hashCode() {
125         return Objects.hash(
126                 mServiceUuids,
127                 mServiceSolicitationUuids,
128                 mTransportDiscoveryData,
129                 mManufacturerSpecificData,
130                 mServiceData,
131                 mIncludeDeviceName,
132                 mIncludeTxPowerLevel);
133     }
134 
135     /** @hide */
136     @Override
equals(@ullable Object obj)137     public boolean equals(@Nullable Object obj) {
138         if (this == obj) {
139             return true;
140         }
141         if (obj == null || getClass() != obj.getClass()) {
142             return false;
143         }
144         AdvertiseData other = (AdvertiseData) obj;
145         return Objects.equals(mServiceUuids, other.mServiceUuids)
146                 && Objects.equals(mServiceSolicitationUuids, other.mServiceSolicitationUuids)
147                 && Objects.equals(mTransportDiscoveryData, other.mTransportDiscoveryData)
148                 && BluetoothLeUtils.equals(
149                         mManufacturerSpecificData, other.mManufacturerSpecificData)
150                 && BluetoothLeUtils.equals(mServiceData, other.mServiceData)
151                 && mIncludeDeviceName == other.mIncludeDeviceName
152                 && mIncludeTxPowerLevel == other.mIncludeTxPowerLevel;
153     }
154 
155     @Override
toString()156     public String toString() {
157         return "AdvertiseData [mServiceUuids="
158                 + mServiceUuids
159                 + ", mServiceSolicitationUuids="
160                 + mServiceSolicitationUuids
161                 + ", mTransportDiscoveryData="
162                 + mTransportDiscoveryData
163                 + ", mManufacturerSpecificData="
164                 + BluetoothLeUtils.toString(mManufacturerSpecificData)
165                 + ", mServiceData="
166                 + BluetoothLeUtils.toString(mServiceData)
167                 + ", mIncludeTxPowerLevel="
168                 + mIncludeTxPowerLevel
169                 + ", mIncludeDeviceName="
170                 + mIncludeDeviceName
171                 + "]";
172     }
173 
174     @Override
describeContents()175     public int describeContents() {
176         return 0;
177     }
178 
179     @Override
writeToParcel(Parcel dest, int flags)180     public void writeToParcel(Parcel dest, int flags) {
181         dest.writeTypedArray(mServiceUuids.toArray(new ParcelUuid[mServiceUuids.size()]), flags);
182         dest.writeTypedArray(
183                 mServiceSolicitationUuids.toArray(new ParcelUuid[mServiceSolicitationUuids.size()]),
184                 flags);
185 
186         dest.writeTypedList(mTransportDiscoveryData);
187 
188         // mManufacturerSpecificData could not be null.
189         dest.writeInt(mManufacturerSpecificData.size());
190         for (int i = 0; i < mManufacturerSpecificData.size(); ++i) {
191             dest.writeInt(mManufacturerSpecificData.keyAt(i));
192             dest.writeByteArray(mManufacturerSpecificData.valueAt(i));
193         }
194         dest.writeInt(mServiceData.size());
195         for (ParcelUuid uuid : mServiceData.keySet()) {
196             dest.writeTypedObject(uuid, flags);
197             dest.writeByteArray(mServiceData.get(uuid));
198         }
199         dest.writeByte((byte) (getIncludeTxPowerLevel() ? 1 : 0));
200         dest.writeByte((byte) (getIncludeDeviceName() ? 1 : 0));
201     }
202 
203     public static final @android.annotation.NonNull Parcelable.Creator<AdvertiseData> CREATOR =
204             new Creator<AdvertiseData>() {
205                 @Override
206                 public AdvertiseData[] newArray(int size) {
207                     return new AdvertiseData[size];
208                 }
209 
210                 @Override
211                 public AdvertiseData createFromParcel(Parcel in) {
212                     Builder builder = new Builder();
213                     ArrayList<ParcelUuid> uuids = in.createTypedArrayList(ParcelUuid.CREATOR);
214                     for (ParcelUuid uuid : uuids) {
215                         builder.addServiceUuid(uuid);
216                     }
217 
218                     ArrayList<ParcelUuid> solicitationUuids =
219                             in.createTypedArrayList(ParcelUuid.CREATOR);
220                     for (ParcelUuid uuid : solicitationUuids) {
221                         builder.addServiceSolicitationUuid(uuid);
222                     }
223 
224                     List<TransportDiscoveryData> transportDiscoveryData =
225                             in.createTypedArrayList(TransportDiscoveryData.CREATOR);
226                     for (TransportDiscoveryData tdd : transportDiscoveryData) {
227                         builder.addTransportDiscoveryData(tdd);
228                     }
229 
230                     int manufacturerSize = in.readInt();
231                     for (int i = 0; i < manufacturerSize; ++i) {
232                         int manufacturerId = in.readInt();
233                         byte[] manufacturerData = in.createByteArray();
234                         builder.addManufacturerData(manufacturerId, manufacturerData);
235                     }
236                     int serviceDataSize = in.readInt();
237                     for (int i = 0; i < serviceDataSize; ++i) {
238                         ParcelUuid serviceDataUuid = in.readTypedObject(ParcelUuid.CREATOR);
239                         byte[] serviceData = in.createByteArray();
240                         builder.addServiceData(serviceDataUuid, serviceData);
241                     }
242                     builder.setIncludeTxPowerLevel(in.readByte() == 1);
243                     builder.setIncludeDeviceName(in.readByte() == 1);
244                     return builder.build();
245                 }
246             };
247 
248     /** Builder for {@link AdvertiseData}. */
249     public static final class Builder {
250         @Nullable private List<ParcelUuid> mServiceUuids = new ArrayList<ParcelUuid>();
251         @NonNull private List<ParcelUuid> mServiceSolicitationUuids = new ArrayList<ParcelUuid>();
252 
253         @Nullable
254         private List<TransportDiscoveryData> mTransportDiscoveryData =
255                 new ArrayList<TransportDiscoveryData>();
256 
257         private SparseArray<byte[]> mManufacturerSpecificData = new SparseArray<byte[]>();
258         private Map<ParcelUuid, byte[]> mServiceData = new ArrayMap<ParcelUuid, byte[]>();
259         private boolean mIncludeTxPowerLevel;
260         private boolean mIncludeDeviceName;
261 
262         /**
263          * Add a service UUID to advertise data.
264          *
265          * @param serviceUuid A service UUID to be advertised.
266          * @throws IllegalArgumentException If the {@code serviceUuid} is null.
267          */
addServiceUuid(ParcelUuid serviceUuid)268         public Builder addServiceUuid(ParcelUuid serviceUuid) {
269             if (serviceUuid == null) {
270                 throw new IllegalArgumentException("serviceUuid is null");
271             }
272             mServiceUuids.add(serviceUuid);
273             return this;
274         }
275 
276         /**
277          * Add a service solicitation UUID to advertise data.
278          *
279          * @param serviceSolicitationUuid A service solicitation UUID to be advertised.
280          * @throws IllegalArgumentException If the {@code serviceSolicitationUuid} is null.
281          */
282         @NonNull
addServiceSolicitationUuid(@onNull ParcelUuid serviceSolicitationUuid)283         public Builder addServiceSolicitationUuid(@NonNull ParcelUuid serviceSolicitationUuid) {
284             if (serviceSolicitationUuid == null) {
285                 throw new IllegalArgumentException("serviceSolicitationUuid is null");
286             }
287             mServiceSolicitationUuids.add(serviceSolicitationUuid);
288             return this;
289         }
290 
291         /**
292          * Add service data to advertise data.
293          *
294          * @param serviceDataUuid 16-bit UUID of the service the data is associated with
295          * @param serviceData Service data
296          * @throws IllegalArgumentException If the {@code serviceDataUuid} or {@code serviceData} is
297          *     empty.
298          */
addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)299         public Builder addServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
300             if (serviceDataUuid == null || serviceData == null) {
301                 throw new IllegalArgumentException("serviceDataUuid or serviceDataUuid is null");
302             }
303             mServiceData.put(serviceDataUuid, serviceData);
304             return this;
305         }
306 
307         /**
308          * Add Transport Discovery Data to advertise data.
309          *
310          * @param transportDiscoveryData Transport Discovery Data, consisting of one or more
311          *     Transport Blocks. Transport Discovery Data AD Type Code is already included.
312          * @throws IllegalArgumentException If the {@code transportDiscoveryData} is empty
313          */
314         @NonNull
addTransportDiscoveryData( @onNull TransportDiscoveryData transportDiscoveryData)315         public Builder addTransportDiscoveryData(
316                 @NonNull TransportDiscoveryData transportDiscoveryData) {
317             if (transportDiscoveryData == null) {
318                 throw new IllegalArgumentException("transportDiscoveryData is null");
319             }
320             mTransportDiscoveryData.add(transportDiscoveryData);
321             return this;
322         }
323 
324         /**
325          * Add manufacturer specific data.
326          *
327          * <p>Please refer to the Bluetooth Assigned Numbers document provided by the <a
328          * href="https://www.bluetooth.org">Bluetooth SIG</a> for a list of existing company
329          * identifiers.
330          *
331          * @param manufacturerId Manufacturer ID assigned by Bluetooth SIG.
332          * @param manufacturerSpecificData Manufacturer specific data
333          * @throws IllegalArgumentException If the {@code manufacturerId} is negative or {@code
334          *     manufacturerSpecificData} is null.
335          */
addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData)336         public Builder addManufacturerData(int manufacturerId, byte[] manufacturerSpecificData) {
337             if (manufacturerId < 0) {
338                 throw new IllegalArgumentException("invalid manufacturerId - " + manufacturerId);
339             }
340             if (manufacturerSpecificData == null) {
341                 throw new IllegalArgumentException("manufacturerSpecificData is null");
342             }
343             mManufacturerSpecificData.put(manufacturerId, manufacturerSpecificData);
344             return this;
345         }
346 
347         /**
348          * Whether the transmission power level should be included in the advertise packet. Tx power
349          * level field takes 3 bytes in advertise packet.
350          */
setIncludeTxPowerLevel(boolean includeTxPowerLevel)351         public Builder setIncludeTxPowerLevel(boolean includeTxPowerLevel) {
352             mIncludeTxPowerLevel = includeTxPowerLevel;
353             return this;
354         }
355 
356         /** Set whether the device name should be included in advertise packet. */
setIncludeDeviceName(boolean includeDeviceName)357         public Builder setIncludeDeviceName(boolean includeDeviceName) {
358             mIncludeDeviceName = includeDeviceName;
359             return this;
360         }
361 
362         /** Build the {@link AdvertiseData}. */
build()363         public AdvertiseData build() {
364             return new AdvertiseData(
365                     mServiceUuids,
366                     mServiceSolicitationUuids,
367                     mTransportDiscoveryData,
368                     mManufacturerSpecificData,
369                     mServiceData,
370                     mIncludeTxPowerLevel,
371                     mIncludeDeviceName);
372         }
373     }
374 }
375