blob: 914e8fd9e07f8334e149b1793a9b5de8e057ecf0 [file] [log] [blame]
Wei Wang6d811182014-05-22 12:10:25 -07001/*
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;
Wei Wang6bf513d2014-08-01 11:12:37 -070022import android.util.ArrayMap;
Wei Wang6d811182014-05-22 12:10:25 -070023import android.util.Log;
Wei Wang6bf513d2014-08-01 11:12:37 -070024import android.util.SparseArray;
Wei Wang6d811182014-05-22 12:10:25 -070025
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.List;
Wei Wang6bf513d2014-08-01 11:12:37 -070029import java.util.Map;
Wei Wang6d811182014-05-22 12:10:25 -070030
31/**
32 * Represents a scan record from Bluetooth LE scan.
33 */
34public 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;
Jakub Pawlowski50f8f3d2017-05-26 13:59:55 -070050 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;
Wei Wang6d811182014-05-22 12:10:25 -070053 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
Wei Wang6bf513d2014-08-01 11:12:37 -070061 private final SparseArray<byte[]> mManufacturerSpecificData;
Wei Wang6d811182014-05-22 12:10:25 -070062
Wei Wang6bf513d2014-08-01 11:12:37 -070063 private final Map<ParcelUuid, byte[]> mServiceData;
Wei Wang6d811182014-05-22 12:10:25 -070064
65 // Transmission power level(in dB).
66 private final int mTxPowerLevel;
67
68 // Local name of the Bluetooth LE device.
Wei Wangaf74e662014-07-09 14:03:42 -070069 private final String mDeviceName;
Wei Wang6d811182014-05-22 12:10:25 -070070
Wei Wang685c17582014-07-16 22:02:03 -070071 // Raw bytes of scan record.
72 private final byte[] mBytes;
73
Wei Wang6d811182014-05-22 12:10:25 -070074 /**
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 /**
Wei Wangaf74e662014-07-09 14:03:42 -070083 * Returns a list of service UUIDs within the advertisement that are used to identify the
Wei Wang6bf513d2014-08-01 11:12:37 -070084 * bluetooth GATT services.
Wei Wang6d811182014-05-22 12:10:25 -070085 */
86 public List<ParcelUuid> getServiceUuids() {
87 return mServiceUuids;
88 }
89
90 /**
Wei Wang6bf513d2014-08-01 11:12:37 -070091 * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
92 * data.
Wei Wang6d811182014-05-22 12:10:25 -070093 */
Wei Wang6bf513d2014-08-01 11:12:37 -070094 public SparseArray<byte[]> getManufacturerSpecificData() {
Wei Wang6d811182014-05-22 12:10:25 -070095 return mManufacturerSpecificData;
96 }
97
98 /**
Wei Wang6bf513d2014-08-01 11:12:37 -070099 * Returns the manufacturer specific data associated with the manufacturer id. Returns
100 * {@code null} if the {@code manufacturerId} is not found.
Wei Wang6d811182014-05-22 12:10:25 -0700101 */
Wei Wang6bf513d2014-08-01 11:12:37 -0700102 @Nullable
103 public byte[] getManufacturerSpecificData(int manufacturerId) {
104 return mManufacturerSpecificData.get(manufacturerId);
Wei Wang6d811182014-05-22 12:10:25 -0700105 }
106
107 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700108 * Returns a map of service UUID and its corresponding service data.
Wei Wang6d811182014-05-22 12:10:25 -0700109 */
Wei Wang6bf513d2014-08-01 11:12:37 -0700110 public Map<ParcelUuid, byte[]> getServiceData() {
Wei Wang6d811182014-05-22 12:10:25 -0700111 return mServiceData;
112 }
113
114 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700115 * 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
119 public byte[] getServiceData(ParcelUuid serviceDataUuid) {
120 if (serviceDataUuid == null) {
121 return null;
122 }
123 return mServiceData.get(serviceDataUuid);
124 }
125
126 /**
Wei Wang6d811182014-05-22 12:10:25 -0700127 * 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 */
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
Wei Wangaf74e662014-07-09 14:03:42 -0700141 public String getDeviceName() {
142 return mDeviceName;
Wei Wang6d811182014-05-22 12:10:25 -0700143 }
144
Wei Wang685c17582014-07-16 22:02:03 -0700145 /**
146 * Returns raw bytes of scan record.
147 */
148 public byte[] getBytes() {
149 return mBytes;
150 }
151
Wei Wang6d811182014-05-22 12:10:25 -0700152 private ScanRecord(List<ParcelUuid> serviceUuids,
Wei Wang6bf513d2014-08-01 11:12:37 -0700153 SparseArray<byte[]> manufacturerData,
154 Map<ParcelUuid, byte[]> serviceData,
155 int advertiseFlags, int txPowerLevel,
Wei Wang685c17582014-07-16 22:02:03 -0700156 String localName, byte[] bytes) {
Wei Wang6d811182014-05-22 12:10:25 -0700157 mServiceUuids = serviceUuids;
Wei Wang6bf513d2014-08-01 11:12:37 -0700158 mManufacturerSpecificData = manufacturerData;
Wei Wang6d811182014-05-22 12:10:25 -0700159 mServiceData = serviceData;
Wei Wangaf74e662014-07-09 14:03:42 -0700160 mDeviceName = localName;
Wei Wang6d811182014-05-22 12:10:25 -0700161 mAdvertiseFlags = advertiseFlags;
162 mTxPowerLevel = txPowerLevel;
Wei Wang685c17582014-07-16 22:02:03 -0700163 mBytes = bytes;
Wei Wang6d811182014-05-22 12:10:25 -0700164 }
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.
Wei Wang685c17582014-07-16 22:02:03 -0700175 * @hide
Wei Wang6d811182014-05-22 12:10:25 -0700176 */
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;
Wei Wang6bf513d2014-08-01 11:12:37 -0700187
188 SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
189 Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
Wei Wang6d811182014-05-22 12:10:25 -0700190
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;
Jakub Pawlowski50f8f3d2017-05-26 13:59:55 -0700229 case DATA_TYPE_SERVICE_DATA_16_BIT:
230 case DATA_TYPE_SERVICE_DATA_32_BIT:
231 case DATA_TYPE_SERVICE_DATA_128_BIT:
Wei Wang6d811182014-05-22 12:10:25 -0700232 int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
Jakub Pawlowski50f8f3d2017-05-26 13:59:55 -0700233 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
Wei Wang6d811182014-05-22 12:10:25 -0700239 byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
240 serviceUuidLength);
Wei Wang6bf513d2014-08-01 11:12:37 -0700241 ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
242 serviceDataUuidBytes);
243 byte[] serviceDataArray = extractBytes(scanRecord,
244 currentPos + serviceUuidLength, dataLength - serviceUuidLength);
245 serviceData.put(serviceDataUuid, serviceDataArray);
Wei Wang6d811182014-05-22 12:10:25 -0700246 break;
247 case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
Wei Wang6d811182014-05-22 12:10:25 -0700248 // The first two bytes of the manufacturer specific data are
249 // manufacturer ids in little endian.
Wei Wang6bf513d2014-08-01 11:12:37 -0700250 int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
Wei Wangab2ed622014-07-25 15:14:55 -0700251 (scanRecord[currentPos] & 0xFF);
Wei Wang6bf513d2014-08-01 11:12:37 -0700252 byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
Wei Wangab2ed622014-07-25 15:14:55 -0700253 dataLength - 2);
Wei Wang6bf513d2014-08-01 11:12:37 -0700254 manufacturerData.put(manufacturerId, manufacturerDataBytes);
Wei Wang6d811182014-05-22 12:10:25 -0700255 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 }
Wei Wang6bf513d2014-08-01 11:12:37 -0700266 return new ScanRecord(serviceUuids, manufacturerData, serviceData,
267 advertiseFlag, txPowerLevel, localName, scanRecord);
Prerepa Viswanadham621085e2014-08-13 16:52:55 -0700268 } catch (Exception e) {
Wei Wang6d811182014-05-22 12:10:25 -0700269 Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
Prerepa Viswanadham621085e2014-08-13 16:52:55 -0700270 // 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);
Wei Wang6d811182014-05-22 12:10:25 -0700273 }
274 }
275
Wei Wang685c17582014-07-16 22:02:03 -0700276 @Override
277 public String toString() {
278 return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
Wei Wang833559d2014-08-29 10:26:13 -0700279 + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(mManufacturerSpecificData)
280 + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
Wei Wang685c17582014-07-16 22:02:03 -0700281 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
282 }
283
Wei Wangaf74e662014-07-09 14:03:42 -0700284 // Parse service UUIDs.
Wei Wang6d811182014-05-22 12:10:25 -0700285 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.
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}