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 = 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     */
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     */
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     */
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
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     */
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
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     */
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
139    public String getDeviceName() {
140        return mDeviceName;
141    }
142
143    /**
144     * Returns raw bytes of scan record.
145     */
146    public byte[] getBytes() {
147        return mBytes;
148    }
149
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     */
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
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.
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.
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