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.os.Parcel;
28 import android.os.ParcelUuid;
29 import android.os.Parcelable;
30 
31 import com.android.internal.util.BitUtils;
32 
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.Objects;
36 import java.util.UUID;
37 
38 /**
39  * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
40  * restrict scan results to only those that are of interest to them.
41  * <p>
42  * Current filtering on the following fields are supported:
43  * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
44  * <li>Name of remote Bluetooth LE device.
45  * <li>Mac address of the remote device.
46  * <li>Service data which is the data associated with a service.
47  * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
48  *
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     /** @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)91     private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
92             ParcelUuid uuidMask, ParcelUuid solicitationUuid,
93             ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid,
94             byte[] serviceData, byte[] serviceDataMask,
95             int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask,
96             @AddressType int addressType, @Nullable byte[] irk) {
97         mDeviceName = name;
98         mServiceUuid = uuid;
99         mServiceUuidMask = uuidMask;
100         mServiceSolicitationUuid = solicitationUuid;
101         mServiceSolicitationUuidMask = solicitationUuidMask;
102         mDeviceAddress = deviceAddress;
103         mServiceDataUuid = serviceDataUuid;
104         mServiceData = serviceData;
105         mServiceDataMask = serviceDataMask;
106         mManufacturerId = manufacturerId;
107         mManufacturerData = manufacturerData;
108         mManufacturerDataMask = manufacturerDataMask;
109         mAddressType = addressType;
110         mIrk = irk;
111     }
112 
113     @Override
describeContents()114     public int describeContents() {
115         return 0;
116     }
117 
118     @Override
writeToParcel(Parcel dest, int flags)119     public void writeToParcel(Parcel dest, int flags) {
120         dest.writeInt(mDeviceName == null ? 0 : 1);
121         if (mDeviceName != null) {
122             dest.writeString(mDeviceName);
123         }
124         dest.writeInt(mDeviceAddress == null ? 0 : 1);
125         if (mDeviceAddress != null) {
126             dest.writeString(mDeviceAddress);
127         }
128         dest.writeInt(mServiceUuid == null ? 0 : 1);
129         if (mServiceUuid != null) {
130             dest.writeParcelable(mServiceUuid, flags);
131             dest.writeInt(mServiceUuidMask == null ? 0 : 1);
132             if (mServiceUuidMask != null) {
133                 dest.writeParcelable(mServiceUuidMask, flags);
134             }
135         }
136         dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1);
137         if (mServiceSolicitationUuid != null) {
138             dest.writeParcelable(mServiceSolicitationUuid, flags);
139             dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1);
140             if (mServiceSolicitationUuidMask != null) {
141                 dest.writeParcelable(mServiceSolicitationUuidMask, flags);
142             }
143         }
144         dest.writeInt(mServiceDataUuid == null ? 0 : 1);
145         if (mServiceDataUuid != null) {
146             dest.writeParcelable(mServiceDataUuid, flags);
147             dest.writeInt(mServiceData == null ? 0 : 1);
148             if (mServiceData != null) {
149                 dest.writeInt(mServiceData.length);
150                 dest.writeByteArray(mServiceData);
151 
152                 dest.writeInt(mServiceDataMask == null ? 0 : 1);
153                 if (mServiceDataMask != null) {
154                     dest.writeInt(mServiceDataMask.length);
155                     dest.writeByteArray(mServiceDataMask);
156                 }
157             }
158         }
159         dest.writeInt(mManufacturerId);
160         dest.writeInt(mManufacturerData == null ? 0 : 1);
161         if (mManufacturerData != null) {
162             dest.writeInt(mManufacturerData.length);
163             dest.writeByteArray(mManufacturerData);
164 
165             dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
166             if (mManufacturerDataMask != null) {
167                 dest.writeInt(mManufacturerDataMask.length);
168                 dest.writeByteArray(mManufacturerDataMask);
169             }
170         }
171 
172         // IRK
173         if (mDeviceAddress != null) {
174             dest.writeInt(mAddressType);
175             dest.writeInt(mIrk == null ? 0 : 1);
176             if (mIrk != null) {
177                 dest.writeByteArray(mIrk);
178             }
179         }
180     }
181 
182     /**
183      * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
184      */
185     public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR =
186             new Creator<ScanFilter>() {
187 
188         @Override
189         public ScanFilter[] newArray(int size) {
190             return new ScanFilter[size];
191         }
192 
193         @Override
194         public ScanFilter createFromParcel(Parcel in) {
195             Builder builder = new Builder();
196             if (in.readInt() == 1) {
197                 builder.setDeviceName(in.readString());
198             }
199             String address = null;
200             // If we have a non-null address
201             if (in.readInt() == 1) {
202                 address = in.readString();
203             }
204             if (in.readInt() == 1) {
205                 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
206                 builder.setServiceUuid(uuid);
207                 if (in.readInt() == 1) {
208                     ParcelUuid uuidMask = in.readParcelable(
209                             ParcelUuid.class.getClassLoader());
210                     builder.setServiceUuid(uuid, uuidMask);
211                 }
212             }
213             if (in.readInt() == 1) {
214                 ParcelUuid solicitationUuid = in.readParcelable(
215                         ParcelUuid.class.getClassLoader());
216                 builder.setServiceSolicitationUuid(solicitationUuid);
217                 if (in.readInt() == 1) {
218                     ParcelUuid solicitationUuidMask = in.readParcelable(
219                             ParcelUuid.class.getClassLoader());
220                     builder.setServiceSolicitationUuid(solicitationUuid,
221                             solicitationUuidMask);
222                 }
223             }
224             if (in.readInt() == 1) {
225                 ParcelUuid servcieDataUuid =
226                         in.readParcelable(ParcelUuid.class.getClassLoader());
227                 if (in.readInt() == 1) {
228                     int serviceDataLength = in.readInt();
229                     byte[] serviceData = new byte[serviceDataLength];
230                     in.readByteArray(serviceData);
231                     if (in.readInt() == 0) {
232                         builder.setServiceData(servcieDataUuid, serviceData);
233                     } else {
234                         int serviceDataMaskLength = in.readInt();
235                         byte[] serviceDataMask = new byte[serviceDataMaskLength];
236                         in.readByteArray(serviceDataMask);
237                         builder.setServiceData(
238                                 servcieDataUuid, serviceData, serviceDataMask);
239                     }
240                 }
241             }
242 
243             int manufacturerId = in.readInt();
244             if (in.readInt() == 1) {
245                 int manufacturerDataLength = in.readInt();
246                 byte[] manufacturerData = new byte[manufacturerDataLength];
247                 in.readByteArray(manufacturerData);
248                 if (in.readInt() == 0) {
249                     builder.setManufacturerData(manufacturerId, manufacturerData);
250                 } else {
251                     int manufacturerDataMaskLength = in.readInt();
252                     byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
253                     in.readByteArray(manufacturerDataMask);
254                     builder.setManufacturerData(manufacturerId, manufacturerData,
255                             manufacturerDataMask);
256                 }
257             }
258 
259             // IRK
260             if (address != null) {
261                 final int addressType = in.readInt();
262                 if (in.readInt() == 1) {
263                     final byte[] irk = new byte[16];
264                     in.readByteArray(irk);
265                     builder.setDeviceAddress(address, addressType, irk);
266                 } else {
267                     builder.setDeviceAddress(address, addressType);
268                 }
269             }
270             return builder.build();
271         }
272     };
273 
274     /**
275      * Returns the filter set the device name field of Bluetooth advertisement data.
276      */
277     @Nullable
getDeviceName()278     public String getDeviceName() {
279         return mDeviceName;
280     }
281 
282     /**
283      * Returns the filter set on the service uuid.
284      */
285     @Nullable
getServiceUuid()286     public ParcelUuid getServiceUuid() {
287         return mServiceUuid;
288     }
289 
290     @Nullable
getServiceUuidMask()291     public ParcelUuid getServiceUuidMask() {
292         return mServiceUuidMask;
293     }
294 
295     /**
296      * Returns the filter set on the service Solicitation uuid.
297      */
298     @Nullable
getServiceSolicitationUuid()299     public ParcelUuid getServiceSolicitationUuid() {
300         return mServiceSolicitationUuid;
301     }
302 
303     /**
304      * Returns the filter set on the service Solicitation uuid mask.
305      */
306     @Nullable
getServiceSolicitationUuidMask()307     public ParcelUuid getServiceSolicitationUuidMask() {
308         return mServiceSolicitationUuidMask;
309     }
310 
311     @Nullable
getDeviceAddress()312     public String getDeviceAddress() {
313         return mDeviceAddress;
314     }
315 
316     /**
317      * @hide
318      */
319     @SystemApi
getAddressType()320     public @AddressType int getAddressType() {
321         return mAddressType;
322     }
323 
324     /**
325      * @hide
326      */
327     @SystemApi
328     @Nullable
getIrk()329     public byte[] getIrk() {
330         return mIrk;
331     }
332 
333     @Nullable
getServiceData()334     public byte[] getServiceData() {
335         return mServiceData;
336     }
337 
338     @Nullable
getServiceDataMask()339     public byte[] getServiceDataMask() {
340         return mServiceDataMask;
341     }
342 
343     @Nullable
getServiceDataUuid()344     public ParcelUuid getServiceDataUuid() {
345         return mServiceDataUuid;
346     }
347 
348     /**
349      * Returns the manufacturer id. -1 if the manufacturer filter is not set.
350      */
getManufacturerId()351     public int getManufacturerId() {
352         return mManufacturerId;
353     }
354 
355     @Nullable
getManufacturerData()356     public byte[] getManufacturerData() {
357         return mManufacturerData;
358     }
359 
360     @Nullable
getManufacturerDataMask()361     public byte[] getManufacturerDataMask() {
362         return mManufacturerDataMask;
363     }
364 
365     /**
366      * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
367      * if it matches all the field filters.
368      */
matches(ScanResult scanResult)369     public boolean matches(ScanResult scanResult) {
370         if (scanResult == null) {
371             return false;
372         }
373         BluetoothDevice device = scanResult.getDevice();
374         // Device match.
375         if (mDeviceAddress != null
376                 && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
377             return false;
378         }
379 
380         ScanRecord scanRecord = scanResult.getScanRecord();
381 
382         // Scan record is null but there exist filters on it.
383         if (scanRecord == null
384                 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
385                 || mServiceData != null || mServiceSolicitationUuid != null)) {
386             return false;
387         }
388 
389         // Local name match.
390         if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
391             return false;
392         }
393 
394         // UUID match.
395         if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
396                 scanRecord.getServiceUuids())) {
397             return false;
398         }
399 
400         // solicitation UUID match.
401         if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids(
402                 mServiceSolicitationUuid, mServiceSolicitationUuidMask,
403                 scanRecord.getServiceSolicitationUuids())) {
404             return false;
405         }
406 
407         // Service data match
408         if (mServiceDataUuid != null) {
409             if (!matchesPartialData(mServiceData, mServiceDataMask,
410                     scanRecord.getServiceData(mServiceDataUuid))) {
411                 return false;
412             }
413         }
414 
415         // Manufacturer data match.
416         if (mManufacturerId >= 0) {
417             if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
418                     scanRecord.getManufacturerSpecificData(mManufacturerId))) {
419                 return false;
420             }
421         }
422         // All filters match.
423         return true;
424     }
425 
426     /**
427      * Check if the uuid pattern is contained in a list of parcel uuids.
428      *
429      * @hide
430      */
matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)431     public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
432             List<ParcelUuid> uuids) {
433         if (uuid == null) {
434             return true;
435         }
436         if (uuids == null) {
437             return false;
438         }
439 
440         for (ParcelUuid parcelUuid : uuids) {
441             UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
442             if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
443                 return true;
444             }
445         }
446         return false;
447     }
448 
449     // Check if the uuid pattern matches the particular service uuid.
matchesServiceUuid(UUID uuid, UUID mask, UUID data)450     private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
451         return BitUtils.maskedEquals(data, uuid, mask);
452     }
453 
454     /**
455      * Check if the solicitation uuid pattern is contained in a list of parcel uuids.
456      *
457      */
matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)458     private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid,
459             ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) {
460         if (solicitationUuid == null) {
461             return true;
462         }
463         if (solicitationUuids == null) {
464             return false;
465         }
466 
467         for (ParcelUuid parcelSolicitationUuid : solicitationUuids) {
468             UUID solicitationUuidMask = parcelSolicitationUuidMask == null
469                     ? null : parcelSolicitationUuidMask.getUuid();
470             if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask,
471                     parcelSolicitationUuid.getUuid())) {
472                 return true;
473             }
474         }
475         return false;
476     }
477 
478     // Check if the solicitation uuid pattern matches the particular service solicitation uuid.
matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)479     private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid,
480             UUID solicitationUuidMask, UUID data) {
481         return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask);
482     }
483 
484     // Check whether the data pattern matches the parsed data.
matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)485     private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
486         if (parsedData == null || parsedData.length < data.length) {
487             return false;
488         }
489         if (dataMask == null) {
490             for (int i = 0; i < data.length; ++i) {
491                 if (parsedData[i] != data[i]) {
492                     return false;
493                 }
494             }
495             return true;
496         }
497         for (int i = 0; i < data.length; ++i) {
498             if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
499                 return false;
500             }
501         }
502         return true;
503     }
504 
505     @Override
toString()506     public String toString() {
507         return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
508                 + mDeviceAddress
509                 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
510                 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid
511                 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask
512                 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
513                 + Arrays.toString(mServiceData) + ", mServiceDataMask="
514                 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
515                 + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
516                 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
517     }
518 
519     @Override
hashCode()520     public int hashCode() {
521         return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId,
522                 Arrays.hashCode(mManufacturerData),
523                 Arrays.hashCode(mManufacturerDataMask),
524                 mServiceDataUuid,
525                 Arrays.hashCode(mServiceData),
526                 Arrays.hashCode(mServiceDataMask),
527                 mServiceUuid, mServiceUuidMask,
528                 mServiceSolicitationUuid, mServiceSolicitationUuidMask);
529     }
530 
531     @Override
equals(@ullable Object obj)532     public boolean equals(@Nullable Object obj) {
533         if (this == obj) {
534             return true;
535         }
536         if (obj == null || getClass() != obj.getClass()) {
537             return false;
538         }
539         ScanFilter other = (ScanFilter) obj;
540         return Objects.equals(mDeviceName, other.mDeviceName)
541                 && Objects.equals(mDeviceAddress, other.mDeviceAddress)
542                 && mManufacturerId == other.mManufacturerId
543                 && Objects.deepEquals(mManufacturerData, other.mManufacturerData)
544                 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask)
545                 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid)
546                 && Objects.deepEquals(mServiceData, other.mServiceData)
547                 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask)
548                 && Objects.equals(mServiceUuid, other.mServiceUuid)
549                 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask)
550                 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid)
551                 && Objects.equals(mServiceSolicitationUuidMask,
552                         other.mServiceSolicitationUuidMask);
553     }
554 
555     /**
556      * Checks if the scanfilter is empty
557      *
558      * @hide
559      */
isAllFieldsEmpty()560     public boolean isAllFieldsEmpty() {
561         return EMPTY.equals(this);
562     }
563 
564     /**
565      * Builder class for {@link ScanFilter}.
566      */
567     public static final class Builder {
568 
569         /**
570          * @hide
571          */
572         @SystemApi
573         public static final int LEN_IRK_OCTETS = 16;
574 
575         private String mDeviceName;
576         private String mDeviceAddress;
577         private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC;
578         private byte[] mIrk;
579 
580         private ParcelUuid mServiceUuid;
581         private ParcelUuid mUuidMask;
582 
583         private ParcelUuid mServiceSolicitationUuid;
584         private ParcelUuid mServiceSolicitationUuidMask;
585 
586         private ParcelUuid mServiceDataUuid;
587         private byte[] mServiceData;
588         private byte[] mServiceDataMask;
589 
590         private int mManufacturerId = -1;
591         private byte[] mManufacturerData;
592         private byte[] mManufacturerDataMask;
593 
594         /**
595          * Set filter on device name.
596          */
setDeviceName(String deviceName)597         public Builder setDeviceName(String deviceName) {
598             mDeviceName = deviceName;
599             return this;
600         }
601 
602         /**
603          * Set filter on device address.
604          *
605          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
606          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
607          * BluetoothAdapter#checkBluetoothAddress}.  The @AddressType is defaulted to {@link
608          * BluetoothDevice#ADDRESS_TYPE_PUBLIC}
609          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
610          */
setDeviceAddress(String deviceAddress)611         public Builder setDeviceAddress(String deviceAddress) {
612             if (deviceAddress == null) {
613                 mDeviceAddress = deviceAddress;
614                 return this;
615             }
616             return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC);
617         }
618 
619         /**
620          * Set filter on Address with AddressType
621          *
622          * <p>This key is used to resolve a private address from a public address.
623          *
624          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
625          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
626          * BluetoothAdapter#checkBluetoothAddress}. May be any type of address.
627          * @param addressType indication of the type of address
628          * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
629          * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
630          *
631          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
632          * @throws IllegalArgumentException If the {@code addressType} is invalid length
633          * @throws NullPointerException if {@code deviceAddress} is null.
634          *
635          * @hide
636          */
637         @NonNull
638         @SystemApi
setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType)639         public Builder setDeviceAddress(@NonNull String deviceAddress,
640                                         @AddressType int addressType) {
641             return setDeviceAddressInternal(deviceAddress, addressType, null);
642         }
643 
644         /**
645          * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
646          *
647          * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from
648          * a PRIVATE_ADDRESS type.
649          *
650          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
651          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
652          * BluetoothAdapter#checkBluetoothAddress}.  This Address type must only be PUBLIC OR RANDOM
653          * STATIC.
654          * @param addressType indication of the type of address
655          * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
656          * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM}
657          * @param irk non-null byte array representing the Identity Resolving Key
658          *
659          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
660          * @throws IllegalArgumentException if the {@code irk} is invalid length.
661          * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not
662          * PUBLIC or RANDOM STATIC when an IRK is present.
663          * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null.
664          *
665          * @hide
666          */
667         @NonNull
668         @SystemApi
setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType, @NonNull byte[] irk)669         public Builder setDeviceAddress(@NonNull String deviceAddress,
670                                         @AddressType int addressType,
671                                         @NonNull byte[] irk) {
672             requireNonNull(irk);
673             if (irk.length != LEN_IRK_OCTETS) {
674                 throw new IllegalArgumentException("'irk' is invalid length!");
675             }
676             return setDeviceAddressInternal(deviceAddress, addressType, irk);
677         }
678 
679         /**
680          * Set filter on Address with AddressType and the Identity Resolving Key (IRK).
681          *
682          * <p>Internal setter for the device address
683          *
684          * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
685          * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link
686          * BluetoothAdapter#checkBluetoothAddress}.
687          * @param addressType indication of the type of address
688          * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}
689          * @param irk non-null byte array representing the Identity Resolving Address; nullable
690          * internally.
691          *
692          * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
693          * @throws IllegalArgumentException If the {@code addressType} is invalid length.
694          * @throws NullPointerException if {@code deviceAddress} is null.
695          *
696          * @hide
697          */
698         @NonNull
setDeviceAddressInternal(@onNull String deviceAddress, @AddressType int addressType, @Nullable byte[] irk)699         private Builder setDeviceAddressInternal(@NonNull String deviceAddress,
700                                                  @AddressType int addressType,
701                                                  @Nullable byte[] irk) {
702 
703             // Make sure our deviceAddress is valid!
704             requireNonNull(deviceAddress);
705             if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
706                 throw new IllegalArgumentException("invalid device address " + deviceAddress);
707             }
708 
709             // Verify type range
710             if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC
711                 || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) {
712                 throw new IllegalArgumentException("'addressType' is invalid!");
713             }
714 
715             // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address.
716             if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) {
717                 // Don't want a bad combination of address and irk!
718                 if (irk != null) {
719                     // Since there are 3 possible RANDOM subtypes we must check to make sure
720                     // the correct type of address is used.
721                     if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) {
722                         throw new IllegalArgumentException(
723                                 "Invalid combination: IRK requires either a PUBLIC or "
724                                 + "RANDOM (STATIC) Address");
725                     }
726                 }
727             }
728 
729             // PUBLIC doesn't require extra work
730             // Without an IRK any address may be accepted
731 
732             mDeviceAddress = deviceAddress;
733             mAddressType = addressType;
734             mIrk = irk;
735             return this;
736         }
737 
738         /**
739          * Set filter on service uuid.
740          */
setServiceUuid(ParcelUuid serviceUuid)741         public Builder setServiceUuid(ParcelUuid serviceUuid) {
742             mServiceUuid = serviceUuid;
743             mUuidMask = null; // clear uuid mask
744             return this;
745         }
746 
747         /**
748          * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
749          * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
750          * bit in {@code serviceUuid}, and 0 to ignore that bit.
751          *
752          * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code
753          * uuidMask} is not {@code null}.
754          */
setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)755         public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
756             if (mUuidMask != null && mServiceUuid == null) {
757                 throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
758             }
759             mServiceUuid = serviceUuid;
760             mUuidMask = uuidMask;
761             return this;
762         }
763 
764 
765         /**
766          * Set filter on service solicitation uuid.
767          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)768         public @NonNull Builder setServiceSolicitationUuid(
769                 @Nullable ParcelUuid serviceSolicitationUuid) {
770             mServiceSolicitationUuid = serviceSolicitationUuid;
771             if (serviceSolicitationUuid == null) {
772                 mServiceSolicitationUuidMask = null;
773             }
774             return this;
775         }
776 
777 
778         /**
779          * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the
780          * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to
781          * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to
782          * ignore that bit.
783          *
784          * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null.
785          * @param solicitationUuidMask can be null or a mask with no restriction.
786          *
787          * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but
788          *             {@code serviceSolicitationUuidMask} is not {@code null}.
789          */
setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)790         public @NonNull Builder setServiceSolicitationUuid(
791                 @Nullable ParcelUuid serviceSolicitationUuid,
792                 @Nullable ParcelUuid solicitationUuidMask) {
793             if (solicitationUuidMask != null && serviceSolicitationUuid == null) {
794                 throw new IllegalArgumentException(
795                         "SolicitationUuid is null while SolicitationUuidMask is not null!");
796             }
797             mServiceSolicitationUuid = serviceSolicitationUuid;
798             mServiceSolicitationUuidMask = solicitationUuidMask;
799             return this;
800         }
801 
802         /**
803          * Set filtering on service data.
804          *
805          * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
806          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)807         public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
808             if (serviceDataUuid == null) {
809                 throw new IllegalArgumentException("serviceDataUuid is null");
810             }
811             mServiceDataUuid = serviceDataUuid;
812             mServiceData = serviceData;
813             mServiceDataMask = null; // clear service data mask
814             return this;
815         }
816 
817         /**
818          * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
819          * match the one in service data, otherwise set it to 0 to ignore that bit.
820          * <p>
821          * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
822          *
823          * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code
824          * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code
825          * serviceDataMask} and {@code serviceData} has different length.
826          */
setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)827         public Builder setServiceData(ParcelUuid serviceDataUuid,
828                 byte[] serviceData, byte[] serviceDataMask) {
829             if (serviceDataUuid == null) {
830                 throw new IllegalArgumentException("serviceDataUuid is null");
831             }
832             if (mServiceDataMask != null) {
833                 if (mServiceData == null) {
834                     throw new IllegalArgumentException(
835                             "serviceData is null while serviceDataMask is not null");
836                 }
837                 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
838                 // byte array need to be the same.
839                 if (mServiceData.length != mServiceDataMask.length) {
840                     throw new IllegalArgumentException(
841                             "size mismatch for service data and service data mask");
842                 }
843             }
844             mServiceDataUuid = serviceDataUuid;
845             mServiceData = serviceData;
846             mServiceDataMask = serviceDataMask;
847             return this;
848         }
849 
850         /**
851          * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
852          *
853          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
854          */
setManufacturerData(int manufacturerId, byte[] manufacturerData)855         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
856             if (manufacturerData != null && manufacturerId < 0) {
857                 throw new IllegalArgumentException("invalid manufacture id");
858             }
859             mManufacturerId = manufacturerId;
860             mManufacturerData = manufacturerData;
861             mManufacturerDataMask = null; // clear manufacturer data mask
862             return this;
863         }
864 
865         /**
866          * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
867          * to match the one in manufacturer data, otherwise set it to 0.
868          * <p>
869          * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
870          *
871          * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code
872          * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code
873          * manufacturerData} and {@code manufacturerDataMask} have different length.
874          */
setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)875         public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
876                 byte[] manufacturerDataMask) {
877             if (manufacturerData != null && manufacturerId < 0) {
878                 throw new IllegalArgumentException("invalid manufacture id");
879             }
880             if (mManufacturerDataMask != null) {
881                 if (mManufacturerData == null) {
882                     throw new IllegalArgumentException(
883                             "manufacturerData is null while manufacturerDataMask is not null");
884                 }
885                 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
886                 // of the two byte array need to be the same.
887                 if (mManufacturerData.length != mManufacturerDataMask.length) {
888                     throw new IllegalArgumentException(
889                             "size mismatch for manufacturerData and manufacturerDataMask");
890                 }
891             }
892             mManufacturerId = manufacturerId;
893             mManufacturerData = manufacturerData;
894             mManufacturerDataMask = manufacturerDataMask;
895             return this;
896         }
897 
898         /**
899          * Build {@link ScanFilter}.
900          *
901          * @throws IllegalArgumentException If the filter cannot be built.
902          */
build()903         public ScanFilter build() {
904             return new ScanFilter(mDeviceName, mDeviceAddress,
905                     mServiceUuid, mUuidMask, mServiceSolicitationUuid,
906                     mServiceSolicitationUuidMask,
907                     mServiceDataUuid, mServiceData, mServiceDataMask,
908                     mManufacturerId, mManufacturerData, mManufacturerDataMask,
909                     mAddressType, mIrk);
910         }
911     }
912 }
913