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