Home | History | Annotate | Download | only in le
      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      */
     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