blob: 7988008f03c0c441bf6be76e967a819fda30737d [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;
Mathew Inwood4dc66d32018-08-01 15:07:20 +010020import android.annotation.UnsupportedAppUsage;
Wei Wang6d811182014-05-22 12:10:25 -070021import android.bluetooth.BluetoothUuid;
22import android.os.ParcelUuid;
Wei Wang6bf513d2014-08-01 11:12:37 -070023import android.util.ArrayMap;
Wei Wang6d811182014-05-22 12:10:25 -070024import android.util.Log;
Wei Wang6bf513d2014-08-01 11:12:37 -070025import android.util.SparseArray;
Wei Wang6d811182014-05-22 12:10:25 -070026
27import java.util.ArrayList;
28import java.util.Arrays;
29import java.util.List;
Wei Wang6bf513d2014-08-01 11:12:37 -070030import java.util.Map;
Wei Wang6d811182014-05-22 12:10:25 -070031
32/**
33 * Represents a scan record from Bluetooth LE scan.
34 */
35public final class ScanRecord {
36
37 private static final String TAG = "ScanRecord";
38
39 // The following data type values are assigned by Bluetooth SIG.
40 // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
41 private static final int DATA_TYPE_FLAGS = 0x01;
42 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
43 private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
44 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
45 private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
46 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
47 private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
48 private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
49 private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
50 private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
Jakub Pawlowski45033a72017-05-26 13:59:55 -070051 private static final int DATA_TYPE_SERVICE_DATA_16_BIT = 0x16;
52 private static final int DATA_TYPE_SERVICE_DATA_32_BIT = 0x20;
53 private static final int DATA_TYPE_SERVICE_DATA_128_BIT = 0x21;
Nitin Shivpure1555eae2018-04-02 13:45:45 +053054 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT = 0x14;
55 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT = 0x1F;
56 private static final int DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT = 0x15;
Wei Wang6d811182014-05-22 12:10:25 -070057 private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
58
59 // Flags of the advertising data.
60 private final int mAdvertiseFlags;
61
62 @Nullable
63 private final List<ParcelUuid> mServiceUuids;
Nitin Shivpure1555eae2018-04-02 13:45:45 +053064 @Nullable
65 private final List<ParcelUuid> mServiceSolicitationUuids;
Wei Wang6d811182014-05-22 12:10:25 -070066
Wei Wang6bf513d2014-08-01 11:12:37 -070067 private final SparseArray<byte[]> mManufacturerSpecificData;
Wei Wang6d811182014-05-22 12:10:25 -070068
Wei Wang6bf513d2014-08-01 11:12:37 -070069 private final Map<ParcelUuid, byte[]> mServiceData;
Wei Wang6d811182014-05-22 12:10:25 -070070
71 // Transmission power level(in dB).
72 private final int mTxPowerLevel;
73
74 // Local name of the Bluetooth LE device.
Wei Wangaf74e662014-07-09 14:03:42 -070075 private final String mDeviceName;
Wei Wang6d811182014-05-22 12:10:25 -070076
Wei Wang685c17582014-07-16 22:02:03 -070077 // Raw bytes of scan record.
78 private final byte[] mBytes;
79
Wei Wang6d811182014-05-22 12:10:25 -070080 /**
81 * Returns the advertising flags indicating the discoverable mode and capability of the device.
82 * Returns -1 if the flag field is not set.
83 */
84 public int getAdvertiseFlags() {
85 return mAdvertiseFlags;
86 }
87
88 /**
Wei Wangaf74e662014-07-09 14:03:42 -070089 * Returns a list of service UUIDs within the advertisement that are used to identify the
Wei Wang6bf513d2014-08-01 11:12:37 -070090 * bluetooth GATT services.
Wei Wang6d811182014-05-22 12:10:25 -070091 */
92 public List<ParcelUuid> getServiceUuids() {
93 return mServiceUuids;
94 }
95
96 /**
Nitin Shivpure1555eae2018-04-02 13:45:45 +053097 * Returns a list of service solicitation UUIDs within the advertisement that are used to
98 * identify the Bluetooth GATT services.
99 */
100 @Nullable
101 public List<ParcelUuid> getServiceSolicitationUuids() {
102 return mServiceSolicitationUuids;
103 }
104
105 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700106 * Returns a sparse array of manufacturer identifier and its corresponding manufacturer specific
107 * data.
Wei Wang6d811182014-05-22 12:10:25 -0700108 */
Wei Wang6bf513d2014-08-01 11:12:37 -0700109 public SparseArray<byte[]> getManufacturerSpecificData() {
Wei Wang6d811182014-05-22 12:10:25 -0700110 return mManufacturerSpecificData;
111 }
112
113 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700114 * Returns the manufacturer specific data associated with the manufacturer id. Returns
115 * {@code null} if the {@code manufacturerId} is not found.
Wei Wang6d811182014-05-22 12:10:25 -0700116 */
Wei Wang6bf513d2014-08-01 11:12:37 -0700117 @Nullable
118 public byte[] getManufacturerSpecificData(int manufacturerId) {
119 return mManufacturerSpecificData.get(manufacturerId);
Wei Wang6d811182014-05-22 12:10:25 -0700120 }
121
122 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700123 * Returns a map of service UUID and its corresponding service data.
Wei Wang6d811182014-05-22 12:10:25 -0700124 */
Wei Wang6bf513d2014-08-01 11:12:37 -0700125 public Map<ParcelUuid, byte[]> getServiceData() {
Wei Wang6d811182014-05-22 12:10:25 -0700126 return mServiceData;
127 }
128
129 /**
Wei Wang6bf513d2014-08-01 11:12:37 -0700130 * Returns the service data byte array associated with the {@code serviceUuid}. Returns
131 * {@code null} if the {@code serviceDataUuid} is not found.
132 */
133 @Nullable
134 public byte[] getServiceData(ParcelUuid serviceDataUuid) {
Jakub Pawlowski59880122018-06-04 10:32:06 -0700135 if (serviceDataUuid == null || mServiceData == null) {
Wei Wang6bf513d2014-08-01 11:12:37 -0700136 return null;
137 }
138 return mServiceData.get(serviceDataUuid);
139 }
140
141 /**
Wei Wang6d811182014-05-22 12:10:25 -0700142 * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
143 * if the field is not set. This value can be used to calculate the path loss of a received
144 * packet using the following equation:
145 * <p>
146 * <code>pathloss = txPowerLevel - rssi</code>
147 */
148 public int getTxPowerLevel() {
149 return mTxPowerLevel;
150 }
151
152 /**
153 * Returns the local name of the BLE device. The is a UTF-8 encoded string.
154 */
155 @Nullable
Wei Wangaf74e662014-07-09 14:03:42 -0700156 public String getDeviceName() {
157 return mDeviceName;
Wei Wang6d811182014-05-22 12:10:25 -0700158 }
159
Wei Wang685c17582014-07-16 22:02:03 -0700160 /**
161 * Returns raw bytes of scan record.
162 */
163 public byte[] getBytes() {
164 return mBytes;
165 }
166
Wei Wang6d811182014-05-22 12:10:25 -0700167 private ScanRecord(List<ParcelUuid> serviceUuids,
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530168 List<ParcelUuid> serviceSolicitationUuids,
Wei Wang6bf513d2014-08-01 11:12:37 -0700169 SparseArray<byte[]> manufacturerData,
170 Map<ParcelUuid, byte[]> serviceData,
171 int advertiseFlags, int txPowerLevel,
Wei Wang685c17582014-07-16 22:02:03 -0700172 String localName, byte[] bytes) {
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530173 mServiceSolicitationUuids = serviceSolicitationUuids;
Wei Wang6d811182014-05-22 12:10:25 -0700174 mServiceUuids = serviceUuids;
Wei Wang6bf513d2014-08-01 11:12:37 -0700175 mManufacturerSpecificData = manufacturerData;
Wei Wang6d811182014-05-22 12:10:25 -0700176 mServiceData = serviceData;
Wei Wangaf74e662014-07-09 14:03:42 -0700177 mDeviceName = localName;
Wei Wang6d811182014-05-22 12:10:25 -0700178 mAdvertiseFlags = advertiseFlags;
179 mTxPowerLevel = txPowerLevel;
Wei Wang685c17582014-07-16 22:02:03 -0700180 mBytes = bytes;
Wei Wang6d811182014-05-22 12:10:25 -0700181 }
182
183 /**
184 * Parse scan record bytes to {@link ScanRecord}.
185 * <p>
186 * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
187 * <p>
188 * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
189 * order.
190 *
191 * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
Wei Wang685c17582014-07-16 22:02:03 -0700192 * @hide
Wei Wang6d811182014-05-22 12:10:25 -0700193 */
Mathew Inwood4dc66d32018-08-01 15:07:20 +0100194 @UnsupportedAppUsage
Wei Wang6d811182014-05-22 12:10:25 -0700195 public static ScanRecord parseFromBytes(byte[] scanRecord) {
196 if (scanRecord == null) {
197 return null;
198 }
199
200 int currentPos = 0;
201 int advertiseFlag = -1;
202 List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530203 List<ParcelUuid> serviceSolicitationUuids = new ArrayList<ParcelUuid>();
Wei Wang6d811182014-05-22 12:10:25 -0700204 String localName = null;
205 int txPowerLevel = Integer.MIN_VALUE;
Wei Wang6bf513d2014-08-01 11:12:37 -0700206
207 SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
208 Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
Wei Wang6d811182014-05-22 12:10:25 -0700209
210 try {
211 while (currentPos < scanRecord.length) {
212 // length is unsigned int.
213 int length = scanRecord[currentPos++] & 0xFF;
214 if (length == 0) {
215 break;
216 }
217 // Note the length includes the length of the field type itself.
218 int dataLength = length - 1;
219 // fieldType is unsigned int.
220 int fieldType = scanRecord[currentPos++] & 0xFF;
221 switch (fieldType) {
222 case DATA_TYPE_FLAGS:
223 advertiseFlag = scanRecord[currentPos] & 0xFF;
224 break;
225 case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
226 case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
227 parseServiceUuid(scanRecord, currentPos,
228 dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
229 break;
230 case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
231 case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
232 parseServiceUuid(scanRecord, currentPos, dataLength,
233 BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
234 break;
235 case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
236 case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
237 parseServiceUuid(scanRecord, currentPos, dataLength,
238 BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
239 break;
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530240 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_16_BIT:
241 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
242 BluetoothUuid.UUID_BYTES_16_BIT, serviceSolicitationUuids);
243 break;
244 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_32_BIT:
245 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
246 BluetoothUuid.UUID_BYTES_32_BIT, serviceSolicitationUuids);
247 break;
248 case DATA_TYPE_SERVICE_SOLICITATION_UUIDS_128_BIT:
249 parseServiceSolicitationUuid(scanRecord, currentPos, dataLength,
250 BluetoothUuid.UUID_BYTES_128_BIT, serviceSolicitationUuids);
251 break;
Wei Wang6d811182014-05-22 12:10:25 -0700252 case DATA_TYPE_LOCAL_NAME_SHORT:
253 case DATA_TYPE_LOCAL_NAME_COMPLETE:
254 localName = new String(
255 extractBytes(scanRecord, currentPos, dataLength));
256 break;
257 case DATA_TYPE_TX_POWER_LEVEL:
258 txPowerLevel = scanRecord[currentPos];
259 break;
Jakub Pawlowski45033a72017-05-26 13:59:55 -0700260 case DATA_TYPE_SERVICE_DATA_16_BIT:
261 case DATA_TYPE_SERVICE_DATA_32_BIT:
262 case DATA_TYPE_SERVICE_DATA_128_BIT:
Wei Wang6d811182014-05-22 12:10:25 -0700263 int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
Jakub Pawlowski45033a72017-05-26 13:59:55 -0700264 if (fieldType == DATA_TYPE_SERVICE_DATA_32_BIT) {
Jack Hea355e5e2017-08-22 16:06:54 -0700265 serviceUuidLength = BluetoothUuid.UUID_BYTES_32_BIT;
Jakub Pawlowski45033a72017-05-26 13:59:55 -0700266 } else if (fieldType == DATA_TYPE_SERVICE_DATA_128_BIT) {
Jack Hea355e5e2017-08-22 16:06:54 -0700267 serviceUuidLength = BluetoothUuid.UUID_BYTES_128_BIT;
Jakub Pawlowski45033a72017-05-26 13:59:55 -0700268 }
269
Wei Wang6d811182014-05-22 12:10:25 -0700270 byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
271 serviceUuidLength);
Wei Wang6bf513d2014-08-01 11:12:37 -0700272 ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
273 serviceDataUuidBytes);
274 byte[] serviceDataArray = extractBytes(scanRecord,
275 currentPos + serviceUuidLength, dataLength - serviceUuidLength);
276 serviceData.put(serviceDataUuid, serviceDataArray);
Wei Wang6d811182014-05-22 12:10:25 -0700277 break;
278 case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
Wei Wang6d811182014-05-22 12:10:25 -0700279 // The first two bytes of the manufacturer specific data are
280 // manufacturer ids in little endian.
Jack He2992cd02017-08-22 21:21:23 -0700281 int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8)
282 + (scanRecord[currentPos] & 0xFF);
Wei Wang6bf513d2014-08-01 11:12:37 -0700283 byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
Wei Wangab2ed622014-07-25 15:14:55 -0700284 dataLength - 2);
Wei Wang6bf513d2014-08-01 11:12:37 -0700285 manufacturerData.put(manufacturerId, manufacturerDataBytes);
Wei Wang6d811182014-05-22 12:10:25 -0700286 break;
287 default:
288 // Just ignore, we don't handle such data type.
289 break;
290 }
291 currentPos += dataLength;
292 }
293
294 if (serviceUuids.isEmpty()) {
295 serviceUuids = null;
296 }
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530297 if (serviceSolicitationUuids.isEmpty()) {
298 serviceSolicitationUuids = null;
299 }
300 return new ScanRecord(serviceUuids, serviceSolicitationUuids, manufacturerData,
301 serviceData, advertiseFlag, txPowerLevel, localName, scanRecord);
Prerepa Viswanadham621085e2014-08-13 16:52:55 -0700302 } catch (Exception e) {
Wei Wang6d811182014-05-22 12:10:25 -0700303 Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
Prerepa Viswanadham621085e2014-08-13 16:52:55 -0700304 // As the record is invalid, ignore all the parsed results for this packet
305 // and return an empty record with raw scanRecord bytes in results
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530306 return new ScanRecord(null, null, null, null, -1, Integer.MIN_VALUE, null, scanRecord);
Wei Wang6d811182014-05-22 12:10:25 -0700307 }
308 }
309
Wei Wang685c17582014-07-16 22:02:03 -0700310 @Override
311 public String toString() {
312 return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530313 + ", mServiceSolicitationUuids=" + mServiceSolicitationUuids
Jack Hea355e5e2017-08-22 16:06:54 -0700314 + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(
315 mManufacturerSpecificData)
Wei Wang833559d2014-08-29 10:26:13 -0700316 + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
Wei Wang685c17582014-07-16 22:02:03 -0700317 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
318 }
319
Wei Wangaf74e662014-07-09 14:03:42 -0700320 // Parse service UUIDs.
Wei Wang6d811182014-05-22 12:10:25 -0700321 private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
322 int uuidLength, List<ParcelUuid> serviceUuids) {
323 while (dataLength > 0) {
324 byte[] uuidBytes = extractBytes(scanRecord, currentPos,
325 uuidLength);
326 serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
327 dataLength -= uuidLength;
328 currentPos += uuidLength;
329 }
330 return currentPos;
331 }
332
Nitin Shivpure1555eae2018-04-02 13:45:45 +0530333 /**
334 * Parse service Solicitation UUIDs.
335 */
336 private static int parseServiceSolicitationUuid(byte[] scanRecord, int currentPos,
337 int dataLength, int uuidLength, List<ParcelUuid> serviceSolicitationUuids) {
338 while (dataLength > 0) {
339 byte[] uuidBytes = extractBytes(scanRecord, currentPos, uuidLength);
340 serviceSolicitationUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
341 dataLength -= uuidLength;
342 currentPos += uuidLength;
343 }
344 return currentPos;
345 }
346
Wei Wang6d811182014-05-22 12:10:25 -0700347 // Helper method to extract bytes from byte array.
348 private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
349 byte[] bytes = new byte[length];
350 System.arraycopy(scanRecord, start, bytes, 0, length);
351 return bytes;
352 }
353}