ScanFilter.java revision 0462468aaff1089f08d0859e4920f90e4378b66b
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     * @hide
143     */
144    public static final Creator<ScanFilter>
145            CREATOR = new Creator<ScanFilter>() {
146
147                    @Override
148                public ScanFilter[] newArray(int size) {
149                    return new ScanFilter[size];
150                }
151
152                    @Override
153                public ScanFilter createFromParcel(Parcel in) {
154                    Builder builder = new Builder();
155                    if (in.readInt() == 1) {
156                        builder.setDeviceName(in.readString());
157                    }
158                    if (in.readInt() == 1) {
159                        builder.setDeviceAddress(in.readString());
160                    }
161                    if (in.readInt() == 1) {
162                        ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader());
163                        builder.setServiceUuid(uuid);
164                        if (in.readInt() == 1) {
165                            ParcelUuid uuidMask = in.readParcelable(
166                                    ParcelUuid.class.getClassLoader());
167                            builder.setServiceUuid(uuid, uuidMask);
168                        }
169                    }
170                    if (in.readInt() == 1) {
171                        ParcelUuid servcieDataUuid =
172                                in.readParcelable(ParcelUuid.class.getClassLoader());
173                        if (in.readInt() == 1) {
174                            int serviceDataLength = in.readInt();
175                            byte[] serviceData = new byte[serviceDataLength];
176                            in.readByteArray(serviceData);
177                            if (in.readInt() == 0) {
178                                builder.setServiceData(servcieDataUuid, serviceData);
179                            } else {
180                                int serviceDataMaskLength = in.readInt();
181                                byte[] serviceDataMask = new byte[serviceDataMaskLength];
182                                in.readByteArray(serviceDataMask);
183                                builder.setServiceData(
184                                            servcieDataUuid, serviceData, serviceDataMask);
185                            }
186                        }
187                    }
188
189                    int manufacturerId = in.readInt();
190                    if (in.readInt() == 1) {
191                        int manufacturerDataLength = in.readInt();
192                        byte[] manufacturerData = new byte[manufacturerDataLength];
193                        in.readByteArray(manufacturerData);
194                        if (in.readInt() == 0) {
195                            builder.setManufacturerData(manufacturerId, manufacturerData);
196                        } else {
197                            int manufacturerDataMaskLength = in.readInt();
198                            byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength];
199                            in.readByteArray(manufacturerDataMask);
200                            builder.setManufacturerData(manufacturerId, manufacturerData,
201                                    manufacturerDataMask);
202                        }
203                    }
204
205                    return builder.build();
206                }
207            };
208
209    /**
210     * Returns the filter set the device name field of Bluetooth advertisement data.
211     */
212    @Nullable
213    public String getDeviceName() {
214        return mDeviceName;
215    }
216
217    /**
218     * Returns the filter set on the service uuid.
219     */
220    @Nullable
221    public ParcelUuid getServiceUuid() {
222        return mServiceUuid;
223    }
224
225    @Nullable
226    public ParcelUuid getServiceUuidMask() {
227        return mServiceUuidMask;
228    }
229
230    @Nullable
231    public String getDeviceAddress() {
232        return mDeviceAddress;
233    }
234
235    @Nullable
236    public byte[] getServiceData() {
237        return mServiceData;
238    }
239
240    @Nullable
241    public byte[] getServiceDataMask() {
242        return mServiceDataMask;
243    }
244
245    /**
246     * @hide
247     */
248    @Nullable
249    public ParcelUuid getServiceDataUuid() {
250        return mServiceDataUuid;
251    }
252
253    /**
254     * Returns the manufacturer id. -1 if the manufacturer filter is not set.
255     */
256    public int getManufacturerId() {
257        return mManufacturerId;
258    }
259
260    @Nullable
261    public byte[] getManufacturerData() {
262        return mManufacturerData;
263    }
264
265    @Nullable
266    public byte[] getManufacturerDataMask() {
267        return mManufacturerDataMask;
268    }
269
270    /**
271     * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match
272     * if it matches all the field filters.
273     */
274    public boolean matches(ScanResult scanResult) {
275        if (scanResult == null) {
276            return false;
277        }
278        BluetoothDevice device = scanResult.getDevice();
279        // Device match.
280        if (mDeviceAddress != null
281                && (device == null || !mDeviceAddress.equals(device.getAddress()))) {
282            return false;
283        }
284
285        ScanRecord scanRecord = scanResult.getScanRecord();
286
287        // Scan record is null but there exist filters on it.
288        if (scanRecord == null
289                && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null
290                        || mServiceData != null)) {
291            return false;
292        }
293
294        // Local name match.
295        if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) {
296            return false;
297        }
298
299        // UUID match.
300        if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask,
301                scanRecord.getServiceUuids())) {
302            return false;
303        }
304
305        // Service data match
306        if (mServiceData != null) {
307            if (!Objects.equals(mServiceDataUuid, scanRecord.getServiceDataUuid()) ||
308                    !matchesPartialData(mServiceData, mServiceDataMask,
309                            scanRecord.getServiceData())) {
310                return false;
311            }
312        }
313
314        // Manufacturer data match.
315        if (mManufacturerData != null) {
316            if (mManufacturerId != scanRecord.getManufacturerId() ||
317                    !matchesPartialData(mManufacturerData,
318                            mManufacturerDataMask, scanRecord.getManufacturerSpecificData())) {
319                return false;
320            }
321        }
322        // All filters match.
323        return true;
324    }
325
326    // Check if the uuid pattern is contained in a list of parcel uuids.
327    private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask,
328            List<ParcelUuid> uuids) {
329        if (uuid == null) {
330            return true;
331        }
332        if (uuids == null) {
333            return false;
334        }
335
336        for (ParcelUuid parcelUuid : uuids) {
337            UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid();
338            if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) {
339                return true;
340            }
341        }
342        return false;
343    }
344
345    // Check if the uuid pattern matches the particular service uuid.
346    private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) {
347        if (mask == null) {
348            return uuid.equals(data);
349        }
350        if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) !=
351                (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) {
352            return false;
353        }
354        return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) ==
355                (data.getMostSignificantBits() & mask.getMostSignificantBits()));
356    }
357
358    // Check whether the data pattern matches the parsed data.
359    private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) {
360        if (parsedData == null || parsedData.length < data.length) {
361            return false;
362        }
363        if (dataMask == null) {
364            for (int i = 0; i < data.length; ++i) {
365                if (parsedData[i] != data[i]) {
366                    return false;
367                }
368            }
369            return true;
370        }
371        for (int i = 0; i < data.length; ++i) {
372            if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) {
373                return false;
374            }
375        }
376        return true;
377    }
378
379    @Override
380    public String toString() {
381        return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress="
382                + mDeviceAddress
383                + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask
384                + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData="
385                + Arrays.toString(mServiceData) + ", mServiceDataMask="
386                + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId
387                + ", mManufacturerData=" + Arrays.toString(mManufacturerData)
388                + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]";
389    }
390
391    @Override
392    public int hashCode() {
393        return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, mManufacturerData,
394                mManufacturerDataMask, mServiceDataUuid, mServiceData, mServiceDataMask,
395                mServiceUuid, mServiceUuidMask);
396    }
397
398    @Override
399    public boolean equals(Object obj) {
400        if (this == obj) {
401            return true;
402        }
403        if (obj == null || getClass() != obj.getClass()) {
404            return false;
405        }
406        ScanFilter other = (ScanFilter) obj;
407        return Objects.equals(mDeviceName, other.mDeviceName) &&
408                Objects.equals(mDeviceAddress, other.mDeviceAddress) &&
409                        mManufacturerId == other.mManufacturerId &&
410                Objects.deepEquals(mManufacturerData, other.mManufacturerData) &&
411                Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) &&
412                Objects.deepEquals(mServiceDataUuid, other.mServiceDataUuid) &&
413                Objects.deepEquals(mServiceData, other.mServiceData) &&
414                Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) &&
415                Objects.equals(mServiceUuid, other.mServiceUuid) &&
416                Objects.equals(mServiceUuidMask, other.mServiceUuidMask);
417    }
418
419    /**
420     * Builder class for {@link ScanFilter}.
421     */
422    public static final class Builder {
423
424        private String mDeviceName;
425        private String mDeviceAddress;
426
427        private ParcelUuid mServiceUuid;
428        private ParcelUuid mUuidMask;
429
430        private ParcelUuid mServiceDataUuid;
431        private byte[] mServiceData;
432        private byte[] mServiceDataMask;
433
434        private int mManufacturerId = -1;
435        private byte[] mManufacturerData;
436        private byte[] mManufacturerDataMask;
437
438        /**
439         * Set filter on device name.
440         */
441        public Builder setDeviceName(String deviceName) {
442            mDeviceName = deviceName;
443            return this;
444        }
445
446        /**
447         * Set filter on device address.
448         *
449         * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the
450         *            format of "01:02:03:AB:CD:EF". The device address can be validated using
451         *            {@link BluetoothAdapter#checkBluetoothAddress}.
452         * @throws IllegalArgumentException If the {@code deviceAddress} is invalid.
453         */
454        public Builder setDeviceAddress(String deviceAddress) {
455            if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) {
456                throw new IllegalArgumentException("invalid device address " + deviceAddress);
457            }
458            mDeviceAddress = deviceAddress;
459            return this;
460        }
461
462        /**
463         * Set filter on service uuid.
464         */
465        public Builder setServiceUuid(ParcelUuid serviceUuid) {
466            mServiceUuid = serviceUuid;
467            mUuidMask = null; // clear uuid mask
468            return this;
469        }
470
471        /**
472         * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the
473         * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the
474         * bit in {@code serviceUuid}, and 0 to ignore that bit.
475         *
476         * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but
477         *             {@code uuidMask} is not {@code null}.
478         */
479        public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) {
480            if (mUuidMask != null && mServiceUuid == null) {
481                throw new IllegalArgumentException("uuid is null while uuidMask is not null!");
482            }
483            mServiceUuid = serviceUuid;
484            mUuidMask = uuidMask;
485            return this;
486        }
487
488        /**
489         * Set filtering on service data.
490         *
491         * @throws IllegalArgumentException If {@code serviceDataUuid} is null.
492         */
493        public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) {
494            if (serviceDataUuid == null) {
495                throw new IllegalArgumentException("serviceDataUuid is null");
496            }
497            mServiceDataUuid = serviceDataUuid;
498            mServiceData = serviceData;
499            mServiceDataMask = null; // clear service data mask
500            return this;
501        }
502
503        /**
504         * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to
505         * match the one in service data, otherwise set it to 0 to ignore that bit.
506         * <p>
507         * The {@code serviceDataMask} must have the same length of the {@code serviceData}.
508         *
509         * @throws IllegalArgumentException If {@code serviceDataUuid} is null or
510         *             {@code serviceDataMask} is {@code null} while {@code serviceData} is not or
511         *             {@code serviceDataMask} and {@code serviceData} has different length.
512         */
513        public Builder setServiceData(ParcelUuid serviceDataUuid,
514                byte[] serviceData, byte[] serviceDataMask) {
515            if (serviceDataUuid == null) {
516                throw new IllegalArgumentException("serviceDataUuid is null");
517            }
518            if (mServiceDataMask != null) {
519                if (mServiceData == null) {
520                    throw new IllegalArgumentException(
521                            "serviceData is null while serviceDataMask is not null");
522                }
523                // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two
524                // byte array need to be the same.
525                if (mServiceData.length != mServiceDataMask.length) {
526                    throw new IllegalArgumentException(
527                            "size mismatch for service data and service data mask");
528                }
529            }
530            mServiceDataUuid = serviceDataUuid;
531            mServiceData = serviceData;
532            mServiceDataMask = serviceDataMask;
533            return this;
534        }
535
536        /**
537         * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id.
538         * <p>
539         * Note the first two bytes of the {@code manufacturerData} is the manufacturerId.
540         *
541         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid.
542         */
543        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) {
544            if (manufacturerData != null && manufacturerId < 0) {
545                throw new IllegalArgumentException("invalid manufacture id");
546            }
547            mManufacturerId = manufacturerId;
548            mManufacturerData = manufacturerData;
549            mManufacturerDataMask = null; // clear manufacturer data mask
550            return this;
551        }
552
553        /**
554         * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs
555         * to match the one in manufacturer data, otherwise set it to 0.
556         * <p>
557         * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}.
558         *
559         * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or
560         *             {@code manufacturerData} is null while {@code manufacturerDataMask} is not,
561         *             or {@code manufacturerData} and {@code manufacturerDataMask} have different
562         *             length.
563         */
564        public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData,
565                byte[] manufacturerDataMask) {
566            if (manufacturerData != null && manufacturerId < 0) {
567                throw new IllegalArgumentException("invalid manufacture id");
568            }
569            if (mManufacturerDataMask != null) {
570                if (mManufacturerData == null) {
571                    throw new IllegalArgumentException(
572                            "manufacturerData is null while manufacturerDataMask is not null");
573                }
574                // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths
575                // of the two byte array need to be the same.
576                if (mManufacturerData.length != mManufacturerDataMask.length) {
577                    throw new IllegalArgumentException(
578                            "size mismatch for manufacturerData and manufacturerDataMask");
579                }
580            }
581            mManufacturerId = manufacturerId;
582            mManufacturerData = manufacturerData;
583            mManufacturerDataMask = manufacturerDataMask;
584            return this;
585        }
586
587        /**
588         * Build {@link ScanFilter}.
589         *
590         * @throws IllegalArgumentException If the filter cannot be built.
591         */
592        public ScanFilter build() {
593            return new ScanFilter(mDeviceName, mDeviceAddress,
594                    mServiceUuid, mUuidMask,
595                    mServiceDataUuid, mServiceData, mServiceDataMask,
596                    mManufacturerId, mManufacturerData, mManufacturerDataMask);
597        }
598    }
599}
600