1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.bluetooth.le;
18 
19 import android.annotation.NonNull;
20 import android.annotation.Nullable;
21 import android.bluetooth.BluetoothAdapter;
22 import android.bluetooth.BluetoothDevice;
23 import android.os.Parcel;
24 import android.os.ParcelUuid;
25 import android.os.Parcelable;
26 
27 import com.android.internal.util.BitUtils;
28 
29 import java.util.Arrays;
30 import java.util.List;
31 import java.util.Objects;
32 import java.util.UUID;
33 
34 /**
35  * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
36  * restrict scan results to only those that are of interest to them.
37  * <p>
38  * Current filtering on the following fields are supported:
39  * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
40  * <li>Name of remote Bluetooth LE device.
41  * <li>Mac address of the remote device.
42  * <li>Service data which is the data associated with a service.
43  * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
44  *
45  * @see ScanResult
46  * @see BluetoothLeScanner
47  */
48 public final class ScanFilter implements Parcelable {
49 
50     @Nullable
51     private final String mDeviceName;
52 
53     @Nullable
54     private final String mDeviceAddress;
55 
56     @Nullable
57     private final ParcelUuid mServiceUuid;
58     @Nullable
59     private final ParcelUuid mServiceUuidMask;
60 
61     @Nullable
62     private final ParcelUuid mServiceSolicitationUuid;
63     @Nullable
64     private final ParcelUuid mServiceSolicitationUuidMask;
65 
66     @Nullable
67     private final ParcelUuid mServiceDataUuid;
68     @Nullable
69     private final byte[] mServiceData;
70     @Nullable
71     private final byte[] mServiceDataMask;
72 
73     private final int mManufacturerId;
74     @Nullable
75     private final byte[] mManufacturerData;
76     @Nullable
77     private final byte[] mManufacturerDataMask;
78 
79     /** @hide */
80     public static final ScanFilter EMPTY = new ScanFilter.Builder().build();
81 
82 
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)83     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
84             ParcelUuid uuidMask, ParcelUuid solicitationUuid,
85             ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
86             byte[] serviceData, byte[] serviceDataMask,
87             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
88         mDeviceName = name;
89         mServiceUuid = uuid;
90         mServiceUuidMask = uuidMask;
91         mServiceSolicitationUuid = solicitationUuid;
92         mServiceSolicitationUuidMask = solicitationUuidMask;
93         mDeviceAddress = deviceAddress;
94         mServiceDataUuid = serviceDataUuid;
95         mServiceData = serviceData;
96         mServiceDataMask = serviceDataMask;
97         mManufacturerId = manufacturerId;
98         mManufacturerData = manufacturerData;
99         mManufacturerDataMask = manufacturerDataMask;
100     }
101 
102     @Override
describeContents()103     public int describeContents() {
104         return 0;
105     }
106 
107     @Override
writeToParcel(Parcel dest, int flags)108     public void writeToParcel(Parcel dest, int flags) {
109         dest.writeInt(mDeviceName == null ? 0 : 1);
110         if (mDeviceName != null) {
111             dest.writeString(mDeviceName);
112         }
113         dest.writeInt(mDeviceAddress == null ? 0 : 1);
114         if (mDeviceAddress != null) {
115             dest.writeString(mDeviceAddress);
116         }
117         dest.writeInt(mServiceUuid == null ? 0 : 1);
118         if (mServiceUuid != null) {
119             dest.writeParcelable(mServiceUuid, flags);
120             dest.writeInt(mServiceUuidMask == null ? 0 : 1);
121             if (mServiceUuidMask != null) {
122                 dest.writeParcelable(mServiceUuidMask, flags);
123             }
124         }
125         dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
126         if (mServiceSolicitationUuid != null) {
127             dest.writeParcelable(mServiceSolicitationUuid, flags);
128             dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
129             if (mServiceSolicitationUuidMask != null) {
130                 dest.writeParcelable(mServiceSolicitationUuidMask, flags);
131             }
132         }
133         dest.writeInt(mServiceDataUuid == null ? 0 : 1);
134         if (mServiceDataUuid != null) {
135             dest.writeParcelable(mServiceDataUuid, flags);
136             dest.writeInt(mServiceData == null ? 0 : 1);
137             if (mServiceData != null) {
138                 dest.writeInt(mServiceData.length);
139                 dest.writeByteArray(mServiceData);
140 
141                 dest.writeInt(mServiceDataMask == null ? 0 : 1);
142                 if (mServiceDataMask != null) {
143                     dest.writeInt(mServiceDataMask.length);
144                     dest.writeByteArray(mServiceDataMask);
145                 }
146             }
147         }
148         dest.writeInt(mManufacturerId);
149         dest.writeInt(mManufacturerData == null ? 0 : 1);
150         if (mManufacturerData != null) {
151             dest.writeInt(mManufacturerData.length);
152             dest.writeByteArray(mManufacturerData);
153 
154             dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
155             if (mManufacturerDataMask != null) {
156                 dest.writeInt(mManufacturerDataMask.length);
157                 dest.writeByteArray(mManufacturerDataMask);
158             }
159         }
160     }
161 
162     /**
163      * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
164      */
165     public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR =
166             new Creator<ScanFilter>() {
167 
168         @Override
169         public ScanFilter[] newArray(int size) {
170             return new ScanFilter[size];
171         }
172 
173         @Override
174         public ScanFilter createFromParcel(Parcel in) {
175             Builder builder = new Builder();
176             if (in.readInt() == 1) {
177                 builder.setDeviceName(in.readString());
178             }
179             if (in.readInt() == 1) {
180                 builder.setDeviceAddress(in.readString());
181             }
182             if (in.readInt() == 1) {
183                 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
184                 builder.setServiceUuid(uuid);
185                 if (in.readInt() == 1) {
186                     ParcelUuid uuidMask = in.readParcelable(
187                             ParcelUuid.class.getClassLoader());
188                     builder.setServiceUuid(uuid, uuidMask);
189                 }
190             }
191             if (in.readInt() == 1) {
192                 ParcelUuid solicitationUuid = in.readParcelable(
193                         ParcelUuid.class.getClassLoader());
194                 builder.setServiceSolicitationUuid(solicitationUuid);
195                 if (in.readInt() == 1) {
196                     ParcelUuid solicitationUuidMask = in.readParcelable(
197                             ParcelUuid.class.getClassLoader());
198                     builder.setServiceSolicitationUuid(solicitationUuid,
199                             solicitationUuidMask);
200                 }
201             }
202             if (in.readInt() == 1) {
203                 ParcelUuid servcieDataUuid =
204                         in.readParcelable(ParcelUuid.class.getClassLoader());
205                 if (in.readInt() == 1) {
206                     int serviceDataLength = in.readInt();
207                     byte[] serviceData = new byte[serviceDataLength];
208                     in.readByteArray(serviceData);
209                     if (in.readInt() == 0) {
210                         builder.setServiceData(servcieDataUuid, serviceData);
211                     } else {
212                         int serviceDataMaskLength = in.readInt();
213                         byte[] serviceDataMask = new byte[serviceDataMaskLength];
214                         in.readByteArray(serviceDataMask);
215                         builder.setServiceData(
216                                 servcieDataUuid, serviceData, serviceDataMask);
217                     }
218                 }
219             }
220 
221             int manufacturerId = in.readInt();
222             if (in.readInt() == 1) {
223                 int manufacturerDataLength = in.readInt();
224                 byte[] manufacturerData = new byte[manufacturerDataLength];
225                 in.readByteArray(manufacturerData);
226                 if (in.readInt() == 0) {
227                     builder.setManufacturerData(manufacturerId, manufacturerData);
228                 } else {
229                     int manufacturerDataMaskLength = in.readInt();
230                     byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
231                     in.readByteArray(manufacturerDataMask);
232                     builder.setManufacturerData(manufacturerId, manufacturerData,
233                             manufacturerDataMask);
234                 }
235             }
236 
237             return builder.build();
238         }
239     };
240 
241     /**
242      * Returns the filter set the device name field of Bluetooth advertisement data.
243      */
244     @Nullable
getDeviceName()245     public String getDeviceName() {
246         return mDeviceName;
247     }
248 
249     /**
250      * Returns the filter set on the service uuid.
251      */
252     @Nullable
getServiceUuid()253     public ParcelUuid getServiceUuid() {
254         return mServiceUuid;
255     }
256 
257     @Nullable
getServiceUuidMask()258     public ParcelUuid getServiceUuidMask() {
259         return mServiceUuidMask;
260     }
261 
262     /**
263      * Returns the filter set on the service Solicitation uuid.
264      */
265     @Nullable
getServiceSolicitationUuid()266     public ParcelUuid getServiceSolicitationUuid() {
267         return mServiceSolicitationUuid;
268     }
269 
270     /**
271      * Returns the filter set on the service Solicitation uuid mask.
272      */
273     @Nullable
getServiceSolicitationUuidMask()274     public ParcelUuid getServiceSolicitationUuidMask() {
275         return mServiceSolicitationUuidMask;
276     }
277 
278     @Nullable
getDeviceAddress()279     public String getDeviceAddress() {
280         return mDeviceAddress;
281     }
282 
283     @Nullable
getServiceData()284     public byte[] getServiceData() {
285         return mServiceData;
286     }
287 
288     @Nullable
getServiceDataMask()289     public byte[] getServiceDataMask() {
290         return mServiceDataMask;
291     }
292 
293     @Nullable
getServiceDataUuid()294     public ParcelUuid getServiceDataUuid() {
295         return mServiceDataUuid;
296     }
297 
298     /**
299      * Returns the manufacturer id. -1 if the manufacturer filter is not set.
300      */
getManufacturerId()301     public int getManufacturerId() {
302         return mManufacturerId;
303     }
304 
305     @Nullable
getManufacturerData()306     public byte[] getManufacturerData() {
307         return mManufacturerData;
308     }
309 
310     @Nullable
getManufacturerDataMask()311     public byte[] getManufacturerDataMask() {
312         return mManufacturerDataMask;
313     }
314 
315     /**
316      * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
317      * if it matches all the field filters.
318      */
matches(ScanResult scanResult)319     public boolean matches(ScanResult scanResult) {
320         if (scanResult == null) {
321             return false;
322         }
323         BluetoothDevice device = scanResult.getDevice();
324         // Device match.
325         if (mDeviceAddress != null
326                 && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
327             return false;
328         }
329 
330         ScanRecord scanRecord = scanResult.getScanRecord();
331 
332         // Scan record is null but there exist filters on it.
333         if (scanRecord == null
334                 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
335                 || mServiceData != null || mServiceSolicitationUuid != null)) {
336             return false;
337         }
338 
339         // Local name match.
340         if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
341             return false;
342         }
343 
344         // UUID match.
345         if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
346                 scanRecord.getServiceUuids())) {
347             return false;
348         }
349 
350         // solicitation UUID match.
351         if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
352                 mServiceSolicitationUuid, mServiceSolicitationUuidMask,
353                 scanRecord.getServiceSolicitationUuids())) {
354             return false;
355         }
356 
357         // Service data match
358         if (mServiceDataUuid != null) {
359             if (!matchesPartialData(mServiceData, mServiceDataMask,
360                     scanRecord.getServiceData(mServiceDataUuid))) {
361                 return false;
362             }
363         }
364 
365         // Manufacturer data match.
366         if (mManufacturerId >= 0) {
367             if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
368                     scanRecord.getManufacturerSpecificData(mManufacturerId))) {
369                 return false;
370             }
371         }
372         // All filters match.
373         return true;
374     }
375 
376     /**
377      * Check if the uuid pattern is contained in a list of parcel uuids.
378      *
379      * @hide
380      */
matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)381     public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
382             List<ParcelUuid> uuids) {
383         if (uuid == null) {
384             return true;
385         }
386         if (uuids == null) {
387             return false;
388         }
389 
390         for (ParcelUuid parcelUuid : uuids) {
391             UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
392             if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
393                 return true;
394             }
395         }
396         return false;
397     }
398 
399     // Check if the uuid pattern matches the particular service uuid.
matchesServiceUuid(UUID uuid, UUID mask, UUID data)400     private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
401         return BitUtils.maskedEquals(data, uuid, mask);
402     }
403 
404     /**
405      * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
406      *
407      */
matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)408     private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
409             ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
410         if (solicitationUuid == null) {
411             return true;
412         }
413         if (solicitationUuids == null) {
414             return false;
415         }
416 
417         for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
418             UUID solicitationUuidMask = parcelSolicitationUuidMask == null
419                     ? null : parcelSolicitationUuidMask.getUuid();
420             if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
421                     parcelSolicitationUuid.getUuid())) {
422                 return true;
423             }
424         }
425         return false;
426     }
427 
428     // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)429     private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
430             UUID solicitationUuidMask, UUID data) {
431         return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
432     }
433 
434     // Check whether the data pattern matches the parsed data.
matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)435     private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
436         if (parsedData == null || parsedData.length < data.length) {
437             return false;
438         }
439         if (dataMask == null) {
440             for (int i = 0; i < data.length; ++i) {
441                 if (parsedData[i] != data[i]) {
442                     return false;
443                 }
444             }
445             return true;
446         }
447         for (int i = 0; i < data.length; ++i) {
448             if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
449                 return false;
450             }
451         }
452         return true;
453     }
454 
455     @Override
toString()456     public String toString() {
457         return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
458                 + mDeviceAddress
459                 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
460                 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
461                 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
462                 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
463                 + Arrays.toString(mServiceData) + ", mServiceDataMask="
464                 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
465                 + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
466                 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
467     }
468 
469     @Override
hashCode()470     public int hashCode() {
471         return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId,
472                 Arrays.hashCode(mManufacturerData),
473                 Arrays.hashCode(mManufacturerDataMask),
474                 mServiceDataUuid,
475                 Arrays.hashCode(mServiceData),
476                 Arrays.hashCode(mServiceDataMask),
477                 mServiceUuid, mServiceUuidMask,
478                 mServiceSolicitationUuid, mServiceSolicitationUuidMask);
479     }
480 
481     @Override
equals(Object obj)482     public boolean equals(Object obj) {
483         if (this == obj) {
484             return true;
485         }
486         if (obj == null || getClass() != obj.getClass()) {
487             return false;
488         }
489         ScanFilter other = (ScanFilter) obj;
490         return Objects.equals(mDeviceName, other.mDeviceName)
491                 && Objects.equals(mDeviceAddress, other.mDeviceAddress)
492                 && mManufacturerId == other.mManufacturerId
493                 && Objects.deepEquals(mManufacturerData, other.mManufacturerData)
494                 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask)
495                 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid)
496                 && Objects.deepEquals(mServiceData, other.mServiceData)
497                 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
498                 && Objects.equals(mServiceUuid, other.mServiceUuid)
499                 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
500                 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
501                 && Objects.equals(mServiceSolicitationUuidMask,
502                         other.mServiceSolicitationUuidMask);
503     }
504 
505     /**
506      * Checks if the scanfilter is empty
507      *
508      * @hide
509      */
isAllFieldsEmpty()510     public boolean isAllFieldsEmpty() {
511         return EMPTY.equals(this);
512     }
513 
514     /**
515      * Builder class for {@link ScanFilter}.
516      */
517     public static final class Builder {
518 
519         private String mDeviceName;
520         private String mDeviceAddress;
521 
522         private ParcelUuid mServiceUuid;
523         private ParcelUuid mUuidMask;
524 
525         private ParcelUuid mServiceSolicitationUuid;
526         private ParcelUuid mServiceSolicitationUuidMask;
527 
528         private ParcelUuid mServiceDataUuid;
529         private byte[] mServiceData;
530         private byte[] mServiceDataMask;
531 
532         private int mManufacturerId = -1;
533         private byte[] mManufacturerData;
534         private byte[] mManufacturerDataMask;
535 
536         /**
537          * Set filter on device name.
538          */
setDeviceName(String deviceName)539         public Builder setDeviceName(String deviceName) {
540             mDeviceName = deviceName;
541             return this;
542         }
543 
544         /**
545          * Set filter on device address.
546          *
547          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
548          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
549          * BluetoothAdapter#checkBluetoothAddress}.
550          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
551          */
setDeviceAddress(String deviceAddress)552         public Builder setDeviceAddress(String deviceAddress) {
553             if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
554                 throw new IllegalArgumentException("invalid device address " + deviceAddress);
555             }
556             mDeviceAddress = deviceAddress;
557             return this;
558         }
559 
560         /**
561          * Set filter on service uuid.
562          */
setServiceUuid(ParcelUuid serviceUuid)563         public Builder setServiceUuid(ParcelUuid serviceUuid) {
564             mServiceUuid = serviceUuid;
565             mUuidMask = null; // clear uuid mask
566             return this;
567         }
568 
569         /**
570          * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
571          * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
572          * bit in {@code serviceUuid}, and 0 to ignore that bit.
573          *
574          * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
575          * uuidMask} is not {@code null}.
576          */
setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)577         public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
578             if (mUuidMask != null && mServiceUuid == null) {
579                 throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
580             }
581             mServiceUuid = serviceUuid;
582             mUuidMask = uuidMask;
583             return this;
584         }
585 
586 
587         /**
588          * Set filter on service solicitation uuid.
589          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)590         public @NonNull Builder setServiceSolicitationUuid(
591                 @Nullable ParcelUuid serviceSolicitationUuid) {
592             mServiceSolicitationUuid = serviceSolicitationUuid;
593             if (serviceSolicitationUuid == null) {
594                 mServiceSolicitationUuidMask = null;
595             }
596             return this;
597         }
598 
599 
600         /**
601          * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
602          * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
603          * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
604          * ignore that bit.
605          *
606          * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null.
607          * @param solicitationUuidMask can be null or a mask with no restriction.
608          *
609          * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
610          *             {@code serviceSolicitationUuidMask} is not {@code null}.
611          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)612         public @NonNull Builder setServiceSolicitationUuid(
613                 @Nullable ParcelUuid serviceSolicitationUuid,
614                 @Nullable ParcelUuid solicitationUuidMask) {
615             if (solicitationUuidMask != null && serviceSolicitationUuid == null) {
616                 throw new IllegalArgumentException(
617                         "SolicitationUuid is null while SolicitationUuidMask is not null!");
618             }
619             mServiceSolicitationUuid = serviceSolicitationUuid;
620             mServiceSolicitationUuidMask = solicitationUuidMask;
621             return this;
622         }
623 
624         /**
625          * Set filtering on service data.
626          *
627          * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
628          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)629         public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
630             if (serviceDataUuid == null) {
631                 throw new IllegalArgumentException("serviceDataUuid is null");
632             }
633             mServiceDataUuid = serviceDataUuid;
634             mServiceData = serviceData;
635             mServiceDataMask = null; // clear service data mask
636             return this;
637         }
638 
639         /**
640          * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
641          * match the one in service data, otherwise set it to 0 to ignore that bit.
642          * <p>
643          * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
644          *
645          * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code
646          * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code
647          * serviceDataMask} and {@code serviceData} has different length.
648          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)649         public Builder setServiceData(ParcelUuid serviceDataUuid,
650                 byte[] serviceData, byte[] serviceDataMask) {
651             if (serviceDataUuid == null) {
652                 throw new IllegalArgumentException("serviceDataUuid is null");
653             }
654             if (mServiceDataMask != null) {
655                 if (mServiceData == null) {
656                     throw new IllegalArgumentException(
657                             "serviceData is null while serviceDataMask is not null");
658                 }
659                 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
660                 // byte array need to be the same.
661                 if (mServiceData.length != mServiceDataMask.length) {
662                     throw new IllegalArgumentException(
663                             "size mismatch for service data and service data mask");
664                 }
665             }
666             mServiceDataUuid = serviceDataUuid;
667             mServiceData = serviceData;
668             mServiceDataMask = serviceDataMask;
669             return this;
670         }
671 
672         /**
673          * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
674          *
675          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
676          */
setManufacturerData(int manufacturerId, byte[] manufacturerData)677         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
678             if (manufacturerData != null && manufacturerId < 0) {
679                 throw new IllegalArgumentException("invalid manufacture id");
680             }
681             mManufacturerId = manufacturerId;
682             mManufacturerData = manufacturerData;
683             mManufacturerDataMask = null; // clear manufacturer data mask
684             return this;
685         }
686 
687         /**
688          * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
689          * to match the one in manufacturer data, otherwise set it to 0.
690          * <p>
691          * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
692          *
693          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
694          * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code
695          * manufacturerData} and {@code manufacturerDataMask} have different length.
696          */
setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)697         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
698                 byte[] manufacturerDataMask) {
699             if (manufacturerData != null && manufacturerId < 0) {
700                 throw new IllegalArgumentException("invalid manufacture id");
701             }
702             if (mManufacturerDataMask != null) {
703                 if (mManufacturerData == null) {
704                     throw new IllegalArgumentException(
705                             "manufacturerData is null while manufacturerDataMask is not null");
706                 }
707                 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
708                 // of the two byte array need to be the same.
709                 if (mManufacturerData.length != mManufacturerDataMask.length) {
710                     throw new IllegalArgumentException(
711                             "size mismatch for manufacturerData and manufacturerDataMask");
712                 }
713             }
714             mManufacturerId = manufacturerId;
715             mManufacturerData = manufacturerData;
716             mManufacturerDataMask = manufacturerDataMask;
717             return this;
718         }
719 
720         /**
721          * Build {@link ScanFilter}.
722          *
723          * @throws IllegalArgumentException If the filter cannot be built.
724          */
build()725         public ScanFilter build() {
726             return new ScanFilter(mDeviceName, mDeviceAddress,
727                     mServiceUuid, mUuidMask, mServiceSolicitationUuid,
728                     mServiceSolicitationUuidMask,
729                     mServiceDataUuid, mServiceData, mServiceDataMask,
730                     mManufacturerId, mManufacturerData, mManufacturerDataMask);
731         }
732     }
733 }
734