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