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.annotation.SuppressLint; 22 import android.bluetooth.BluetoothUuid; 23 import android.compat.annotation.UnsupportedAppUsage; 24 import android.os.ParcelUuid; 25 import android.util.ArrayMap; 26 import android.util.Log; 27 import android.util.SparseArray; 28 29 import java.util.ArrayList; 30 import java.util.Arrays; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.function.Predicate; 34 35 /** 36 * Represents a scan record from Bluetooth LE scan. 37 */ 38 @SuppressLint("AndroidFrameworkBluetoothPermission") 39 public final class ScanRecord { 40 41 private static final String TAG = "ScanRecord"; 42 43 // The following data type values are assigned by Bluetooth SIG. 44 // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. 45 private static final int DATA_TYPE_FLAGS = 0x01; 46 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; 47 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; 48 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; 49 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; 50 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; 51 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; 52 private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; 53 private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; 54 private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; 55 private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16; 56 private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20; 57 private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21; 58 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14; 59 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F; 60 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15; 61 private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; 62 63 // Flags of the advertising data. 64 private final int mAdvertiseFlags; 65 66 @Nullable 67 private final List<ParcelUuid> mServiceUuids; 68 @Nullable 69 private final List<ParcelUuid> mServiceSolicitationUuids; 70 71 private final SparseArray<byte[]> mManufacturerSpecificData; 72 73 private final Map<ParcelUuid, byte[]> mServiceData; 74 75 // Transmission power level(in dB). 76 private final int mTxPowerLevel; 77 78 // Local name of the Bluetooth LE device. 79 private final String mDeviceName; 80 81 // Raw bytes of scan record. 82 private final byte[] mBytes; 83 84 /** 85 * Returns the advertising flags indicating the discoverable mode and capability of the device. 86 * Returns -1 if the flag field is not set. 87 */ getAdvertiseFlags()88 public int getAdvertiseFlags() { 89 return mAdvertiseFlags; 90 } 91 92 /** 93 * Returns a list of service UUIDs within the advertisement that are used to identify the 94 * bluetooth GATT services. 95 */ getServiceUuids()96 public List<ParcelUuid> getServiceUuids() { 97 return mServiceUuids; 98 } 99 100 /** 101 * Returns a list of service solicitation UUIDs within the advertisement that are used to 102 * identify the Bluetooth GATT services. 103 */ 104 @NonNull getServiceSolicitationUuids()105 public List<ParcelUuid> getServiceSolicitationUuids() { 106 return mServiceSolicitationUuids; 107 } 108 109 /** 110 * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific 111 * data. 112 */ getManufacturerSpecificData()113 public SparseArray<byte[]> getManufacturerSpecificData() { 114 return mManufacturerSpecificData; 115 } 116 117 /** 118 * Returns the manufacturer specific data associated with the manufacturer id. Returns 119 * {@code null} if the {@code manufacturerId} is not found. 120 */ 121 @Nullable getManufacturerSpecificData(int manufacturerId)122 public byte[] getManufacturerSpecificData(int manufacturerId) { 123 if (mManufacturerSpecificData == null) { 124 return null; 125 } 126 return mManufacturerSpecificData.get(manufacturerId); 127 } 128 129 /** 130 * Returns a map of service UUID and its corresponding service data. 131 */ getServiceData()132 public Map<ParcelUuid, byte[]> getServiceData() { 133 return mServiceData; 134 } 135 136 /** 137 * Returns the service data byte array associated with the {@code serviceUuid}. Returns 138 * {@code null} if the {@code serviceDataUuid} is not found. 139 */ 140 @Nullable getServiceData(ParcelUuid serviceDataUuid)141 public byte[] getServiceData(ParcelUuid serviceDataUuid) { 142 if (serviceDataUuid == null || mServiceData == null) { 143 return null; 144 } 145 return mServiceData.get(serviceDataUuid); 146 } 147 148 /** 149 * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} 150 * if the field is not set. This value can be used to calculate the path loss of a received 151 * packet using the following equation: 152 * <p> 153 * <code>pathloss = txPowerLevel - rssi</code> 154 */ getTxPowerLevel()155 public int getTxPowerLevel() { 156 return mTxPowerLevel; 157 } 158 159 /** 160 * Returns the local name of the BLE device. This is a UTF-8 encoded string. 161 */ 162 @Nullable getDeviceName()163 public String getDeviceName() { 164 return mDeviceName; 165 } 166 167 /** 168 * Returns raw bytes of scan record. 169 */ getBytes()170 public byte[] getBytes() { 171 return mBytes; 172 } 173 174 /** 175 * Test if any fields contained inside this scan record are matched by the 176 * given matcher. 177 * 178 * @hide 179 */ matchesAnyField(@onNull Predicate<byte[]> matcher)180 public boolean matchesAnyField(@NonNull Predicate<byte[]> matcher) { 181 int pos = 0; 182 while (pos < mBytes.length) { 183 final int length = mBytes[pos] & 0xFF; 184 if (length == 0) { 185 break; 186 } 187 if (matcher.test(Arrays.copyOfRange(mBytes, pos, pos + length + 1))) { 188 return true; 189 } 190 pos += length + 1; 191 } 192 return false; 193 } 194 ScanRecord(List<ParcelUuid> serviceUuids, List<ParcelUuid> serviceSolicitationUuids, SparseArray<byte[]> manufacturerData, Map<ParcelUuid, byte[]> serviceData, int advertiseFlags, int txPowerLevel, String localName, byte[] bytes)195 private ScanRecord(List<ParcelUuid> serviceUuids, 196 List<ParcelUuid> serviceSolicitationUuids, 197 SparseArray<byte[]> manufacturerData, 198 Map<ParcelUuid, byte[]> serviceData, 199 int advertiseFlags, int txPowerLevel, 200 String localName, byte[] bytes) { 201 mServiceSolicitationUuids = serviceSolicitationUuids; 202 mServiceUuids = serviceUuids; 203 mManufacturerSpecificData = manufacturerData; 204 mServiceData = serviceData; 205 mDeviceName = localName; 206 mAdvertiseFlags = advertiseFlags; 207 mTxPowerLevel = txPowerLevel; 208 mBytes = bytes; 209 } 210 211 /** 212 * Parse scan record bytes to {@link ScanRecord}. 213 * <p> 214 * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. 215 * <p> 216 * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> 217 * order. 218 * 219 * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. 220 * @hide 221 */ 222 @UnsupportedAppUsage parseFromBytes(byte[] scanRecord)223 public static ScanRecord parseFromBytes(byte[] scanRecord) { 224 if (scanRecord == null) { 225 return null; 226 } 227 228 int currentPos = 0; 229 int advertiseFlag = -1; 230 List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); 231 List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>(); 232 String localName = null; 233 int txPowerLevel = Integer.MIN_VALUE; 234 235 SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>(); 236 Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>(); 237 238 try { 239 while (currentPos < scanRecord.length) { 240 // length is unsigned int. 241 int length = scanRecord[currentPos++] & 0xFF; 242 if (length == 0) { 243 break; 244 } 245 // Note the length includes the length of the field type itself. 246 int dataLength = length - 1; 247 // fieldType is unsigned int. 248 int fieldType = scanRecord[currentPos++] & 0xFF; 249 switch (fieldType) { 250 case DATA_TYPE_FLAGS: 251 advertiseFlag = scanRecord[currentPos] & 0xFF; 252 break; 253 case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL: 254 case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE: 255 parseServiceUuid(scanRecord, currentPos, 256 dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids); 257 break; 258 case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL: 259 case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE: 260 parseServiceUuid(scanRecord, currentPos, dataLength, 261 BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids); 262 break; 263 case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL: 264 case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE: 265 parseServiceUuid(scanRecord, currentPos, dataLength, 266 BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids); 267 break; 268 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT: 269 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, 270 BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids); 271 break; 272 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT: 273 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, 274 BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids); 275 break; 276 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT: 277 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength, 278 BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids); 279 break; 280 case DATA_TYPE_LOCAL_NAME_SHORT: 281 case DATA_TYPE_LOCAL_NAME_COMPLETE: 282 localName = new String( 283 extractBytes(scanRecord, currentPos, dataLength)); 284 break; 285 case DATA_TYPE_TX_POWER_LEVEL: 286 txPowerLevel = scanRecord[currentPos]; 287 break; 288 case DATA_TYPE_SERVICE_DATA_16_BIT: 289 case DATA_TYPE_SERVICE_DATA_32_BIT: 290 case DATA_TYPE_SERVICE_DATA_128_BIT: 291 int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT; 292 if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) { 293 serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT; 294 } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) { 295 serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT; 296 } 297 298 byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos, 299 serviceUuidLength); 300 ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom( 301 serviceDataUuidBytes); 302 byte[] serviceDataArray = extractBytes(scanRecord, 303 currentPos + serviceUuidLength, dataLength - serviceUuidLength); 304 serviceData.put(serviceDataUuid, serviceDataArray); 305 break; 306 case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: 307 // The first two bytes of the manufacturer specific data are 308 // manufacturer ids in little endian. 309 int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) 310 + (scanRecord[currentPos] & 0xFF); 311 byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2, 312 dataLength - 2); 313 manufacturerData.put(manufacturerId, manufacturerDataBytes); 314 break; 315 default: 316 // Just ignore, we don't handle such data type. 317 break; 318 } 319 currentPos += dataLength; 320 } 321 322 if (serviceUuids.isEmpty()) { 323 serviceUuids = null; 324 } 325 return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData, 326 serviceData, advertiseFlag, txPowerLevel, localName, scanRecord); 327 } catch (Exception e) { 328 Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); 329 // As the record is invalid, ignore all the parsed results for this packet 330 // and return an empty record with raw scanRecord bytes in results 331 return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord); 332 } 333 } 334 335 @Override toString()336 public String toString() { 337 return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids 338 + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids 339 + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString( 340 mManufacturerSpecificData) 341 + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData) 342 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; 343 } 344 345 // Parse service UUIDs. parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, int uuidLength, List<ParcelUuid> serviceUuids)346 private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, 347 int uuidLength, List<ParcelUuid> serviceUuids) { 348 while (dataLength > 0) { 349 byte[] uuidBytes = extractBytes(scanRecord, currentPos, 350 uuidLength); 351 serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); 352 dataLength -= uuidLength; 353 currentPos += uuidLength; 354 } 355 return currentPos; 356 } 357 358 /** 359 * Parse service Solicitation UUIDs. 360 */ parseServiceSolicitationUuid(byte[] scanRecord, int currentPos, int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids)361 private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos, 362 int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) { 363 while (dataLength > 0) { 364 byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength); 365 serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); 366 dataLength -= uuidLength; 367 currentPos += uuidLength; 368 } 369 return currentPos; 370 } 371 372 // Helper method to extract bytes from byte array. extractBytes(byte[] scanRecord, int start, int length)373 private static byte[] extractBytes(byte[] scanRecord, int start, int length) { 374 byte[] bytes = new byte[length]; 375 System.arraycopy(scanRecord, start, bytes, 0, length); 376 return bytes; 377 } 378 } 379