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