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.ArrayMap;
23import android.util.Log;
24import android.util.SparseArray;
25
26import java.util.ArrayList;
27import java.util.Arrays;
28import java.util.List;
29import java.util.Map;
30
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;
50    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;
53    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
61    private final SparseArray<byte[]> mManufacturerSpecificData;
62
63    private final Map<ParcelUuid, 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 a sparse array of manufacturer identifier and its corresponding manufacturer specific
92     * data.
93     */
94    public SparseArray<byte[]> getManufacturerSpecificData() {
95        return mManufacturerSpecificData;
96    }
97
98    /**
99     * Returns the manufacturer specific data associated with the manufacturer id. Returns
100     * {@code null} if the {@code manufacturerId} is not found.
101     */
102    @Nullable
103    public byte[] getManufacturerSpecificData(int manufacturerId) {
104        return mManufacturerSpecificData.get(manufacturerId);
105    }
106
107    /**
108     * Returns a map of service UUID and its corresponding service data.
109     */
110    public Map<ParcelUuid, byte[]> getServiceData() {
111        return mServiceData;
112    }
113
114    /**
115     * 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    /**
127     * 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
141    public String getDeviceName() {
142        return mDeviceName;
143    }
144
145    /**
146     * Returns raw bytes of scan record.
147     */
148    public byte[] getBytes() {
149        return mBytes;
150    }
151
152    private ScanRecord(List<ParcelUuid> serviceUuids,
153            SparseArray<byte[]> manufacturerData,
154            Map<ParcelUuid, byte[]> serviceData,
155            int advertiseFlags, int txPowerLevel,
156            String localName, byte[] bytes) {
157        mServiceUuids = serviceUuids;
158        mManufacturerSpecificData = manufacturerData;
159        mServiceData = serviceData;
160        mDeviceName = localName;
161        mAdvertiseFlags = advertiseFlags;
162        mTxPowerLevel = txPowerLevel;
163        mBytes = bytes;
164    }
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.
175     * @hide
176     */
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;
187
188        SparseArray<byte[]> manufacturerData = new SparseArray<byte[]>();
189        Map<ParcelUuid, byte[]> serviceData = new ArrayMap<ParcelUuid, byte[]>();
190
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;
229                    case DATA_TYPE_SERVICE_DATA_16_BIT:
230                    case DATA_TYPE_SERVICE_DATA_32_BIT:
231                    case DATA_TYPE_SERVICE_DATA_128_BIT:
232                        int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
233                        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
239                        byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
240                                serviceUuidLength);
241                        ParcelUuid serviceDataUuid = BluetoothUuid.parseUuidFrom(
242                                serviceDataUuidBytes);
243                        byte[] serviceDataArray = extractBytes(scanRecord,
244                                currentPos + serviceUuidLength, dataLength - serviceUuidLength);
245                        serviceData.put(serviceDataUuid, serviceDataArray);
246                        break;
247                    case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
248                        // The first two bytes of the manufacturer specific data are
249                        // manufacturer ids in little endian.
250                        int manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
251                                (scanRecord[currentPos] & 0xFF);
252                        byte[] manufacturerDataBytes = extractBytes(scanRecord, currentPos + 2,
253                                dataLength - 2);
254                        manufacturerData.put(manufacturerId, manufacturerDataBytes);
255                        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            }
266            return new ScanRecord(serviceUuids, manufacturerData, serviceData,
267                    advertiseFlag, txPowerLevel, localName, scanRecord);
268        } catch (Exception e) {
269            Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
270            // 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);
273        }
274    }
275
276    @Override
277    public String toString() {
278        return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
279                + ", mManufacturerSpecificData=" + BluetoothLeUtils.toString(mManufacturerSpecificData)
280                + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
281                + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
282    }
283
284    // Parse service UUIDs.
285    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}
304