ScanRecord.java revision ab2ed62f15d0dac0f8ef825ff2d3677c9ae18f44
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.Log;
23
24import java.util.ArrayList;
25import java.util.Arrays;
26import java.util.List;
27
28/**
29 * Represents a scan record from Bluetooth LE scan.
30 */
31public final class ScanRecord {
32
33    private static final String TAG = "ScanRecord";
34
35    // The following data type values are assigned by Bluetooth SIG.
36    // For more details refer to Bluetooth 4.1 specification, Volume 3, Part C, Section 18.
37    private static final int DATA_TYPE_FLAGS = 0x01;
38    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL = 0x02;
39    private static final int DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE = 0x03;
40    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL = 0x04;
41    private static final int DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE = 0x05;
42    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL = 0x06;
43    private static final int DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE = 0x07;
44    private static final int DATA_TYPE_LOCAL_NAME_SHORT = 0x08;
45    private static final int DATA_TYPE_LOCAL_NAME_COMPLETE = 0x09;
46    private static final int DATA_TYPE_TX_POWER_LEVEL = 0x0A;
47    private static final int DATA_TYPE_SERVICE_DATA = 0x16;
48    private static final int DATA_TYPE_MANUFACTURER_SPECIFIC_DATA = 0xFF;
49
50    // Flags of the advertising data.
51    private final int mAdvertiseFlags;
52
53    @Nullable
54    private final List<ParcelUuid> mServiceUuids;
55
56    private final int mManufacturerId;
57    @Nullable
58    private final byte[] mManufacturerSpecificData;
59
60    @Nullable
61    private final ParcelUuid mServiceDataUuid;
62    @Nullable
63    private final 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 the manufacturer identifier, which is a non-negative number assigned by Bluetooth
92     * SIG.
93     */
94    public int getManufacturerId() {
95        return mManufacturerId;
96    }
97
98    /**
99     * Returns the manufacturer specific data which is the content of manufacturer specific data
100     * field.
101     */
102    public byte[] getManufacturerSpecificData() {
103        return mManufacturerSpecificData;
104    }
105
106    /**
107     * Returns a 16-bit UUID of the service that the service data is associated with.
108     */
109    public ParcelUuid getServiceDataUuid() {
110        return mServiceDataUuid;
111    }
112
113    /**
114     * Returns service data.
115     */
116    public byte[] getServiceData() {
117        return mServiceData;
118    }
119
120    /**
121     * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
122     * if the field is not set. This value can be used to calculate the path loss of a received
123     * packet using the following equation:
124     * <p>
125     * <code>pathloss = txPowerLevel - rssi</code>
126     */
127    public int getTxPowerLevel() {
128        return mTxPowerLevel;
129    }
130
131    /**
132     * Returns the local name of the BLE device. The is a UTF-8 encoded string.
133     */
134    @Nullable
135    public String getDeviceName() {
136        return mDeviceName;
137    }
138
139    /**
140     * Returns raw bytes of scan record.
141     */
142    public byte[] getBytes() {
143        return mBytes;
144    }
145
146    private ScanRecord(List<ParcelUuid> serviceUuids,
147            ParcelUuid serviceDataUuid, byte[] serviceData,
148            int manufacturerId,
149            byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
150            String localName, byte[] bytes) {
151        mServiceUuids = serviceUuids;
152        mManufacturerId = manufacturerId;
153        mManufacturerSpecificData = manufacturerSpecificData;
154        mServiceDataUuid = serviceDataUuid;
155        mServiceData = serviceData;
156        mDeviceName = localName;
157        mAdvertiseFlags = advertiseFlags;
158        mTxPowerLevel = txPowerLevel;
159        mBytes = bytes;
160    }
161
162    /**
163     * Parse scan record bytes to {@link ScanRecord}.
164     * <p>
165     * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
166     * <p>
167     * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
168     * order.
169     *
170     * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
171     *
172     * @hide
173     */
174    public static ScanRecord parseFromBytes(byte[] scanRecord) {
175        if (scanRecord == null) {
176            return null;
177        }
178
179        int currentPos = 0;
180        int advertiseFlag = -1;
181        List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
182        String localName = null;
183        int txPowerLevel = Integer.MIN_VALUE;
184        ParcelUuid serviceDataUuid = null;
185        byte[] serviceData = null;
186        int manufacturerId = -1;
187        byte[] manufacturerSpecificData = null;
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                        serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
234                        serviceData = extractBytes(scanRecord, currentPos + 2, dataLength - 2);
235                        break;
236                    case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
237                        // The first two bytes of the manufacturer specific data are
238                        // manufacturer ids in little endian.
239                        manufacturerId = ((scanRecord[currentPos + 1] & 0xFF) << 8) +
240                                (scanRecord[currentPos] & 0xFF);
241                        manufacturerSpecificData = extractBytes(scanRecord, currentPos + 2,
242                                dataLength - 2);
243                        break;
244                    default:
245                        // Just ignore, we don't handle such data type.
246                        break;
247                }
248                currentPos += dataLength;
249            }
250
251            if (serviceUuids.isEmpty()) {
252                serviceUuids = null;
253            }
254            return new ScanRecord(serviceUuids, serviceDataUuid, serviceData,
255                    manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
256                    localName, scanRecord);
257        } catch (IndexOutOfBoundsException e) {
258            Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
259            return null;
260        }
261    }
262
263    @Override
264    public String toString() {
265        return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
266                + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
267                + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
268                + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
269                + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
270    }
271
272
273    // Parse service UUIDs.
274    private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
275            int uuidLength, List<ParcelUuid> serviceUuids) {
276        while (dataLength > 0) {
277            byte[] uuidBytes = extractBytes(scanRecord, currentPos,
278                    uuidLength);
279            serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
280            dataLength -= uuidLength;
281            currentPos += uuidLength;
282        }
283        return currentPos;
284    }
285
286    // Helper method to extract bytes from byte array.
287    private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
288        byte[] bytes = new byte[length];
289        System.arraycopy(scanRecord, start, bytes, 0, length);
290        return bytes;
291    }
292}
293