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
17package android.bluetooth.le;
18
19import android.annotation.Nullable;
20import android.bluetooth.BluetoothAdapter;
21import android.bluetooth.BluetoothDevice;
22import android.os.Parcel;
23import android.os.ParcelUuid;
24import android.os.Parcelable;
25
26import java.util.Arrays;
27import java.util.List;
28import java.util.Objects;
29import java.util.UUID;
30
31/**
32 * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to
33 * restrict scan results to only those that are of interest to them.
34 * <p>
35 * Current filtering on the following fields are supported:
36 * <li>Service UUIDs which identify the bluetooth gatt services running on the device.
37 * <li>Name of remote Bluetooth LE device.
38 * <li>Mac address of the remote device.
39 * <li>Service data which is the data associated with a service.
40 * <li>Manufacturer specific data which is the data associated with a particular manufacturer.
41 *
42 * @see ScanResult
43 * @see BluetoothLeScanner
44 */
45public final class ScanFilter implements Parcelable {
46
47    @Nullable
48    private final String mDeviceName;
49
50    @Nullable
51    private final String mDeviceAddress;
52
53    @Nullable
54    private final ParcelUuid mServiceUuid;
55    @Nullable
56    private final ParcelUuid mServiceUuidMask;
57
58    @Nullable
59    private final ParcelUuid mServiceDataUuid;
60    @Nullable
61    private final byte[] mServiceData;
62    @Nullable
63    private final byte[] mServiceDataMask;
64
65    private final int mManufacturerId;
66    @Nullable
67    private final byte[] mManufacturerData;
68    @Nullable
69    private final byte[] mManufacturerDataMask;
70
71    private ScanFilter(String name, String deviceAddress, ParcelUuid uuid,
72            ParcelUuid uuidMask, ParcelUuid serviceDataUuid,
73            byte[] serviceData, byte[] serviceDataMask,
74            int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) {
75        mDeviceName = name;
76        mServiceUuid = uuid;
77        mServiceUuidMask = uuidMask;
78        mDeviceAddress = deviceAddress;
79        mServiceDataUuid = serviceDataUuid;
80        mServiceData = serviceData;
81        mServiceDataMask = serviceDataMask;
82        mManufacturerId = manufacturerId;
83        mManufacturerData = manufacturerData;
84        mManufacturerDataMask = manufacturerDataMask;
85    }
86
87    @Override
88    public int describeContents() {
89        return 0;
90    }
91
92    @Override
93    public void writeToParcel(Parcel dest, int flags) {
94        dest.writeInt(mDeviceName == null ? 0 : 1);
95        if (mDeviceName != null) {
96            dest.writeString(mDeviceName);
97        }
98        dest.writeInt(mDeviceAddress == null ? 0 : 1);
99        if (mDeviceAddress != null) {
100            dest.writeString(mDeviceAddress);
101        }
102        dest.writeInt(mServiceUuid == null ? 0 : 1);
103        if (mServiceUuid != null) {
104            dest.writeParcelable(mServiceUuid, flags);
105            dest.writeInt(mServiceUuidMask == null ? 0 : 1);
106            if (mServiceUuidMask != null) {
107                dest.writeParcelable(mServiceUuidMask, flags);
108            }
109        }
110        dest.writeInt(mServiceDataUuid == null ? 0 : 1);
111        if (mServiceDataUuid != null) {
112            dest.writeParcelable(mServiceDataUuid, flags);
113            dest.writeInt(mServiceData == null ? 0 : 1);
114            if (mServiceData != null) {
115                dest.writeInt(mServiceData.length);
116                dest.writeByteArray(mServiceData);
117
118                dest.writeInt(mServiceDataMask == null ? 0 : 1);
119                if (mServiceDataMask != null) {
120                    dest.writeInt(mServiceDataMask.length);
121                    dest.writeByteArray(mServiceDataMask);
122                }
123            }
124        }
125        dest.writeInt(mManufacturerId);
126        dest.writeInt(mManufacturerData == null ? 0 : 1);
127        if (mManufacturerData != null) {
128            dest.writeInt(mManufacturerData.length);
129            dest.writeByteArray(mManufacturerData);
130
131            dest.writeInt(mManufacturerDataMask == null ? 0 : 1);
132            if (mManufacturerDataMask != null) {
133                dest.writeInt(mManufacturerDataMask.length);
134                dest.writeByteArray(mManufacturerDataMask);
135            }
136        }
137    }
138
139    /**
140     * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel.
141     */
142    public static final Creator<ScanFilter>
143            CREATOR = new Creator<ScanFilter>() {
144
145                    @Override
146                public ScanFilter[] newArray(int size) {
147                    return new ScanFilter[size];
148                }
149
150                    @Override
151                public ScanFilter createFromParcel(Parcel in) {
152                    Builder builder = new Builder();
153                    if (in.readInt() == 1) {
154                        builder.setDeviceName(in.readString());
155                    }
156                    if (in.readInt() == 1) {
157                        builder.setDeviceAddress(in.readString());
158                    }
159                    if (in.readInt() == 1) {
160                        ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
161                        builder.setServiceUuid(uuid);
162                        if (in.readInt() == 1) {
163                            ParcelUuid uuidMask = in.readParcelable(
164                                    ParcelUuid.class.getClassLoader());
165                            builder.setServiceUuid(uuid, uuidMask);
166                        }
167                    }
168                    if (in.readInt() == 1) {
169                        ParcelUuid servcieDataUuid =
170                                in.readParcelable(ParcelUuid.class.getClassLoader());
171                        if (in.readInt() == 1) {
172                            int serviceDataLength = in.readInt();
173                            byte[] serviceData = new byte[serviceDataLength];
174                            in.readByteArray(serviceData);
175                            if (in.readInt() == 0) {
176                                builder.setServiceData(servcieDataUuid, serviceData);
177                            } else {
178                                int serviceDataMaskLength = in.readInt();
179                                byte[] serviceDataMask = new byte[serviceDataMaskLength];
180                                in.readByteArray(serviceDataMask);
181                                builder.setServiceData(
182                                        servcieDataUuid, serviceData, serviceDataMask);
183                            }
184                        }
185                    }
186
187                    int manufacturerId = in.readInt();
188                    if (in.readInt() == 1) {
189                        int manufacturerDataLength = in.readInt();
190                        byte[] manufacturerData = new byte[manufacturerDataLength];
191                        in.readByteArray(manufacturerData);
192                        if (in.readInt() == 0) {
193                            builder.setManufacturerData(manufacturerId, manufacturerData);
194                        } else {
195                            int manufacturerDataMaskLength = in.readInt();
196                            byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
197                            in.readByteArray(manufacturerDataMask);
198                            builder.setManufacturerData(manufacturerId, manufacturerData,
199                                    manufacturerDataMask);
200                        }
201                    }
202
203                    return builder.build();
204                }
205            };
206
207    /**
208     * Returns the filter set the device name field of Bluetooth advertisement data.
209     */
210    @Nullable
211    public String getDeviceName() {
212        return mDeviceName;
213    }
214
215    /**
216     * Returns the filter set on the service uuid.
217     */
218    @Nullable
219    public ParcelUuid getServiceUuid() {
220        return mServiceUuid;
221    }
222
223    @Nullable
224    public ParcelUuid getServiceUuidMask() {
225        return mServiceUuidMask;
226    }
227
228    @Nullable
229    public String getDeviceAddress() {
230        return mDeviceAddress;
231    }
232
233    @Nullable
234    public byte[] getServiceData() {
235        return mServiceData;
236    }
237
238    @Nullable
239    public byte[] getServiceDataMask() {
240        return mServiceDataMask;
241    }
242
243    @Nullable
244    public ParcelUuid getServiceDataUuid() {
245        return mServiceDataUuid;
246    }
247
248    /**
249     * Returns the manufacturer id. -1 if the manufacturer filter is not set.
250     */
251    public int getManufacturerId() {
252        return mManufacturerId;
253    }
254
255    @Nullable
256    public byte[] getManufacturerData() {
257        return mManufacturerData;
258    }
259
260    @Nullable
261    public byte[] getManufacturerDataMask() {
262        return mManufacturerDataMask;
263    }
264
265    /**
266     * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
267     * if it matches all the field filters.
268     */
269    public boolean matches(ScanResult scanResult) {
270        if (scanResult == null) {
271            return false;
272        }
273        BluetoothDevice device = scanResult.getDevice();
274        // Device match.
275        if (mDeviceAddress != null
276                && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
277            return false;
278        }
279
280        ScanRecord scanRecord = scanResult.getScanRecord();
281
282        // Scan record is null but there exist filters on it.
283        if (scanRecord == null
284                && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
285                        || mServiceData != null)) {
286            return false;
287        }
288
289        // Local name match.
290        if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
291            return false;
292        }
293
294        // UUID match.
295        if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
296                scanRecord.getServiceUuids())) {
297            return false;
298        }
299
300        // Service data match
301        if (mServiceDataUuid != null) {
302            if (!matchesPartialData(mServiceData, mServiceDataMask,
303                    scanRecord.getServiceData(mServiceDataUuid))) {
304                return false;
305            }
306        }
307
308        // Manufacturer data match.
309        if (mManufacturerId >= 0) {
310            if (!matchesPartialData(mManufacturerData, mManufacturerDataMask,
311                    scanRecord.getManufacturerSpecificData(mManufacturerId))) {
312                return false;
313            }
314        }
315        // All filters match.
316        return true;
317    }
318
319    // Check if the uuid pattern is contained in a list of parcel uuids.
320    private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
321            List<ParcelUuid> uuids) {
322        if (uuid == null) {
323            return true;
324        }
325        if (uuids == null) {
326            return false;
327        }
328
329        for (ParcelUuid parcelUuid : uuids) {
330            UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
331            if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
332                return true;
333            }
334        }
335        return false;
336    }
337
338    // Check if the uuid pattern matches the particular service uuid.
339    private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
340        if (mask == null) {
341            return uuid.equals(data);
342        }
343        if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) !=
344                (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
345            return false;
346        }
347        return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) ==
348                (data.getMostSignificantBits() & mask.getMostSignificantBits()));
349    }
350
351    // Check whether the data pattern matches the parsed data.
352    private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
353        if (parsedData == null || parsedData.length < data.length) {
354            return false;
355        }
356        if (dataMask == null) {
357            for (int i = 0; i < data.length; ++i) {
358                if (parsedData[i] != data[i]) {
359                    return false;
360                }
361            }
362            return true;
363        }
364        for (int i = 0; i < data.length; ++i) {
365            if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
366                return false;
367            }
368        }
369        return true;
370    }
371
372    @Override
373    public String toString() {
374        return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
375                + mDeviceAddress
376                + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
377                + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
378                + Arrays.toString(mServiceData) + ", mServiceDataMask="
379                + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
380                + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
381                + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
382    }
383
384    @Override
385    public int hashCode() {
386        return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, mManufacturerData,
387                mManufacturerDataMask, mServiceDataUuid, mServiceData, mServiceDataMask,
388                mServiceUuid, mServiceUuidMask);
389    }
390
391    @Override
392    public boolean equals(Object obj) {
393        if (this == obj) {
394            return true;
395        }
396        if (obj == null || getClass() != obj.getClass()) {
397            return false;
398        }
399        ScanFilter other = (ScanFilter) obj;
400        return Objects.equals(mDeviceName, other.mDeviceName) &&
401                Objects.equals(mDeviceAddress, other.mDeviceAddress) &&
402                        mManufacturerId == other.mManufacturerId &&
403                Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
404                Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
405                Objects.deepEquals(mServiceDataUuid, other.mServiceDataUuid) &&
406                Objects.deepEquals(mServiceData, other.mServiceData) &&
407                Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) &&
408                Objects.equals(mServiceUuid, other.mServiceUuid) &&
409                Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
410    }
411
412    /**
413     * Builder class for {@link ScanFilter}.
414     */
415    public static final class Builder {
416
417        private String mDeviceName;
418        private String mDeviceAddress;
419
420        private ParcelUuid mServiceUuid;
421        private ParcelUuid mUuidMask;
422
423        private ParcelUuid mServiceDataUuid;
424        private byte[] mServiceData;
425        private byte[] mServiceDataMask;
426
427        private int mManufacturerId = -1;
428        private byte[] mManufacturerData;
429        private byte[] mManufacturerDataMask;
430
431        /**
432         * Set filter on device name.
433         */
434        public Builder setDeviceName(String deviceName) {
435            mDeviceName = deviceName;
436            return this;
437        }
438
439        /**
440         * Set filter on device address.
441         *
442         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
443         *            format of "01:02:03:AB:CD:EF". The device address can be validated using
444         *            {@link BluetoothAdapter#checkBluetoothAddress}.
445         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
446         */
447        public Builder setDeviceAddress(String deviceAddress) {
448            if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
449                throw new IllegalArgumentException("invalid device address " + deviceAddress);
450            }
451            mDeviceAddress = deviceAddress;
452            return this;
453        }
454
455        /**
456         * Set filter on service uuid.
457         */
458        public Builder setServiceUuid(ParcelUuid serviceUuid) {
459            mServiceUuid = serviceUuid;
460            mUuidMask = null; // clear uuid mask
461            return this;
462        }
463
464        /**
465         * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
466         * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
467         * bit in {@code serviceUuid}, and 0 to ignore that bit.
468         *
469         * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but
470         *             {@code uuidMask} is not {@code null}.
471         */
472        public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
473            if (mUuidMask != null && mServiceUuid == null) {
474                throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
475            }
476            mServiceUuid = serviceUuid;
477            mUuidMask = uuidMask;
478            return this;
479        }
480
481        /**
482         * Set filtering on service data.
483         *
484         * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
485         */
486        public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
487            if (serviceDataUuid == null) {
488                throw new IllegalArgumentException("serviceDataUuid is null");
489            }
490            mServiceDataUuid = serviceDataUuid;
491            mServiceData = serviceData;
492            mServiceDataMask = null; // clear service data mask
493            return this;
494        }
495
496        /**
497         * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
498         * match the one in service data, otherwise set it to 0 to ignore that bit.
499         * <p>
500         * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
501         *
502         * @throws IllegalArgumentException If {@code serviceDataUuid} is null or
503         *             {@code serviceDataMask} is {@code null} while {@code serviceData} is not or
504         *             {@code serviceDataMask} and {@code serviceData} has different length.
505         */
506        public Builder setServiceData(ParcelUuid serviceDataUuid,
507                byte[] serviceData, byte[] serviceDataMask) {
508            if (serviceDataUuid == null) {
509                throw new IllegalArgumentException("serviceDataUuid is null");
510            }
511            if (mServiceDataMask != null) {
512                if (mServiceData == null) {
513                    throw new IllegalArgumentException(
514                            "serviceData is null while serviceDataMask is not null");
515                }
516                // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
517                // byte array need to be the same.
518                if (mServiceData.length != mServiceDataMask.length) {
519                    throw new IllegalArgumentException(
520                            "size mismatch for service data and service data mask");
521                }
522            }
523            mServiceDataUuid = serviceDataUuid;
524            mServiceData = serviceData;
525            mServiceDataMask = serviceDataMask;
526            return this;
527        }
528
529        /**
530         * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
531         * <p>
532         * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
533         *
534         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
535         */
536        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
537            if (manufacturerData != null && manufacturerId < 0) {
538                throw new IllegalArgumentException("invalid manufacture id");
539            }
540            mManufacturerId = manufacturerId;
541            mManufacturerData = manufacturerData;
542            mManufacturerDataMask = null; // clear manufacturer data mask
543            return this;
544        }
545
546        /**
547         * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
548         * to match the one in manufacturer data, otherwise set it to 0.
549         * <p>
550         * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
551         *
552         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or
553         *             {@code manufacturerData} is null while {@code manufacturerDataMask} is not,
554         *             or {@code manufacturerData} and {@code manufacturerDataMask} have different
555         *             length.
556         */
557        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
558                byte[] manufacturerDataMask) {
559            if (manufacturerData != null && manufacturerId < 0) {
560                throw new IllegalArgumentException("invalid manufacture id");
561            }
562            if (mManufacturerDataMask != null) {
563                if (mManufacturerData == null) {
564                    throw new IllegalArgumentException(
565                            "manufacturerData is null while manufacturerDataMask is not null");
566                }
567                // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
568                // of the two byte array need to be the same.
569                if (mManufacturerData.length != mManufacturerDataMask.length) {
570                    throw new IllegalArgumentException(
571                            "size mismatch for manufacturerData and manufacturerDataMask");
572                }
573            }
574            mManufacturerId = manufacturerId;
575            mManufacturerData = manufacturerData;
576            mManufacturerDataMask = manufacturerDataMask;
577            return this;
578        }
579
580        /**
581         * Build {@link ScanFilter}.
582         *
583         * @throws IllegalArgumentException If the filter cannot be built.
584         */
585        public ScanFilter build() {
586            return new ScanFilter(mDeviceName, mDeviceAddress,
587                    mServiceUuid, mUuidMask,
588                    mServiceDataUuid, mServiceData, mServiceDataMask,
589                    mManufacturerId, mManufacturerData, mManufacturerDataMask);
590        }
591    }
592}
593