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