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