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