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 private static final ScanFilter EMPTY = new ScanFilter.Builder().build() ; 71 72 73 private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, 74 ParcelUuid uuidMask, ParcelUuid serviceDataUuid, 75 byte[] serviceData, byte[] serviceDataMask, 76 int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) { 77 mDeviceName = name; 78 mServiceUuid = uuid; 79 mServiceUuidMask = uuidMask; 80 mDeviceAddress = deviceAddress; 81 mServiceDataUuid = serviceDataUuid; 82 mServiceData = serviceData; 83 mServiceDataMask = serviceDataMask; 84 mManufacturerId = manufacturerId; 85 mManufacturerData = manufacturerData; 86 mManufacturerDataMask = manufacturerDataMask; 87 } 88 89 @Override 90 public int describeContents() { 91 return 0; 92 } 93 94 @Override 95 public void writeToParcel(Parcel dest, int flags) { 96 dest.writeInt(mDeviceName == null ? 0 : 1); 97 if (mDeviceName != null) { 98 dest.writeString(mDeviceName); 99 } 100 dest.writeInt(mDeviceAddress == null ? 0 : 1); 101 if (mDeviceAddress != null) { 102 dest.writeString(mDeviceAddress); 103 } 104 dest.writeInt(mServiceUuid == null ? 0 : 1); 105 if (mServiceUuid != null) { 106 dest.writeParcelable(mServiceUuid, flags); 107 dest.writeInt(mServiceUuidMask == null ? 0 : 1); 108 if (mServiceUuidMask != null) { 109 dest.writeParcelable(mServiceUuidMask, flags); 110 } 111 } 112 dest.writeInt(mServiceDataUuid == null ? 0 : 1); 113 if (mServiceDataUuid != null) { 114 dest.writeParcelable(mServiceDataUuid, flags); 115 dest.writeInt(mServiceData == null ? 0 : 1); 116 if (mServiceData != null) { 117 dest.writeInt(mServiceData.length); 118 dest.writeByteArray(mServiceData); 119 120 dest.writeInt(mServiceDataMask == null ? 0 : 1); 121 if (mServiceDataMask != null) { 122 dest.writeInt(mServiceDataMask.length); 123 dest.writeByteArray(mServiceDataMask); 124 } 125 } 126 } 127 dest.writeInt(mManufacturerId); 128 dest.writeInt(mManufacturerData == null ? 0 : 1); 129 if (mManufacturerData != null) { 130 dest.writeInt(mManufacturerData.length); 131 dest.writeByteArray(mManufacturerData); 132 133 dest.writeInt(mManufacturerDataMask == null ? 0 : 1); 134 if (mManufacturerDataMask != null) { 135 dest.writeInt(mManufacturerDataMask.length); 136 dest.writeByteArray(mManufacturerDataMask); 137 } 138 } 139 } 140 141 /** 142 * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. 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 @Nullable 246 public ParcelUuid getServiceDataUuid() { 247 return mServiceDataUuid; 248 } 249 250 /** 251 * Returns the manufacturer id. -1 if the manufacturer filter is not set. 252 */ 253 public int getManufacturerId() { 254 return mManufacturerId; 255 } 256 257 @Nullable 258 public byte[] getManufacturerData() { 259 return mManufacturerData; 260 } 261 262 @Nullable 263 public byte[] getManufacturerDataMask() { 264 return mManufacturerDataMask; 265 } 266 267 /** 268 * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match 269 * if it matches all the field filters. 270 */ 271 public boolean matches(ScanResult scanResult) { 272 if (scanResult == null) { 273 return false; 274 } 275 BluetoothDevice device = scanResult.getDevice(); 276 // Device match. 277 if (mDeviceAddress != null 278 && (device == null || !mDeviceAddress.equals(device.getAddress()))) { 279 return false; 280 } 281 282 ScanRecord scanRecord = scanResult.getScanRecord(); 283 284 // Scan record is null but there exist filters on it. 285 if (scanRecord == null 286 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null 287 || mServiceData != null)) { 288 return false; 289 } 290 291 // Local name match. 292 if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { 293 return false; 294 } 295 296 // UUID match. 297 if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, 298 scanRecord.getServiceUuids())) { 299 return false; 300 } 301 302 // Service data match 303 if (mServiceDataUuid != null) { 304 if (!matchesPartialData(mServiceData, mServiceDataMask, 305 scanRecord.getServiceData(mServiceDataUuid))) { 306 return false; 307 } 308 } 309 310 // Manufacturer data match. 311 if (mManufacturerId >= 0) { 312 if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, 313 scanRecord.getManufacturerSpecificData(mManufacturerId))) { 314 return false; 315 } 316 } 317 // All filters match. 318 return true; 319 } 320 321 // Check if the uuid pattern is contained in a list of parcel uuids. 322 private boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, 323 List<ParcelUuid> uuids) { 324 if (uuid == null) { 325 return true; 326 } 327 if (uuids == null) { 328 return false; 329 } 330 331 for (ParcelUuid parcelUuid : uuids) { 332 UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); 333 if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { 334 return true; 335 } 336 } 337 return false; 338 } 339 340 // Check if the uuid pattern matches the particular service uuid. 341 private boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { 342 if (mask == null) { 343 return uuid.equals(data); 344 } 345 if ((uuid.getLeastSignificantBits() & mask.getLeastSignificantBits()) != 346 (data.getLeastSignificantBits() & mask.getLeastSignificantBits())) { 347 return false; 348 } 349 return ((uuid.getMostSignificantBits() & mask.getMostSignificantBits()) == 350 (data.getMostSignificantBits() & mask.getMostSignificantBits())); 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