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_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(
    280                 mManufacturerSpecificData)
    281                 + ", mServiceData=" + BluetoothLeUtils.toString(mServiceData)
    282                 + ", mTxPowerLevel=" + mTxPowerLevel + ", mDeviceName=" + mDeviceName + "]";
    283     }
    284 
    285     // Parse service UUIDs.
    286     private static int parseServiceUuid(byte[] scanRecord, int currentPos, int dataLength,
    287             int uuidLength, List<ParcelUuid> serviceUuids) {
    288         while (dataLength > 0) {
    289             byte[] uuidBytes = extractBytes(scanRecord, currentPos,
    290                     uuidLength);
    291             serviceUuids.add(BluetoothUuid.parseUuidFrom(uuidBytes));
    292             dataLength -= uuidLength;
    293             currentPos += uuidLength;
    294         }
    295         return currentPos;
    296     }
    297 
    298     // Helper method to extract bytes from byte array.
    299     private static byte[] extractBytes(byte[] scanRecord, int start, int length) {
    300         byte[] bytes = new byte[length];
    301         System.arraycopy(scanRecord, start, bytes, 0, length);
    302         return bytes;
    303     }
    304 }
    305