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