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