• 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 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 
ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask, ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)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
describeContents()94     public int describeContents() {
95         return 0;
96     }
97 
98     @Override
writeToParcel(Parcel dest, int flags)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
getDeviceName()217     public String getDeviceName() {
218         return mDeviceName;
219     }
220 
221     /**
222      * Returns the filter set on the service uuid.
223      */
224     @Nullable
getServiceUuid()225     public ParcelUuid getServiceUuid() {
226         return mServiceUuid;
227     }
228 
229     @Nullable
getServiceUuidMask()230     public ParcelUuid getServiceUuidMask() {
231         return mServiceUuidMask;
232     }
233 
234     @Nullable
getDeviceAddress()235     public String getDeviceAddress() {
236         return mDeviceAddress;
237     }
238 
239     @Nullable
getServiceData()240     public byte[] getServiceData() {
241         return mServiceData;
242     }
243 
244     @Nullable
getServiceDataMask()245     public byte[] getServiceDataMask() {
246         return mServiceDataMask;
247     }
248 
249     @Nullable
getServiceDataUuid()250     public ParcelUuid getServiceDataUuid() {
251         return mServiceDataUuid;
252     }
253 
254     /**
255      * Returns the manufacturer id. -1 if the manufacturer filter is not set.
256      */
getManufacturerId()257     public int getManufacturerId() {
258         return mManufacturerId;
259     }
260 
261     @Nullable
getManufacturerData()262     public byte[] getManufacturerData() {
263         return mManufacturerData;
264     }
265 
266     @Nullable
getManufacturerDataMask()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      */
matches(ScanResult scanResult)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      */
matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)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.
matchesServiceUuid(UUID uuid, UUID mask, UUID data)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.
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      *
421      * @hide
422      */
isAllFieldsEmpty()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          */
setDeviceName(String deviceName)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          */
setDeviceAddress(String deviceAddress)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          */
setServiceUuid(ParcelUuid serviceUuid)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          */
setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)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          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)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          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)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          */
setManufacturerData(int manufacturerId, byte[] manufacturerData)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          */
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