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