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