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