ScanRecord.java revision ab2ed62f15d0dac0f8ef825ff2d3677c9ae18f44
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 17package android.bluetooth.le; 18 19import android.annotation.Nullable; 20import android.bluetooth.BluetoothUuid; 21import android.os.ParcelUuid; 22import android.util.Log; 23 24import java.util.ArrayList; 25import java.util.Arrays; 26import java.util.List; 27 28/** 29 * Represents a scan record from Bluetooth LE scan. 30 */ 31public final class ScanRecord { 32 33 private static final String TAG = "ScanRecord"; 34 35 // The following data type values are assigned by Bluetooth SIG. 36 // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18. 37 private static final int DATA_TYPE_FLAGS = 0x01; 38 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02; 39 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03; 40 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04; 41 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05; 42 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06; 43 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07; 44 private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08; 45 private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09; 46 private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A; 47 private static final int DATA_TYPE_SERVICE_DATA = 0x16; 48 private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF; 49 50 // Flags of the advertising data. 51 private final int mAdvertiseFlags; 52 53 @Nullable 54 private final List<ParcelUuid> mServiceUuids; 55 56 private final int mManufacturerId; 57 @Nullable 58 private final byte[] mManufacturerSpecificData; 59 60 @Nullable 61 private final ParcelUuid mServiceDataUuid; 62 @Nullable 63 private final 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 */ 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 */ 86 public List<ParcelUuid> getServiceUuids() { 87 return mServiceUuids; 88 } 89 90 /** 91 * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth 92 * SIG. 93 */ 94 public int getManufacturerId() { 95 return mManufacturerId; 96 } 97 98 /** 99 * Returns the manufacturer specific data which is the content of manufacturer specific data 100 * field. 101 */ 102 public byte[] getManufacturerSpecificData() { 103 return mManufacturerSpecificData; 104 } 105 106 /** 107 * Returns a 16-bit UUID of the service that the service data is associated with. 108 */ 109 public ParcelUuid getServiceDataUuid() { 110 return mServiceDataUuid; 111 } 112 113 /** 114 * Returns service data. 115 */ 116 public byte[] getServiceData() { 117 return mServiceData; 118 } 119 120 /** 121 * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE} 122 * if the field is not set. This value can be used to calculate the path loss of a received 123 * packet using the following equation: 124 * <p> 125 * <code>pathloss = txPowerLevel - rssi</code> 126 */ 127 public int getTxPowerLevel() { 128 return mTxPowerLevel; 129 } 130 131 /** 132 * Returns the local name of the BLE device. The is a UTF-8 encoded string. 133 */ 134 @Nullable 135 public String getDeviceName() { 136 return mDeviceName; 137 } 138 139 /** 140 * Returns raw bytes of scan record. 141 */ 142 public byte[] getBytes() { 143 return mBytes; 144 } 145 146 private ScanRecord(List<ParcelUuid> serviceUuids, 147 ParcelUuid serviceDataUuid, byte[] serviceData, 148 int manufacturerId, 149 byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel, 150 String localName, byte[] bytes) { 151 mServiceUuids = serviceUuids; 152 mManufacturerId = manufacturerId; 153 mManufacturerSpecificData = manufacturerSpecificData; 154 mServiceDataUuid = serviceDataUuid; 155 mServiceData = serviceData; 156 mDeviceName = localName; 157 mAdvertiseFlags = advertiseFlags; 158 mTxPowerLevel = txPowerLevel; 159 mBytes = bytes; 160 } 161 162 /** 163 * Parse scan record bytes to {@link ScanRecord}. 164 * <p> 165 * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18. 166 * <p> 167 * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong> 168 * order. 169 * 170 * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response. 171 * 172 * @hide 173 */ 174 public static ScanRecord parseFromBytes(byte[] scanRecord) { 175 if (scanRecord == null) { 176 return null; 177 } 178 179 int currentPos = 0; 180 int advertiseFlag = -1; 181 List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>(); 182 String localName = null; 183 int txPowerLevel = Integer.MIN_VALUE; 184 ParcelUuid serviceDataUuid = null; 185 byte[] serviceData = null; 186 int manufacturerId = -1; 187 byte[] manufacturerSpecificData = null; 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 serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes); 234 serviceData = extractBytes(scanRecord, currentPos + 2, dataLength - 2); 235 break; 236 case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA: 237 // The first two bytes of the manufacturer specific data are 238 // manufacturer ids in little endian. 239 manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) + 240 (scanRecord[currentPos] & 0xFF); 241 manufacturerSpecificData = extractBytes(scanRecord, currentPos + 2, 242 dataLength - 2); 243 break; 244 default: 245 // Just ignore, we don't handle such data type. 246 break; 247 } 248 currentPos += dataLength; 249 } 250 251 if (serviceUuids.isEmpty()) { 252 serviceUuids = null; 253 } 254 return new ScanRecord(serviceUuids, serviceDataUuid, serviceData, 255 manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel, 256 localName, scanRecord); 257 } catch (IndexOutOfBoundsException e) { 258 Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord)); 259 return null; 260 } 261 } 262 263 @Override 264 public String toString() { 265 return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids 266 + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData=" 267 + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid=" 268 + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData) 269 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]"; 270 } 271 272 273 // Parse service UUIDs. 274 private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength, 275 int uuidLength, List<ParcelUuid> serviceUuids) { 276 while (dataLength > 0) { 277 byte[] uuidBytes = extractBytes(scanRecord, currentPos, 278 uuidLength); 279 serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes)); 280 dataLength -= uuidLength; 281 currentPos += uuidLength; 282 } 283 return currentPos; 284 } 285 286 // Helper method to extract bytes from byte array. 287 private static byte[] extractBytes(byte[] scanRecord, int start, int length) { 288 byte[] bytes = new byte[length]; 289 System.arraycopy(scanRecord, start, bytes, 0, length); 290 return bytes; 291 } 292} 293