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.BluetoothAdapter;
     21 import android.bluetooth.BluetoothDevice;
     22 import android.os.Parcel;
     23 import android.os.ParcelUuid;
     24 import android.os.Parcelable;
     25 
     26 import com.android.internal.util.BitUtils;
     27 
     28 import java.util.Arrays;
     29 import java.util.List;
     30 import java.util.Objects;
     31 import java.util.UUID;
     32 
     33 /**
     34  * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
     35  * restrict scan results to only those that are of interest to them.
     36  * <p>
     37  * Current filtering on the following fields are supported:
     38  * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
     39  * <li>Name of remote Bluetooth LE device.
     40  * <li>Mac address of the remote device.
     41  * <li>Service data which is the data associated with a service.
     42  * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
     43  *
     44  * @see ScanResult
     45  * @see BluetoothLeScanner
     46  */
     47 public final class ScanFilter implements Parcelable {
     48 
     49     @Nullable
     50     private final String mDeviceName;
     51 
     52     @Nullable
     53     private final String mDeviceAddress;
     54 
     55     @Nullable
     56     private final ParcelUuid mServiceUuid;
     57     @Nullable
     58     private final ParcelUuid mServiceUuidMask;
     59 
     60     @Nullable
     61     private final ParcelUuid mServiceDataUuid;
     62     @Nullable
     63     private final byte[] mServiceData;
     64     @Nullable
     65     private final byte[] mServiceDataMask;
     66 
     67     private final int mManufacturerId;
     68     @Nullable
     69     private final byte[] mManufacturerData;
     70     @Nullable
     71     private final byte[] mManufacturerDataMask;
     72 
     73     /** @hide */
     74     public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
     75 
     76 
     77     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
     78             ParcelUuid uuidMask, ParcelUuid serviceDataUuid,
     79             byte[] serviceData, byte[] serviceDataMask,
     80             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
     81         mDeviceName = name;
     82         mServiceUuid = uuid;
     83         mServiceUuidMask = uuidMask;
     84         mDeviceAddress = deviceAddress;
     85         mServiceDataUuid = serviceDataUuid;
     86         mServiceData = serviceData;
     87         mServiceDataMask = serviceDataMask;
     88         mManufacturerId = manufacturerId;
     89         mManufacturerData = manufacturerData;
     90         mManufacturerDataMask = manufacturerDataMask;
     91     }
     92 
     93     @Override
     94     public int describeContents() {
     95         return 0;
     96     }
     97 
     98     @Override
     99     public void writeToParcel(Parcel dest, int flags) {
    100         dest.writeInt(mDeviceName == null ? 0 : 1);
    101         if (mDeviceName != null) {
    102             dest.writeString(mDeviceName);
    103         }
    104         dest.writeInt(mDeviceAddress == null ? 0 : 1);
    105         if (mDeviceAddress != null) {
    106             dest.writeString(mDeviceAddress);
    107         }
    108         dest.writeInt(mServiceUuid == null ? 0 : 1);
    109         if (mServiceUuid != null) {
    110             dest.writeParcelable(mServiceUuid, flags);
    111             dest.writeInt(mServiceUuidMask == null ? 0 : 1);
    112             if (mServiceUuidMask != null) {
    113                 dest.writeParcelable(mServiceUuidMask, flags);
    114             }
    115         }
    116         dest.writeInt(mServiceDataUuid == null ? 0 : 1);
    117         if (mServiceDataUuid != null) {
    118             dest.writeParcelable(mServiceDataUuid, flags);
    119             dest.writeInt(mServiceData == null ? 0 : 1);
    120             if (mServiceData != null) {
    121                 dest.writeInt(mServiceData.length);
    122                 dest.writeByteArray(mServiceData);
    123 
    124                 dest.writeInt(mServiceDataMask == null ? 0 : 1);
    125                 if (mServiceDataMask != null) {
    126                     dest.writeInt(mServiceDataMask.length);
    127                     dest.writeByteArray(mServiceDataMask);
    128                 }
    129             }
    130         }
    131         dest.writeInt(mManufacturerId);
    132         dest.writeInt(mManufacturerData == null ? 0 : 1);
    133         if (mManufacturerData != null) {
    134             dest.writeInt(mManufacturerData.length);
    135             dest.writeByteArray(mManufacturerData);
    136 
    137             dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
    138             if (mManufacturerDataMask != null) {
    139                 dest.writeInt(mManufacturerDataMask.length);
    140                 dest.writeByteArray(mManufacturerDataMask);
    141             }
    142         }
    143     }
    144 
    145     /**
    146      * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
    147      */
    148     public static final Creator<ScanFilter> CREATOR =
    149             new Creator<ScanFilter>() {
    150 
    151         @Override
    152         public ScanFilter[] newArray(int size) {
    153             return new ScanFilter[size];
    154         }
    155 
    156         @Override
    157         public ScanFilter createFromParcel(Parcel in) {
    158             Builder builder = new Builder();
    159             if (in.readInt() == 1) {
    160                 builder.setDeviceName(in.readString());
    161             }
    162             if (in.readInt() == 1) {
    163                 builder.setDeviceAddress(in.readString());
    164             }
    165             if (in.readInt() == 1) {
    166                 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
    167                 builder.setServiceUuid(uuid);
    168                 if (in.readInt() == 1) {
    169                     ParcelUuid uuidMask = in.readParcelable(
    170                             ParcelUuid.class.getClassLoader());
    171                     builder.setServiceUuid(uuid, uuidMask);
    172                 }
    173             }
    174             if (in.readInt() == 1) {
    175                 ParcelUuid servcieDataUuid =
    176                         in.readParcelable(ParcelUuid.class.getClassLoader());
    177                 if (in.readInt() == 1) {
    178                     int serviceDataLength = in.readInt();
    179                     byte[] serviceData = new byte[serviceDataLength];
    180                     in.readByteArray(serviceData);
    181                     if (in.readInt() == 0) {
    182                         builder.setServiceData(servcieDataUuid, serviceData);
    183                     } else {
    184                         int serviceDataMaskLength = in.readInt();
    185                         byte[] serviceDataMask = new byte[serviceDataMaskLength];
    186                         in.readByteArray(serviceDataMask);
    187                         builder.setServiceData(
    188                                 servcieDataUuid, serviceData, serviceDataMask);
    189                     }
    190                 }
    191             }
    192 
    193             int manufacturerId = in.readInt();
    194             if (in.readInt() == 1) {
    195                 int manufacturerDataLength = in.readInt();
    196                 byte[] manufacturerData = new byte[manufacturerDataLength];
    197                 in.readByteArray(manufacturerData);
    198                 if (in.readInt() == 0) {
    199                     builder.setManufacturerData(manufacturerId, manufacturerData);
    200                 } else {
    201                     int manufacturerDataMaskLength = in.readInt();
    202                     byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
    203                     in.readByteArray(manufacturerDataMask);
    204                     builder.setManufacturerData(manufacturerId, manufacturerData,
    205                             manufacturerDataMask);
    206                 }
    207             }
    208 
    209             return builder.build();
    210         }
    211     };
    212 
    213     /**
    214      * Returns the filter set the device name field of Bluetooth advertisement data.
    215      */
    216     @Nullable
    217     public String getDeviceName() {
    218         return mDeviceName;
    219     }
    220 
    221     /**
    222      * Returns the filter set on the service uuid.
    223      */
    224     @Nullable
    225     public ParcelUuid getServiceUuid() {
    226         return mServiceUuid;
    227     }
    228 
    229     @Nullable
    230     public ParcelUuid getServiceUuidMask() {
    231         return mServiceUuidMask;
    232     }
    233 
    234     @Nullable
    235     public String getDeviceAddress() {
    236         return mDeviceAddress;
    237     }
    238 
    239     @Nullable
    240     public byte[] getServiceData() {
    241         return mServiceData;
    242     }
    243 
    244     @Nullable
    245     public byte[] getServiceDataMask() {
    246         return mServiceDataMask;
    247     }
    248 
    249     @Nullable
    250     public ParcelUuid getServiceDataUuid() {
    251         return mServiceDataUuid;
    252     }
    253 
    254     /**
    255      * Returns the manufacturer id. -1 if the manufacturer filter is not set.
    256      */
    257     public int getManufacturerId() {
    258         return mManufacturerId;
    259     }
    260 
    261     @Nullable
    262     public byte[] getManufacturerData() {
    263         return mManufacturerData;
    264     }
    265 
    266     @Nullable
    267     public byte[] getManufacturerDataMask() {
    268         return mManufacturerDataMask;
    269     }
    270 
    271     /**
    272      * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
    273      * if it matches all the field filters.
    274      */
    275     public boolean matches(ScanResult scanResult) {
    276         if (scanResult == null) {
    277             return false;
    278         }
    279         BluetoothDevice device = scanResult.getDevice();
    280         // Device match.
    281         if (mDeviceAddress != null
    282                 && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
    283             return false;
    284         }
    285 
    286         ScanRecord scanRecord = scanResult.getScanRecord();
    287 
    288         // Scan record is null but there exist filters on it.
    289         if (scanRecord == null
    290                 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
    291                 || mServiceData != null)) {
    292             return false;
    293         }
    294 
    295         // Local name match.
    296         if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
    297             return false;
    298         }
    299 
    300         // UUID match.
    301         if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
    302                 scanRecord.getServiceUuids())) {
    303             return false;
    304         }
    305 
    306         // Service data match
    307         if (mServiceDataUuid != null) {
    308             if (!matchesPartialData(mServiceData, mServiceDataMask,
    309                     scanRecord.getServiceData(mServiceDataUuid))) {
    310                 return false;
    311             }
    312         }
    313 
    314         // Manufacturer data match.
    315         if (mManufacturerId >= 0) {
    316             if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
    317                     scanRecord.getManufacturerSpecificData(mManufacturerId))) {
    318                 return false;
    319             }
    320         }
    321         // All filters match.
    322         return true;
    323     }
    324 
    325     /**
    326      * Check if the uuid pattern is contained in a list of parcel uuids.
    327      *
    328      * @hide
    329      */
    330     public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
    331             List<ParcelUuid> uuids) {
    332         if (uuid == null) {
    333             return true;
    334         }
    335         if (uuids == null) {
    336             return false;
    337         }
    338 
    339         for (ParcelUuid parcelUuid : uuids) {
    340             UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
    341             if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
    342                 return true;
    343             }
    344         }
    345         return false;
    346     }
    347 
    348     // Check if the uuid pattern matches the particular service uuid.
    349     private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
    350         return BitUtils.maskedEquals(data, uuid, mask);
    351     }
    352 
    353     // Check whether the data pattern matches the parsed data.
    354     private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
    355         if (parsedData == null || parsedData.length < data.length) {
    356             return false;
    357         }
    358         if (dataMask == null) {
    359             for (int i = 0; i < data.length; ++i) {
    360                 if (parsedData[i] != data[i]) {
    361                     return false;
    362                 }
    363             }
    364             return true;
    365         }
    366         for (int i = 0; i < data.length; ++i) {
    367             if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
    368                 return false;
    369             }
    370         }
    371         return true;
    372     }
    373 
    374     @Override
    375     public String toString() {
    376         return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
    377                 + mDeviceAddress
    378                 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
    379                 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
    380                 + Arrays.toString(mServiceData) + ", mServiceDataMask="
    381                 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
    382                 + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
    383                 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
    384     }
    385 
    386     @Override
    387     public int hashCode() {
    388         return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId,
    389                 Arrays.hashCode(mManufacturerData),
    390                 Arrays.hashCode(mManufacturerDataMask),
    391                 mServiceDataUuid,
    392                 Arrays.hashCode(mServiceData),
    393                 Arrays.hashCode(mServiceDataMask),
    394                 mServiceUuid, mServiceUuidMask);
    395     }
    396 
    397     @Override
    398     public boolean equals(Object obj) {
    399         if (this == obj) {
    400             return true;
    401         }
    402         if (obj == null || getClass() != obj.getClass()) {
    403             return false;
    404         }
    405         ScanFilter other = (ScanFilter) obj;
    406         return Objects.equals(mDeviceName, other.mDeviceName)
    407                 && Objects.equals(mDeviceAddress, other.mDeviceAddress)
    408                 && mManufacturerId == other.mManufacturerId
    409                 && Objects.deepEquals(mManufacturerData, other.mManufacturerData)
    410                 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask)
    411                 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid)
    412                 && Objects.deepEquals(mServiceData, other.mServiceData)
    413                 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
    414                 && Objects.equals(mServiceUuid, other.mServiceUuid)
    415                 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
    416     }
    417 
    418     /**
    419      * Checks if the scanfilter is empty
    420      *
    421      * @hide
    422      */
    423     public boolean isAllFieldsEmpty() {
    424         return EMPTY.equals(this);
    425     }
    426 
    427     /**
    428      * Builder class for {@link ScanFilter}.
    429      */
    430     public static final class Builder {
    431 
    432         private String mDeviceName;
    433         private String mDeviceAddress;
    434 
    435         private ParcelUuid mServiceUuid;
    436         private ParcelUuid mUuidMask;
    437 
    438         private ParcelUuid mServiceDataUuid;
    439         private byte[] mServiceData;
    440         private byte[] mServiceDataMask;
    441 
    442         private int mManufacturerId = -1;
    443         private byte[] mManufacturerData;
    444         private byte[] mManufacturerDataMask;
    445 
    446         /**
    447          * Set filter on device name.
    448          */
    449         public Builder setDeviceName(String deviceName) {
    450             mDeviceName = deviceName;
    451             return this;
    452         }
    453 
    454         /**
    455          * Set filter on device address.
    456          *
    457          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
    458          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
    459          * BluetoothAdapter#checkBluetoothAddress}.
    460          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
    461          */
    462         public Builder setDeviceAddress(String deviceAddress) {
    463             if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
    464                 throw new IllegalArgumentException("invalid device address " + deviceAddress);
    465             }
    466             mDeviceAddress = deviceAddress;
    467             return this;
    468         }
    469 
    470         /**
    471          * Set filter on service uuid.
    472          */
    473         public Builder setServiceUuid(ParcelUuid serviceUuid) {
    474             mServiceUuid = serviceUuid;
    475             mUuidMask = null; // clear uuid mask
    476             return this;
    477         }
    478 
    479         /**
    480          * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
    481          * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
    482          * bit in {@code serviceUuid}, and 0 to ignore that bit.
    483          *
    484          * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
    485          * uuidMask} is not {@code null}.
    486          */
    487         public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
    488             if (mUuidMask != null && mServiceUuid == null) {
    489                 throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
    490             }
    491             mServiceUuid = serviceUuid;
    492             mUuidMask = uuidMask;
    493             return this;
    494         }
    495 
    496         /**
    497          * Set filtering on service data.
    498          *
    499          * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
    500          */
    501         public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
    502             if (serviceDataUuid == null) {
    503                 throw new IllegalArgumentException("serviceDataUuid is null");
    504             }
    505             mServiceDataUuid = serviceDataUuid;
    506             mServiceData = serviceData;
    507             mServiceDataMask = null; // clear service data mask
    508             return this;
    509         }
    510 
    511         /**
    512          * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
    513          * match the one in service data, otherwise set it to 0 to ignore that bit.
    514          * <p>
    515          * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
    516          *
    517          * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code
    518          * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code
    519          * serviceDataMask} and {@code serviceData} has different length.
    520          */
    521         public Builder setServiceData(ParcelUuid serviceDataUuid,
    522                 byte[] serviceData, byte[] serviceDataMask) {
    523             if (serviceDataUuid == null) {
    524                 throw new IllegalArgumentException("serviceDataUuid is null");
    525             }
    526             if (mServiceDataMask != null) {
    527                 if (mServiceData == null) {
    528                     throw new IllegalArgumentException(
    529                             "serviceData is null while serviceDataMask is not null");
    530                 }
    531                 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
    532                 // byte array need to be the same.
    533                 if (mServiceData.length != mServiceDataMask.length) {
    534                     throw new IllegalArgumentException(
    535                             "size mismatch for service data and service data mask");
    536                 }
    537             }
    538             mServiceDataUuid = serviceDataUuid;
    539             mServiceData = serviceData;
    540             mServiceDataMask = serviceDataMask;
    541             return this;
    542         }
    543 
    544         /**
    545          * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
    546          * <p>
    547          * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
    548          *
    549          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
    550          */
    551         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
    552             if (manufacturerData != null && manufacturerId < 0) {
    553                 throw new IllegalArgumentException("invalid manufacture id");
    554             }
    555             mManufacturerId = manufacturerId;
    556             mManufacturerData = manufacturerData;
    557             mManufacturerDataMask = null; // clear manufacturer data mask
    558             return this;
    559         }
    560 
    561         /**
    562          * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
    563          * to match the one in manufacturer data, otherwise set it to 0.
    564          * <p>
    565          * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
    566          *
    567          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
    568          * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code
    569          * manufacturerData} and {@code manufacturerDataMask} have different length.
    570          */
    571         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
    572                 byte[] manufacturerDataMask) {
    573             if (manufacturerData != null && manufacturerId < 0) {
    574                 throw new IllegalArgumentException("invalid manufacture id");
    575             }
    576             if (mManufacturerDataMask != null) {
    577                 if (mManufacturerData == null) {
    578                     throw new IllegalArgumentException(
    579                             "manufacturerData is null while manufacturerDataMask is not null");
    580                 }
    581                 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
    582                 // of the two byte array need to be the same.
    583                 if (mManufacturerData.length != mManufacturerDataMask.length) {
    584                     throw new IllegalArgumentException(
    585                             "size mismatch for manufacturerData and manufacturerDataMask");
    586                 }
    587             }
    588             mManufacturerId = manufacturerId;
    589             mManufacturerData = manufacturerData;
    590             mManufacturerDataMask = manufacturerDataMask;
    591             return this;
    592         }
    593 
    594         /**
    595          * Build {@link ScanFilter}.
    596          *
    597          * @throws IllegalArgumentException If the filter cannot be built.
    598          */
    599         public ScanFilter build() {
    600             return new ScanFilter(mDeviceName, mDeviceAddress,
    601                     mServiceUuid, mUuidMask,
    602                     mServiceDataUuid, mServiceData, mServiceDataMask,
    603                     mManufacturerId, mManufacturerData, mManufacturerDataMask);
    604         }
    605     }
    606 }
    607