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