• 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 static java.util.Objects.requireNonNull;
20 
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.annotation.SystemApi;
24 import android.bluetooth.BluetoothAdapter;
25 import android.bluetooth.BluetoothDevice;
26 import android.bluetooth.BluetoothDevice.AddressType;
27 import android.bluetooth.BluetoothStatusCodes;
28 import android.bluetooth.le.ScanRecord.AdvertisingDataType;
29 import android.os.Parcel;
30 import android.os.ParcelUuid;
31 import android.os.Parcelable;
32 
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.UUID;
37 
38 /**
39  * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
40  * restrict scan results to only those that are of interest to them.
41  * <p>
42  * Current filtering on the following fields are supported:
43  * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
44  * <li>Name of remote Bluetooth LE device.
45  * <li>Mac address of the remote device.
46  * <li>Service data which is the data associated with a service.
47  * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
48  * <li>Advertising data type and corresponding data.
49  *
50  * @see ScanResult
51  * @see BluetoothLeScanner
52  */
53 public final class ScanFilter implements Parcelable {
54 
55     @Nullable
56     private final String mDeviceName;
57 
58     @Nullable
59     private final String mDeviceAddress;
60 
61     private final @AddressType int mAddressType;
62 
63     @Nullable
64     private final byte[] mIrk;
65 
66     @Nullable
67     private final ParcelUuid mServiceUuid;
68     @Nullable
69     private final ParcelUuid mServiceUuidMask;
70 
71     @Nullable
72     private final ParcelUuid mServiceSolicitationUuid;
73     @Nullable
74     private final ParcelUuid mServiceSolicitationUuidMask;
75 
76     @Nullable
77     private final ParcelUuid mServiceDataUuid;
78     @Nullable
79     private final byte[] mServiceData;
80     @Nullable
81     private final byte[] mServiceDataMask;
82 
83     private final int mManufacturerId;
84     @Nullable
85     private final byte[] mManufacturerData;
86     @Nullable
87     private final byte[] mManufacturerDataMask;
88 
89     private int mAdvertisingDataType = ScanRecord.DATA_TYPE_NONE;
90     @Nullable
91     private final byte[] mAdvertisingData;
92     @Nullable
93     private final byte[] mAdvertisingDataMask;
94 
95     @Nullable
96     private final TransportBlockFilter mTransportBlockFilter;
97 
98     /** @hide */
99     public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
100 
ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask, ParcelUuid solicitationUuid, ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, @AddressType int addressType, @Nullable byte[] irk, int advertisingDataType, @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask, @Nullable TransportBlockFilter transportBlockFilter)101     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask,
102             ParcelUuid solicitationUuid, ParcelUuid solicitationUuidMask,
103             ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask,
104             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
105             @AddressType int addressType, @Nullable byte[] irk, int advertisingDataType,
106             @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask,
107             @Nullable TransportBlockFilter transportBlockFilter) {
108         mDeviceName = name;
109         mServiceUuid = uuid;
110         mServiceUuidMask = uuidMask;
111         mServiceSolicitationUuid = solicitationUuid;
112         mServiceSolicitationUuidMask = solicitationUuidMask;
113         mDeviceAddress = deviceAddress;
114         mServiceDataUuid = serviceDataUuid;
115         mServiceData = serviceData;
116         mServiceDataMask = serviceDataMask;
117         mManufacturerId = manufacturerId;
118         mManufacturerData = manufacturerData;
119         mManufacturerDataMask = manufacturerDataMask;
120         mAddressType = addressType;
121         mIrk = irk;
122         mAdvertisingDataType = advertisingDataType;
123         mAdvertisingData = advertisingData;
124         mAdvertisingDataMask = advertisingDataMask;
125         mTransportBlockFilter = transportBlockFilter;
126     }
127 
128     @Override
describeContents()129     public int describeContents() {
130         return 0;
131     }
132 
133     @Override
writeToParcel(Parcel dest, int flags)134     public void writeToParcel(Parcel dest, int flags) {
135         dest.writeInt(mDeviceName == null ? 0 : 1);
136         if (mDeviceName != null) {
137             dest.writeString(mDeviceName);
138         }
139         dest.writeInt(mDeviceAddress == null ? 0 : 1);
140         if (mDeviceAddress != null) {
141             dest.writeString(mDeviceAddress);
142         }
143         dest.writeInt(mServiceUuid == null ? 0 : 1);
144         if (mServiceUuid != null) {
145             dest.writeParcelable(mServiceUuid, flags);
146             dest.writeInt(mServiceUuidMask == null ? 0 : 1);
147             if (mServiceUuidMask != null) {
148                 dest.writeParcelable(mServiceUuidMask, flags);
149             }
150         }
151         dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
152         if (mServiceSolicitationUuid != null) {
153             dest.writeParcelable(mServiceSolicitationUuid, flags);
154             dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
155             if (mServiceSolicitationUuidMask != null) {
156                 dest.writeParcelable(mServiceSolicitationUuidMask, flags);
157             }
158         }
159         dest.writeInt(mServiceDataUuid == null ? 0 : 1);
160         if (mServiceDataUuid != null) {
161             dest.writeParcelable(mServiceDataUuid, flags);
162             dest.writeInt(mServiceData == null ? 0 : 1);
163             if (mServiceData != null) {
164                 dest.writeInt(mServiceData.length);
165                 dest.writeByteArray(mServiceData);
166 
167                 dest.writeInt(mServiceDataMask == null ? 0 : 1);
168                 if (mServiceDataMask != null) {
169                     dest.writeInt(mServiceDataMask.length);
170                     dest.writeByteArray(mServiceDataMask);
171                 }
172             }
173         }
174         dest.writeInt(mManufacturerId);
175         dest.writeInt(mManufacturerData == null ? 0 : 1);
176         if (mManufacturerData != null) {
177             dest.writeInt(mManufacturerData.length);
178             dest.writeByteArray(mManufacturerData);
179 
180             dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
181             if (mManufacturerDataMask != null) {
182                 dest.writeInt(mManufacturerDataMask.length);
183                 dest.writeByteArray(mManufacturerDataMask);
184             }
185         }
186 
187         // IRK
188         if (mDeviceAddress != null) {
189             dest.writeInt(mAddressType);
190             dest.writeInt(mIrk == null ? 0 : 1);
191             if (mIrk != null) {
192                 dest.writeByteArray(Arrays.copyOfRange(mIrk, 0, 16));
193             }
194         }
195 
196         // Advertising data type filter
197         dest.writeInt(mAdvertisingDataType);
198         dest.writeInt(mAdvertisingData == null ? 0 : 1);
199         if (mAdvertisingData != null) {
200             dest.writeInt(mAdvertisingData.length);
201             dest.writeByteArray(mAdvertisingData);
202 
203             dest.writeInt(mAdvertisingDataMask == null ? 0 : 1);
204             if (mAdvertisingDataMask != null) {
205                 dest.writeInt(mAdvertisingDataMask.length);
206                 dest.writeByteArray(mAdvertisingDataMask);
207             }
208         }
209 
210         dest.writeInt(mTransportBlockFilter == null ? 0 : 1);
211         if (mTransportBlockFilter != null) {
212             dest.writeTypedObject(mTransportBlockFilter, 0);
213         }
214     }
215 
216     /**
217      * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
218      */
219     public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR =
220             new Creator<ScanFilter>() {
221 
222         @Override
223         public ScanFilter[] newArray(int size) {
224             return new ScanFilter[size];
225         }
226 
227         @Override
228         public ScanFilter createFromParcel(Parcel in) {
229             Builder builder = new Builder();
230             if (in.readInt() == 1) {
231                 builder.setDeviceName(in.readString());
232             }
233             String address = null;
234             // If we have a non-null address
235             if (in.readInt() == 1) {
236                 address = in.readString();
237             }
238             if (in.readInt() == 1) {
239                 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
240                 builder.setServiceUuid(uuid);
241                 if (in.readInt() == 1) {
242                     ParcelUuid uuidMask = in.readParcelable(
243                             ParcelUuid.class.getClassLoader());
244                     builder.setServiceUuid(uuid, uuidMask);
245                 }
246             }
247             if (in.readInt() == 1) {
248                 ParcelUuid solicitationUuid = in.readParcelable(
249                         ParcelUuid.class.getClassLoader());
250                 builder.setServiceSolicitationUuid(solicitationUuid);
251                 if (in.readInt() == 1) {
252                     ParcelUuid solicitationUuidMask = in.readParcelable(
253                             ParcelUuid.class.getClassLoader());
254                     builder.setServiceSolicitationUuid(solicitationUuid,
255                             solicitationUuidMask);
256                 }
257             }
258             if (in.readInt() == 1) {
259                 ParcelUuid servcieDataUuid =
260                         in.readParcelable(ParcelUuid.class.getClassLoader());
261                 if (in.readInt() == 1) {
262                     int serviceDataLength = in.readInt();
263                     byte[] serviceData = new byte[serviceDataLength];
264                     in.readByteArray(serviceData);
265                     if (in.readInt() == 0) {
266                         builder.setServiceData(servcieDataUuid, serviceData);
267                     } else {
268                         int serviceDataMaskLength = in.readInt();
269                         byte[] serviceDataMask = new byte[serviceDataMaskLength];
270                         in.readByteArray(serviceDataMask);
271                         builder.setServiceData(
272                                 servcieDataUuid, serviceData, serviceDataMask);
273                     }
274                 }
275             }
276 
277             int manufacturerId = in.readInt();
278             if (in.readInt() == 1) {
279                 int manufacturerDataLength = in.readInt();
280                 byte[] manufacturerData = new byte[manufacturerDataLength];
281                 in.readByteArray(manufacturerData);
282                 if (in.readInt() == 0) {
283                     builder.setManufacturerData(manufacturerId, manufacturerData);
284                 } else {
285                     int manufacturerDataMaskLength = in.readInt();
286                     byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
287                     in.readByteArray(manufacturerDataMask);
288                     builder.setManufacturerData(manufacturerId, manufacturerData,
289                             manufacturerDataMask);
290                 }
291             }
292 
293             // IRK
294             if (address != null) {
295                 final int addressType = in.readInt();
296                 if (in.readInt() == 1) {
297                     final byte[] irk = new byte[16];
298                     in.readByteArray(irk);
299                     builder.setDeviceAddress(address, addressType, irk);
300                 } else {
301                     builder.setDeviceAddress(address, addressType);
302                 }
303             }
304 
305             // Advertising data type
306             int advertisingDataType = in.readInt();
307             if (in.readInt() == 1) {
308                 byte[] advertisingData = null;
309                 byte[] advertisingDataMask = null;
310 
311                 int advertisingDataLength = in.readInt();
312                 advertisingData = new byte[advertisingDataLength];
313                 in.readByteArray(advertisingData);
314                 if (in.readInt() == 1) {
315                     int advertisingDataMaskLength = in.readInt();
316                     advertisingDataMask = new byte[advertisingDataMaskLength];
317                     in.readByteArray(advertisingDataMask);
318                 }
319                 builder.setAdvertisingDataTypeWithData(advertisingDataType, advertisingData,
320                         advertisingDataMask);
321             }
322 
323             if (in.readInt() == 1) {
324                 builder.setTransportBlockFilter(in.readTypedObject(TransportBlockFilter.CREATOR));
325             }
326 
327             return builder.build();
328         }
329     };
330 
331     /**
332      * Returns the filter set the device name field of Bluetooth advertisement data.
333      */
334     @Nullable
getDeviceName()335     public String getDeviceName() {
336         return mDeviceName;
337     }
338 
339     /**
340      * Returns the filter set on the service uuid.
341      */
342     @Nullable
getServiceUuid()343     public ParcelUuid getServiceUuid() {
344         return mServiceUuid;
345     }
346 
347     @Nullable
getServiceUuidMask()348     public ParcelUuid getServiceUuidMask() {
349         return mServiceUuidMask;
350     }
351 
352     /**
353      * Returns the filter set on the service Solicitation uuid.
354      */
355     @Nullable
getServiceSolicitationUuid()356     public ParcelUuid getServiceSolicitationUuid() {
357         return mServiceSolicitationUuid;
358     }
359 
360     /**
361      * Returns the filter set on the service Solicitation uuid mask.
362      */
363     @Nullable
getServiceSolicitationUuidMask()364     public ParcelUuid getServiceSolicitationUuidMask() {
365         return mServiceSolicitationUuidMask;
366     }
367 
368     @Nullable
getDeviceAddress()369     public String getDeviceAddress() {
370         return mDeviceAddress;
371     }
372 
373     /**
374      * @hide
375      */
376     @SystemApi
getAddressType()377     public @AddressType int getAddressType() {
378         return mAddressType;
379     }
380 
381     /**
382      * @hide
383      */
384     @SystemApi
385     @Nullable
getIrk()386     public byte[] getIrk() {
387         return mIrk;
388     }
389 
390     @Nullable
getServiceData()391     public byte[] getServiceData() {
392         return mServiceData;
393     }
394 
395     @Nullable
getServiceDataMask()396     public byte[] getServiceDataMask() {
397         return mServiceDataMask;
398     }
399 
400     @Nullable
getServiceDataUuid()401     public ParcelUuid getServiceDataUuid() {
402         return mServiceDataUuid;
403     }
404 
405     /**
406      * Returns the manufacturer id. -1 if the manufacturer filter is not set.
407      */
getManufacturerId()408     public int getManufacturerId() {
409         return mManufacturerId;
410     }
411 
412     @Nullable
getManufacturerData()413     public byte[] getManufacturerData() {
414         return mManufacturerData;
415     }
416 
417     @Nullable
getManufacturerDataMask()418     public byte[] getManufacturerDataMask() {
419         return mManufacturerDataMask;
420     }
421 
422     /**
423      * Return filter information for a transport block in Transport Discovery Service advertisement.
424      *
425      * @hide
426      */
427     @SystemApi
428     @Nullable
getTransportBlockFilter()429     public TransportBlockFilter getTransportBlockFilter() {
430         return mTransportBlockFilter;
431     }
432 
433     /**
434      * Returns the advertising data type of this filter.
435      * Returns {@link ScanRecord#DATA_TYPE_NONE} if the type is not set.
436      * The values of advertising data type are defined in the Bluetooth Generic Access Profile
437      * (https://www.bluetooth.com/specifications/assigned-numbers/)
438     */
439     @AdvertisingDataType
getAdvertisingDataType()440     public int getAdvertisingDataType() {
441         return mAdvertisingDataType;
442     }
443 
444     /**
445      * Returns the advertising data of this filter.
446     */
getAdvertisingData()447     public @Nullable byte[] getAdvertisingData() {
448         return mAdvertisingData;
449     }
450 
451     /**
452      * Returns the advertising data mask of this filter.
453     */
getAdvertisingDataMask()454     public @Nullable byte[] getAdvertisingDataMask() {
455         return mAdvertisingDataMask;
456     }
457 
458     /**
459      * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
460      * if it matches all the field filters.
461      */
matches(ScanResult scanResult)462     public boolean matches(ScanResult scanResult) {
463         if (scanResult == null) {
464             return false;
465         }
466         BluetoothDevice device = scanResult.getDevice();
467         // Device match.
468         if (mDeviceAddress != null
469                 && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
470             return false;
471         }
472 
473         ScanRecord scanRecord = scanResult.getScanRecord();
474 
475         // Scan record is null but there exist filters on it.
476         if (scanRecord == null
477                 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
478                 || mServiceData != null || mServiceSolicitationUuid != null
479                 || mAdvertisingData != null)) {
480             return false;
481         }
482 
483         // Local name match.
484         if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
485             return false;
486         }
487 
488         // UUID match.
489         if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
490                 scanRecord.getServiceUuids())) {
491             return false;
492         }
493 
494         // solicitation UUID match.
495         if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
496                 mServiceSolicitationUuid, mServiceSolicitationUuidMask,
497                 scanRecord.getServiceSolicitationUuids())) {
498             return false;
499         }
500 
501         // Service data match
502         if (mServiceDataUuid != null) {
503             if (!matchesPartialData(mServiceData, mServiceDataMask,
504                     scanRecord.getServiceData(mServiceDataUuid))) {
505                 return false;
506             }
507         }
508 
509         // Manufacturer data match.
510         if (mManufacturerId >= 0) {
511             if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
512                     scanRecord.getManufacturerSpecificData(mManufacturerId))) {
513                 return false;
514             }
515         }
516 
517         // Advertising data type match
518         if (mAdvertisingDataType > 0) {
519             byte[] advertisingData = scanRecord.getAdvertisingDataMap().get(mAdvertisingDataType);
520             if (advertisingData == null || !matchesPartialData(mAdvertisingData,
521                     mAdvertisingDataMask, advertisingData)) {
522                 return false;
523             }
524         }
525 
526         // Transport Discovery data match
527         if (mTransportBlockFilter != null && !mTransportBlockFilter.matches(scanResult)) {
528             return false;
529         }
530 
531         // All filters match.
532         return true;
533     }
534 
535     /**
536      * Check if the uuid pattern is contained in a list of parcel uuids.
537      *
538      * @hide
539      */
matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)540     public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
541             List<ParcelUuid> uuids) {
542         if (uuid == null) {
543             return true;
544         }
545         if (uuids == null) {
546             return false;
547         }
548 
549         for (ParcelUuid parcelUuid : uuids) {
550             UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
551             if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
552                 return true;
553             }
554         }
555         return false;
556     }
557 
558     // Check if the uuid pattern matches the particular service uuid.
matchesServiceUuid(UUID uuid, UUID mask, UUID data)559     private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
560         return BluetoothLeUtils.maskedEquals(data, uuid, mask);
561     }
562 
563     /**
564      * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
565      *
566      */
matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)567     private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
568             ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
569         if (solicitationUuid == null) {
570             return true;
571         }
572         if (solicitationUuids == null) {
573             return false;
574         }
575 
576         for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
577             UUID solicitationUuidMask = parcelSolicitationUuidMask == null
578                     ? null : parcelSolicitationUuidMask.getUuid();
579             if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
580                     parcelSolicitationUuid.getUuid())) {
581                 return true;
582             }
583         }
584         return false;
585     }
586 
587     // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)588     private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
589             UUID solicitationUuidMask, UUID data) {
590         return BluetoothLeUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
591     }
592 
593     // Check whether the data pattern matches the parsed data.
matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)594     static boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
595         if (parsedData == null || parsedData.length < data.length) {
596             return false;
597         }
598         if (dataMask == null) {
599             for (int i = 0; i < data.length; ++i) {
600                 if (parsedData[i] != data[i]) {
601                     return false;
602                 }
603             }
604             return true;
605         }
606         for (int i = 0; i < data.length; ++i) {
607             if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
608                 return false;
609             }
610         }
611         return true;
612     }
613 
614     @Override
toString()615     public String toString() {
616         return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
617                 + mDeviceAddress + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
618                 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
619                 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
620                 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid)
621                 + ", mServiceData=" + Arrays.toString(mServiceData) + ", mServiceDataMask="
622                 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
623                 + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
624                 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask)
625                 + ", mAdvertisingDataType=" + mAdvertisingDataType + ", mAdvertisingData="
626                 + Arrays.toString(mAdvertisingData) + ", mAdvertisingDataMask="
627                 + Arrays.toString(mAdvertisingDataMask)
628                 + ", mTransportBlockFilter=" + mTransportBlockFilter + "]";
629     }
630 
631     @Override
hashCode()632     public int hashCode() {
633         return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId,
634                 Arrays.hashCode(mManufacturerData),
635                 Arrays.hashCode(mManufacturerDataMask),
636                 mServiceDataUuid,
637                 Arrays.hashCode(mServiceData),
638                 Arrays.hashCode(mServiceDataMask),
639                 mServiceUuid, mServiceUuidMask,
640                 mServiceSolicitationUuid, mServiceSolicitationUuidMask,
641                 mAdvertisingDataType,
642                 Arrays.hashCode(mAdvertisingData),
643                 Arrays.hashCode(mAdvertisingDataMask),
644                 mTransportBlockFilter);
645     }
646 
647     @Override
equals(@ullable Object obj)648     public boolean equals(@Nullable Object obj) {
649         if (this == obj) {
650             return true;
651         }
652         if (obj == null || getClass() != obj.getClass()) {
653             return false;
654         }
655         ScanFilter other = (ScanFilter) obj;
656         return Objects.equals(mDeviceName, other.mDeviceName)
657                 && Objects.equals(mDeviceAddress, other.mDeviceAddress)
658                 && mManufacturerId == other.mManufacturerId
659                 && Objects.deepEquals(mManufacturerData, other.mManufacturerData)
660                 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask)
661                 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid)
662                 && Objects.deepEquals(mServiceData, other.mServiceData)
663                 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
664                 && Objects.equals(mServiceUuid, other.mServiceUuid)
665                 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
666                 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
667                 && Objects.equals(mServiceSolicitationUuidMask,
668                         other.mServiceSolicitationUuidMask)
669                 && mAdvertisingDataType == other.mAdvertisingDataType
670                 && Objects.deepEquals(mAdvertisingData, other.mAdvertisingData)
671                 && Objects.deepEquals(mAdvertisingDataMask, other.mAdvertisingDataMask)
672                 && Objects.equals(mTransportBlockFilter, other.getTransportBlockFilter());
673     }
674 
675     /**
676      * Checks if the scanfilter is empty
677      *
678      * @hide
679      */
isAllFieldsEmpty()680     public boolean isAllFieldsEmpty() {
681         return EMPTY.equals(this);
682     }
683 
684     /**
685      * Builder class for {@link ScanFilter}.
686      */
687     public static final class Builder {
688 
689         /**
690          * @hide
691          */
692         @SystemApi
693         public static final int LEN_IRK_OCTETS = 16;
694 
695         private String mDeviceName;
696         private String mDeviceAddress;
697         private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
698         private byte[] mIrk;
699 
700         private ParcelUuid mServiceUuid;
701         private ParcelUuid mUuidMask;
702 
703         private ParcelUuid mServiceSolicitationUuid;
704         private ParcelUuid mServiceSolicitationUuidMask;
705 
706         private ParcelUuid mServiceDataUuid;
707         private byte[] mServiceData;
708         private byte[] mServiceDataMask;
709 
710         private int mManufacturerId = -1;
711         private byte[] mManufacturerData;
712         private byte[] mManufacturerDataMask;
713 
714         private int mAdvertisingDataType = ScanRecord.DATA_TYPE_NONE;
715         private byte[] mAdvertisingData;
716         private byte[] mAdvertisingDataMask;
717 
718         private TransportBlockFilter mTransportBlockFilter = null;
719         /**
720          * Set filter on device name.
721          */
setDeviceName(String deviceName)722         public Builder setDeviceName(String deviceName) {
723             mDeviceName = deviceName;
724             return this;
725         }
726 
727         /**
728          * Set a scan filter on the remote device address.
729          * <p>
730          * The address passed to this API must be in big endian byte order. It needs to be in the
731          * format of "01:02:03:AB:CD:EF". The device address can be validated using
732          * {@link BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to
733          * {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}.
734          *
735          * @param deviceAddress the remote device Bluetooth address for the filter
736          * @throws IllegalArgumentException if the {@code deviceAddress} is invalid
737          */
setDeviceAddress(String deviceAddress)738         public Builder setDeviceAddress(String deviceAddress) {
739             if (deviceAddress == null) {
740                 mDeviceAddress = deviceAddress;
741                 return this;
742             }
743             return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
744         }
745 
746         /**
747          * Set a scan filter on the remote device address with an address type.
748          * <p>
749          * The address passed to this API must be in big endian byte order. It needs to be in the
750          * format of "01:02:03:AB:CD:EF". The device address can be validated using
751          * {@link BluetoothAdapter#checkBluetoothAddress}.
752          *
753          * @param deviceAddress the remote device Bluetooth address for the filter
754          * @param addressType indication of the type of address
755          *
756          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid
757          * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
758          * either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} or
759          * {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
760          * @throws NullPointerException if {@code deviceAddress} is null
761          *
762          * @hide
763          */
764         @NonNull
765         @SystemApi
setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType)766         public Builder setDeviceAddress(@NonNull String deviceAddress,
767                                         @AddressType int addressType) {
768             return setDeviceAddressInternal(deviceAddress, addressType, null);
769         }
770 
771         /**
772          * Set a scan filter on the remote device address with an address type and the Identity
773          * Resolving Key (IRK).
774          * <p>
775          * The address passed to this API must be either a public or random static address in big
776          * endian byte order. It needs to be in the format of "01:02:03:AB:CD:EF". The device
777          * address can be validated using {@link BluetoothAdapter#checkBluetoothAddress}.
778          * <p>
779          * The IRK is used to resolve a static address from a private address. The IRK must be
780          * provided in little endian byte order.
781          *
782          * @param deviceAddress the remote device Bluetooth address for the filter
783          * @param addressType indication of the type of address
784          * @param irk non-null little endian byte array representing the Identity Resolving Key
785          *
786          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid
787          * @throws IllegalArgumentException if the {@code irk} is invalid length
788          * @throws IllegalArgumentException If the {@code addressType} is an invalid length or is
789          * not PUBLIC or RANDOM STATIC
790          * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null
791          *
792          * @hide
793          */
794         @NonNull
795         @SystemApi
setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType, @NonNull byte[] irk)796         public Builder setDeviceAddress(@NonNull String deviceAddress,
797                                         @AddressType int addressType,
798                                         @NonNull byte[] irk) {
799             requireNonNull(irk);
800             if (irk.length != LEN_IRK_OCTETS) {
801                 throw new IllegalArgumentException("'irk' is invalid length!");
802             }
803             return setDeviceAddressInternal(deviceAddress, addressType, irk);
804         }
805 
806         /**
807          * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
808          *
809          * <p>Internal setter for the device address
810          *
811          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
812          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
813          * BluetoothAdapter#checkBluetoothAddress}.
814          * @param addressType indication of the type of address
815          * @param irk non-null little endian byte array representing the Identity Resolving Key;
816          *            nullable internally.
817          *
818          * @throws IllegalArgumentException if the {@code deviceAddress} is invalid
819          * @throws IllegalArgumentException if the {@code addressType} is not PUBLIC or RANDOM
820          * STATIC when an IRK is present
821          * @throws NullPointerException if {@code deviceAddress} is null
822          *
823          * @hide
824          */
825         @NonNull
setDeviceAddressInternal(@onNull String deviceAddress, @AddressType int addressType, @Nullable byte[] irk)826         private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
827                                                  @AddressType int addressType,
828                                                  @Nullable byte[] irk) {
829 
830             // Make sure our deviceAddress is valid!
831             requireNonNull(deviceAddress);
832             if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
833                 throw new IllegalArgumentException("invalid device address " + deviceAddress);
834             }
835 
836             // Verify type range
837             if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
838                 || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
839                 throw new IllegalArgumentException("'addressType' is invalid!");
840             }
841 
842             // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
843             if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
844                 // Don't want a bad combination of address and irk!
845                 if (irk != null) {
846                     // Since there are 3 possible RANDOM subtypes we must check to make sure
847                     // the correct type of address is used.
848                     if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
849                         throw new IllegalArgumentException(
850                                 "Invalid combination: IRK requires either a PUBLIC or "
851                                 + "RANDOM (STATIC) Address");
852                     }
853                 }
854             }
855 
856             // PUBLIC doesn't require extra work
857             // Without an IRK any address may be accepted
858 
859             mDeviceAddress = deviceAddress;
860             mAddressType = addressType;
861             mIrk = irk;
862             return this;
863         }
864 
865         /**
866          * Set filter on service uuid.
867          */
setServiceUuid(ParcelUuid serviceUuid)868         public Builder setServiceUuid(ParcelUuid serviceUuid) {
869             mServiceUuid = serviceUuid;
870             mUuidMask = null; // clear uuid mask
871             return this;
872         }
873 
874         /**
875          * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
876          * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
877          * bit in {@code serviceUuid}, and 0 to ignore that bit.
878          *
879          * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
880          * uuidMask} is not {@code null}.
881          */
setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)882         public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
883             if (mUuidMask != null && mServiceUuid == null) {
884                 throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
885             }
886             mServiceUuid = serviceUuid;
887             mUuidMask = uuidMask;
888             return this;
889         }
890 
891 
892         /**
893          * Set filter on service solicitation uuid.
894          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)895         public @NonNull Builder setServiceSolicitationUuid(
896                 @Nullable ParcelUuid serviceSolicitationUuid) {
897             mServiceSolicitationUuid = serviceSolicitationUuid;
898             if (serviceSolicitationUuid == null) {
899                 mServiceSolicitationUuidMask = null;
900             }
901             return this;
902         }
903 
904 
905         /**
906          * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
907          * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
908          * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
909          * ignore that bit.
910          *
911          * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null.
912          * @param solicitationUuidMask can be null or a mask with no restriction.
913          *
914          * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
915          *             {@code serviceSolicitationUuidMask} is not {@code null}.
916          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)917         public @NonNull Builder setServiceSolicitationUuid(
918                 @Nullable ParcelUuid serviceSolicitationUuid,
919                 @Nullable ParcelUuid solicitationUuidMask) {
920             if (solicitationUuidMask != null && serviceSolicitationUuid == null) {
921                 throw new IllegalArgumentException(
922                         "SolicitationUuid is null while SolicitationUuidMask is not null!");
923             }
924             mServiceSolicitationUuid = serviceSolicitationUuid;
925             mServiceSolicitationUuidMask = solicitationUuidMask;
926             return this;
927         }
928 
929         /**
930          * Set filtering on service data.
931          *
932          * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
933          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)934         public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
935             if (serviceDataUuid == null) {
936                 throw new IllegalArgumentException("serviceDataUuid is null");
937             }
938             mServiceDataUuid = serviceDataUuid;
939             mServiceData = serviceData;
940             mServiceDataMask = null; // clear service data mask
941             return this;
942         }
943 
944         /**
945          * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
946          * match the one in service data, otherwise set it to 0 to ignore that bit.
947          * <p>
948          * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
949          *
950          * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code
951          * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code
952          * serviceDataMask} and {@code serviceData} has different length.
953          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)954         public Builder setServiceData(ParcelUuid serviceDataUuid,
955                 byte[] serviceData, byte[] serviceDataMask) {
956             if (serviceDataUuid == null) {
957                 throw new IllegalArgumentException("serviceDataUuid is null");
958             }
959             if (mServiceDataMask != null) {
960                 if (mServiceData == null) {
961                     throw new IllegalArgumentException(
962                             "serviceData is null while serviceDataMask is not null");
963                 }
964                 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
965                 // byte array need to be the same.
966                 if (mServiceData.length != mServiceDataMask.length) {
967                     throw new IllegalArgumentException(
968                             "size mismatch for service data and service data mask");
969                 }
970             }
971             mServiceDataUuid = serviceDataUuid;
972             mServiceData = serviceData;
973             mServiceDataMask = serviceDataMask;
974             return this;
975         }
976 
977         /**
978          * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
979          *
980          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
981          */
setManufacturerData(int manufacturerId, byte[] manufacturerData)982         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
983             if (manufacturerData != null && manufacturerId < 0) {
984                 throw new IllegalArgumentException("invalid manufacture id");
985             }
986             mManufacturerId = manufacturerId;
987             mManufacturerData = manufacturerData;
988             mManufacturerDataMask = null; // clear manufacturer data mask
989             return this;
990         }
991 
992         /**
993          * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
994          * to match the one in manufacturer data, otherwise set it to 0.
995          * <p>
996          * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
997          *
998          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
999          * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code
1000          * manufacturerData} and {@code manufacturerDataMask} have different length.
1001          */
setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)1002         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
1003                 byte[] manufacturerDataMask) {
1004             if (manufacturerData != null && manufacturerId < 0) {
1005                 throw new IllegalArgumentException("invalid manufacture id");
1006             }
1007             if (mManufacturerDataMask != null) {
1008                 if (mManufacturerData == null) {
1009                     throw new IllegalArgumentException(
1010                             "manufacturerData is null while manufacturerDataMask is not null");
1011                 }
1012                 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
1013                 // of the two byte array need to be the same.
1014                 if (mManufacturerData.length != mManufacturerDataMask.length) {
1015                     throw new IllegalArgumentException(
1016                             "size mismatch for manufacturerData and manufacturerDataMask");
1017                 }
1018             }
1019             mManufacturerId = manufacturerId;
1020             mManufacturerData = manufacturerData;
1021             mManufacturerDataMask = manufacturerDataMask;
1022             return this;
1023         }
1024 
1025         /**
1026          * Set filter information for a transport block in Transport Discovery Service advertisement
1027          *
1028          * Use {@link BluetoothAdapter#getOffloadedTransportDiscoveryDataScanSupported()} to check
1029          * whether transport discovery data filtering is supported on this device before calling
1030          * this method.
1031          *
1032          * @param transportBlockFilter filter data for a transport block in Transport Discovery
1033          * Service advertisement
1034          * @throws IllegalArgumentException if Transport Discovery Data filter is not supported.
1035          * @return this builder
1036          * @hide
1037          */
1038         @SystemApi
1039         @NonNull
setTransportBlockFilter(@onNull TransportBlockFilter transportBlockFilter)1040         public Builder setTransportBlockFilter(@NonNull TransportBlockFilter transportBlockFilter) {
1041             BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
1042 
1043             if (bluetoothAdapter == null) {
1044                 throw new IllegalArgumentException("BluetoothAdapter is null");
1045             }
1046             if (bluetoothAdapter.getOffloadedTransportDiscoveryDataScanSupported()
1047                     != BluetoothStatusCodes.FEATURE_SUPPORTED) {
1048                 throw new IllegalArgumentException(
1049                         "Transport Discovery Data filter is not supported");
1050             }
1051 
1052             mTransportBlockFilter = transportBlockFilter;
1053             return this;
1054         }
1055 
1056         /**
1057          * Set filter on advertising data with specific advertising data type.
1058          * For any bit in the mask, set it the 1 if it needs to match the one in
1059          * advertising data, otherwise set it to 0.
1060          * <p>
1061          * The values of {@code advertisingDataType} are assigned by Bluetooth SIG. For more
1062          * details refer to Bluetooth Generic Access Profile.
1063          * (https://www.bluetooth.com/specifications/assigned-numbers/)
1064          * The {@code advertisingDataMask} must have the same length of {@code advertisingData}.
1065          *
1066          * @throws IllegalArgumentException If the {@code advertisingDataType} is invalid, {@code
1067          * advertisingData} or {@code advertisingDataMask} is null or {@code
1068          * advertisingData} and {@code advertisingDataMask} have different length.
1069          */
setAdvertisingDataTypeWithData( @dvertisingDataType int advertisingDataType, @NonNull byte[] advertisingData, @NonNull byte[] advertisingDataMask)1070         public @NonNull Builder setAdvertisingDataTypeWithData(
1071                 @AdvertisingDataType int advertisingDataType, @NonNull byte[] advertisingData,
1072                 @NonNull byte[] advertisingDataMask) {
1073             if (advertisingDataType < 0) {
1074                 throw new IllegalArgumentException("invalid advertising data type");
1075             }
1076             if (mAdvertisingDataMask != null) {
1077                 if (mAdvertisingData == null) {
1078                     throw new IllegalArgumentException(
1079                             "mAdvertisingData is null while mAdvertisingDataMask is not null");
1080                 }
1081                 // Since the mAdvertisingDataMask is a bit mask for mAdvertisingData, the lengths
1082                 // of the two byte array need to be the same.
1083                 if (mAdvertisingData.length != mAdvertisingDataMask.length) {
1084                     throw new IllegalArgumentException(
1085                             "size mismatch for mAdvertisingData and mAdvertisingDataMask");
1086                 }
1087             }
1088             mAdvertisingDataType = advertisingDataType;
1089             mAdvertisingData = advertisingData;
1090             mAdvertisingDataMask = advertisingDataMask;
1091             return this;
1092         }
1093 
1094 
1095         /**
1096          * Set filter on advertising data with specific advertising data type.
1097          * <p>
1098          * The values of {@code advertisingDataType} are assigned by Bluetooth SIG. For more
1099          * details refer to Bluetooth Generic Access Profile.
1100          * (https://www.bluetooth.com/specifications/assigned-numbers/)
1101          * @throws IllegalArgumentException If the {@code advertisingDataType} is invalid
1102          */
setAdvertisingDataType( @dvertisingDataType int advertisingDataType)1103         public @NonNull Builder setAdvertisingDataType(
1104                 @AdvertisingDataType int advertisingDataType) {
1105             if (advertisingDataType < 0) {
1106                 throw new IllegalArgumentException("invalid advertising data type");
1107             }
1108             mAdvertisingDataType = advertisingDataType;
1109             return this;
1110         }
1111 
1112         /**
1113          * Build {@link ScanFilter}.
1114          *
1115          * @throws IllegalArgumentException If the filter cannot be built.
1116          */
build()1117         public ScanFilter build() {
1118             return new ScanFilter(mDeviceName, mDeviceAddress, mServiceUuid, mUuidMask,
1119                     mServiceSolicitationUuid, mServiceSolicitationUuidMask, mServiceDataUuid,
1120                     mServiceData, mServiceDataMask, mManufacturerId, mManufacturerData,
1121                     mManufacturerDataMask, mAddressType, mIrk, mAdvertisingDataType,
1122                     mAdvertisingData, mAdvertisingDataMask, mTransportBlockFilter);
1123         }
1124     }
1125 }
1126