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