ScanRecord.java revision af74e66e29a518157cb78fcef4b4fc532b7f60b0
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    /**
72     * Returns the advertising flags indicating the discoverable mode and capability of the device.
73     * Returns -1 if the flag field is not set.
74     */
75    public int getAdvertiseFlags() {
76        return mAdvertiseFlags;
77    }
78
79    /**
80     * Returns a list of service UUIDs within the advertisement that are used to identify the
81     * bluetooth gatt services.
82     */
83    public List<ParcelUuid> getServiceUuids() {
84        return mServiceUuids;
85    }
86
87    /**
88     * Returns the manufacturer identifier, which is a non-negative number assigned by Bluetooth
89     * SIG.
90     */
91    public int getManufacturerId() {
92        return mManufacturerId;
93    }
94
95    /**
96     * Returns the manufacturer specific data which is the content of manufacturer specific data
97     * field.
98     */
99    public byte[] getManufacturerSpecificData() {
100        return mManufacturerSpecificData;
101    }
102
103    /**
104     * Returns a 16-bit UUID of the service that the service data is associated with.
105     */
106    public ParcelUuid getServiceDataUuid() {
107        return mServiceDataUuid;
108    }
109
110    /**
111     * Returns service data.
112     */
113    public byte[] getServiceData() {
114        return mServiceData;
115    }
116
117    /**
118     * Returns the transmission power level of the packet in dBm. Returns {@link Integer#MIN_VALUE}
119     * if the field is not set. This value can be used to calculate the path loss of a received
120     * packet using the following equation:
121     * <p>
122     * <code>pathloss = txPowerLevel - rssi</code>
123     */
124    public int getTxPowerLevel() {
125        return mTxPowerLevel;
126    }
127
128    /**
129     * Returns the local name of the BLE device. The is a UTF-8 encoded string.
130     */
131    @Nullable
132    public String getDeviceName() {
133        return mDeviceName;
134    }
135
136    private ScanRecord(List<ParcelUuid> serviceUuids,
137            ParcelUuid serviceDataUuid, byte[] serviceData,
138            int manufacturerId,
139            byte[] manufacturerSpecificData, int advertiseFlags, int txPowerLevel,
140            String localName) {
141        mServiceUuids = serviceUuids;
142        mManufacturerId = manufacturerId;
143        mManufacturerSpecificData = manufacturerSpecificData;
144        mServiceDataUuid = serviceDataUuid;
145        mServiceData = serviceData;
146        mDeviceName = localName;
147        mAdvertiseFlags = advertiseFlags;
148        mTxPowerLevel = txPowerLevel;
149    }
150
151    @Override
152    public String toString() {
153        return "ScanRecord [mAdvertiseFlags=" + mAdvertiseFlags + ", mServiceUuids=" + mServiceUuids
154                + ", mManufacturerId=" + mManufacturerId + ", mManufacturerSpecificData="
155                + Arrays.toString(mManufacturerSpecificData) + ", mServiceDataUuid="
156                + mServiceDataUuid + ", mServiceData=" + Arrays.toString(mServiceData)
157                + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
158    }
159
160    /**
161     * Parse scan record bytes to {@link ScanRecord}.
162     * <p>
163     * The format is defined in Bluetooth 4.1 specification, Volume 3, Part C, Section 11 and 18.
164     * <p>
165     * All numerical multi-byte entities and values shall use little-endian <strong>byte</strong>
166     * order.
167     *
168     * @param scanRecord The scan record of Bluetooth LE advertisement and/or scan response.
169     */
170    public static ScanRecord parseFromBytes(byte[] scanRecord) {
171        if (scanRecord == null) {
172            return null;
173        }
174
175        int currentPos = 0;
176        int advertiseFlag = -1;
177        List<ParcelUuid> serviceUuids = new ArrayList<ParcelUuid>();
178        String localName = null;
179        int txPowerLevel = Integer.MIN_VALUE;
180        ParcelUuid serviceDataUuid = null;
181        byte[] serviceData = null;
182        int manufacturerId = -1;
183        byte[] manufacturerSpecificData = null;
184
185        try {
186            while (currentPos < scanRecord.length) {
187                // length is unsigned int.
188                int length = scanRecord[currentPos++] & 0xFF;
189                if (length == 0) {
190                    break;
191                }
192                // Note the length includes the length of the field type itself.
193                int dataLength = length - 1;
194                // fieldType is unsigned int.
195                int fieldType = scanRecord[currentPos++] & 0xFF;
196                switch (fieldType) {
197                    case DATA_TYPE_FLAGS:
198                        advertiseFlag = scanRecord[currentPos] & 0xFF;
199                        break;
200                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_PARTIAL:
201                    case DATA_TYPE_SERVICE_UUIDS_16_BIT_COMPLETE:
202                        parseServiceUuid(scanRecord, currentPos,
203                                dataLength, BluetoothUuid.UUID_BYTES_16_BIT, serviceUuids);
204                        break;
205                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_PARTIAL:
206                    case DATA_TYPE_SERVICE_UUIDS_32_BIT_COMPLETE:
207                        parseServiceUuid(scanRecord, currentPos, dataLength,
208                                BluetoothUuid.UUID_BYTES_32_BIT, serviceUuids);
209                        break;
210                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_PARTIAL:
211                    case DATA_TYPE_SERVICE_UUIDS_128_BIT_COMPLETE:
212                        parseServiceUuid(scanRecord, currentPos, dataLength,
213                                BluetoothUuid.UUID_BYTES_128_BIT, serviceUuids);
214                        break;
215                    case DATA_TYPE_LOCAL_NAME_SHORT:
216                    case DATA_TYPE_LOCAL_NAME_COMPLETE:
217                        localName = new String(
218                                extractBytes(scanRecord, currentPos, dataLength));
219                        break;
220                    case DATA_TYPE_TX_POWER_LEVEL:
221                        txPowerLevel = scanRecord[currentPos];
222                        break;
223                    case DATA_TYPE_SERVICE_DATA:
224                        serviceData = extractBytes(scanRecord, currentPos, dataLength);
225                        // The first two bytes of the service data are service data UUID.
226                        int serviceUuidLength = BluetoothUuid.UUID_BYTES_16_BIT;
227                        byte[] serviceDataUuidBytes = extractBytes(scanRecord, currentPos,
228                                serviceUuidLength);
229                        serviceDataUuid = BluetoothUuid.parseUuidFrom(serviceDataUuidBytes);
230                        break;
231                    case DATA_TYPE_MANUFACTURER_SPECIFIC_DATA:
232                        manufacturerSpecificData = extractBytes(scanRecord, currentPos,
233                                dataLength);
234                        // The first two bytes of the manufacturer specific data are
235                        // manufacturer ids in little endian.
236                        manufacturerId = ((manufacturerSpecificData[1] & 0xFF) << 8) +
237                                (manufacturerSpecificData[0] & 0xFF);
238                        break;
239                    default:
240                        // Just ignore, we don't handle such data type.
241                        break;
242                }
243                currentPos += dataLength;
244            }
245
246            if (serviceUuids.isEmpty()) {
247                serviceUuids = null;
248            }
249            return new ScanRecord(serviceUuids, serviceDataUuid, serviceData,
250                    manufacturerId, manufacturerSpecificData, advertiseFlag, txPowerLevel,
251                    localName);
252        } catch (IndexOutOfBoundsException e) {
253            Log.e(TAG, "unable to parse scan record: " + Arrays.toString(scanRecord));
254            return null;
255        }
256    }
257
258    // Parse service UUIDs.
259    private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
260            int uuidLength, List<ParcelUuid> serviceUuids) {
261        while (dataLength > 0) {
262            byte[] uuidBytes = extractBytes(scanRecord, currentPos,
263                    uuidLength);
264            serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
265            dataLength -= uuidLength;
266            currentPos += uuidLength;
267        }
268        return currentPos;
269    }
270
271    // Helper method to extract bytes from byte array.
272    private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
273        byte[] bytes = new byte[length];
274        System.arraycopy(scanRecord, start, bytes, 0, length);
275        return bytes;
276    }
277}
278