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