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 17 package android.bluetooth.le; 18 19 import static java.util.Objects.requireNonNull; 20 21 import android.annotation.NonNull; 22 import android.annotation.Nullable; 23 import android.annotation.SystemApi; 24 import android.bluetooth.BluetoothAdapter; 25 import android.bluetooth.BluetoothDevice; 26 import android.bluetooth.BluetoothDevice.AddressType; 27 import android.bluetooth.le.ScanRecord.AdvertisingDataType; 28 import android.os.Parcel; 29 import android.os.ParcelUuid; 30 import android.os.Parcelable; 31 32 import java.util.Arrays; 33 import java.util.List; 34 import java.util.Objects; 35 import java.util.UUID; 36 37 /** 38 * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to 39 * restrict scan results to only those that are of interest to them. 40 * <p> 41 * Current filtering on the following fields are supported: 42 * <li>Service UUIDs which identify the bluetooth gatt services running on the device. 43 * <li>Name of remote Bluetooth LE device. 44 * <li>Mac address of the remote device. 45 * <li>Service data which is the data associated with a service. 46 * <li>Manufacturer specific data which is the data associated with a particular manufacturer. 47 * <li>Advertising data type and corresponding data. 48 * 49 * @see ScanResult 50 * @see BluetoothLeScanner 51 */ 52 public final class ScanFilter implements Parcelable { 53 54 @Nullable 55 private final String mDeviceName; 56 57 @Nullable 58 private final String mDeviceAddress; 59 60 private final @AddressType int mAddressType; 61 62 @Nullable 63 private final byte[] mIrk; 64 65 @Nullable 66 private final ParcelUuid mServiceUuid; 67 @Nullable 68 private final ParcelUuid mServiceUuidMask; 69 70 @Nullable 71 private final ParcelUuid mServiceSolicitationUuid; 72 @Nullable 73 private final ParcelUuid mServiceSolicitationUuidMask; 74 75 @Nullable 76 private final ParcelUuid mServiceDataUuid; 77 @Nullable 78 private final byte[] mServiceData; 79 @Nullable 80 private final byte[] mServiceDataMask; 81 82 private final int mManufacturerId; 83 @Nullable 84 private final byte[] mManufacturerData; 85 @Nullable 86 private final byte[] mManufacturerDataMask; 87 88 private int mAdvertisingDataType = ScanRecord.DATA_TYPE_NONE; 89 @Nullable 90 private final byte[] mAdvertisingData; 91 @Nullable 92 private final byte[] mAdvertisingDataMask; 93 94 /** @hide */ 95 public static final ScanFilter EMPTY = new ScanFilter.Builder().build(); 96 ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask, ParcelUuid solicitationUuid, ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask, int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, @AddressType int addressType, @Nullable byte[] irk, int advertisingDataType, @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask)97 private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, ParcelUuid uuidMask, 98 ParcelUuid solicitationUuid, ParcelUuid solicitationUuidMask, 99 ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask, 100 int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, 101 @AddressType int addressType, @Nullable byte[] irk, int advertisingDataType, 102 @Nullable byte[] advertisingData, @Nullable byte[] advertisingDataMask) { 103 mDeviceName = name; 104 mServiceUuid = uuid; 105 mServiceUuidMask = uuidMask; 106 mServiceSolicitationUuid = solicitationUuid; 107 mServiceSolicitationUuidMask = solicitationUuidMask; 108 mDeviceAddress = deviceAddress; 109 mServiceDataUuid = serviceDataUuid; 110 mServiceData = serviceData; 111 mServiceDataMask = serviceDataMask; 112 mManufacturerId = manufacturerId; 113 mManufacturerData = manufacturerData; 114 mManufacturerDataMask = manufacturerDataMask; 115 mAddressType = addressType; 116 mIrk = irk; 117 mAdvertisingDataType = advertisingDataType; 118 mAdvertisingData = advertisingData; 119 mAdvertisingDataMask = advertisingDataMask; 120 } 121 122 @Override describeContents()123 public int describeContents() { 124 return 0; 125 } 126 127 @Override writeToParcel(Parcel dest, int flags)128 public void writeToParcel(Parcel dest, int flags) { 129 dest.writeInt(mDeviceName == null ? 0 : 1); 130 if (mDeviceName != null) { 131 dest.writeString(mDeviceName); 132 } 133 dest.writeInt(mDeviceAddress == null ? 0 : 1); 134 if (mDeviceAddress != null) { 135 dest.writeString(mDeviceAddress); 136 } 137 dest.writeInt(mServiceUuid == null ? 0 : 1); 138 if (mServiceUuid != null) { 139 dest.writeParcelable(mServiceUuid, flags); 140 dest.writeInt(mServiceUuidMask == null ? 0 : 1); 141 if (mServiceUuidMask != null) { 142 dest.writeParcelable(mServiceUuidMask, flags); 143 } 144 } 145 dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1); 146 if (mServiceSolicitationUuid != null) { 147 dest.writeParcelable(mServiceSolicitationUuid, flags); 148 dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1); 149 if (mServiceSolicitationUuidMask != null) { 150 dest.writeParcelable(mServiceSolicitationUuidMask, flags); 151 } 152 } 153 dest.writeInt(mServiceDataUuid == null ? 0 : 1); 154 if (mServiceDataUuid != null) { 155 dest.writeParcelable(mServiceDataUuid, flags); 156 dest.writeInt(mServiceData == null ? 0 : 1); 157 if (mServiceData != null) { 158 dest.writeInt(mServiceData.length); 159 dest.writeByteArray(mServiceData); 160 161 dest.writeInt(mServiceDataMask == null ? 0 : 1); 162 if (mServiceDataMask != null) { 163 dest.writeInt(mServiceDataMask.length); 164 dest.writeByteArray(mServiceDataMask); 165 } 166 } 167 } 168 dest.writeInt(mManufacturerId); 169 dest.writeInt(mManufacturerData == null ? 0 : 1); 170 if (mManufacturerData != null) { 171 dest.writeInt(mManufacturerData.length); 172 dest.writeByteArray(mManufacturerData); 173 174 dest.writeInt(mManufacturerDataMask == null ? 0 : 1); 175 if (mManufacturerDataMask != null) { 176 dest.writeInt(mManufacturerDataMask.length); 177 dest.writeByteArray(mManufacturerDataMask); 178 } 179 } 180 181 // IRK 182 if (mDeviceAddress != null) { 183 dest.writeInt(mAddressType); 184 dest.writeInt(mIrk == null ? 0 : 1); 185 if (mIrk != null) { 186 dest.writeByteArray(mIrk); 187 } 188 } 189 190 // Advertising data type filter 191 dest.writeInt(mAdvertisingDataType); 192 dest.writeInt(mAdvertisingData == null ? 0 : 1); 193 if (mAdvertisingData != null) { 194 dest.writeInt(mAdvertisingData.length); 195 dest.writeByteArray(mAdvertisingData); 196 197 dest.writeInt(mAdvertisingDataMask == null ? 0 : 1); 198 if (mAdvertisingDataMask != null) { 199 dest.writeInt(mAdvertisingDataMask.length); 200 dest.writeByteArray(mAdvertisingDataMask); 201 } 202 } 203 } 204 205 /** 206 * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. 207 */ 208 public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR = 209 new Creator<ScanFilter>() { 210 211 @Override 212 public ScanFilter[] newArray(int size) { 213 return new ScanFilter[size]; 214 } 215 216 @Override 217 public ScanFilter createFromParcel(Parcel in) { 218 Builder builder = new Builder(); 219 if (in.readInt() == 1) { 220 builder.setDeviceName(in.readString()); 221 } 222 String address = null; 223 // If we have a non-null address 224 if (in.readInt() == 1) { 225 address = in.readString(); 226 } 227 if (in.readInt() == 1) { 228 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); 229 builder.setServiceUuid(uuid); 230 if (in.readInt() == 1) { 231 ParcelUuid uuidMask = in.readParcelable( 232 ParcelUuid.class.getClassLoader()); 233 builder.setServiceUuid(uuid, uuidMask); 234 } 235 } 236 if (in.readInt() == 1) { 237 ParcelUuid solicitationUuid = in.readParcelable( 238 ParcelUuid.class.getClassLoader()); 239 builder.setServiceSolicitationUuid(solicitationUuid); 240 if (in.readInt() == 1) { 241 ParcelUuid solicitationUuidMask = in.readParcelable( 242 ParcelUuid.class.getClassLoader()); 243 builder.setServiceSolicitationUuid(solicitationUuid, 244 solicitationUuidMask); 245 } 246 } 247 if (in.readInt() == 1) { 248 ParcelUuid servcieDataUuid = 249 in.readParcelable(ParcelUuid.class.getClassLoader()); 250 if (in.readInt() == 1) { 251 int serviceDataLength = in.readInt(); 252 byte[] serviceData = new byte[serviceDataLength]; 253 in.readByteArray(serviceData); 254 if (in.readInt() == 0) { 255 builder.setServiceData(servcieDataUuid, serviceData); 256 } else { 257 int serviceDataMaskLength = in.readInt(); 258 byte[] serviceDataMask = new byte[serviceDataMaskLength]; 259 in.readByteArray(serviceDataMask); 260 builder.setServiceData( 261 servcieDataUuid, serviceData, serviceDataMask); 262 } 263 } 264 } 265 266 int manufacturerId = in.readInt(); 267 if (in.readInt() == 1) { 268 int manufacturerDataLength = in.readInt(); 269 byte[] manufacturerData = new byte[manufacturerDataLength]; 270 in.readByteArray(manufacturerData); 271 if (in.readInt() == 0) { 272 builder.setManufacturerData(manufacturerId, manufacturerData); 273 } else { 274 int manufacturerDataMaskLength = in.readInt(); 275 byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; 276 in.readByteArray(manufacturerDataMask); 277 builder.setManufacturerData(manufacturerId, manufacturerData, 278 manufacturerDataMask); 279 } 280 } 281 282 // IRK 283 if (address != null) { 284 final int addressType = in.readInt(); 285 if (in.readInt() == 1) { 286 final byte[] irk = new byte[16]; 287 in.readByteArray(irk); 288 builder.setDeviceAddress(address, addressType, irk); 289 } else { 290 builder.setDeviceAddress(address, addressType); 291 } 292 } 293 294 // Advertising data type 295 int advertisingDataType = in.readInt(); 296 if (in.readInt() == 1) { 297 byte[] advertisingData = null; 298 byte[] advertisingDataMask = null; 299 300 int advertisingDataLength = in.readInt(); 301 advertisingData = new byte[advertisingDataLength]; 302 in.readByteArray(advertisingData); 303 if (in.readInt() == 1) { 304 int advertisingDataMaskLength = in.readInt(); 305 advertisingDataMask = new byte[advertisingDataMaskLength]; 306 in.readByteArray(advertisingDataMask); 307 } 308 builder.setAdvertisingDataTypeWithData(advertisingDataType, advertisingData, 309 advertisingDataMask); 310 } 311 312 return builder.build(); 313 } 314 }; 315 316 /** 317 * Returns the filter set the device name field of Bluetooth advertisement data. 318 */ 319 @Nullable getDeviceName()320 public String getDeviceName() { 321 return mDeviceName; 322 } 323 324 /** 325 * Returns the filter set on the service uuid. 326 */ 327 @Nullable getServiceUuid()328 public ParcelUuid getServiceUuid() { 329 return mServiceUuid; 330 } 331 332 @Nullable getServiceUuidMask()333 public ParcelUuid getServiceUuidMask() { 334 return mServiceUuidMask; 335 } 336 337 /** 338 * Returns the filter set on the service Solicitation uuid. 339 */ 340 @Nullable getServiceSolicitationUuid()341 public ParcelUuid getServiceSolicitationUuid() { 342 return mServiceSolicitationUuid; 343 } 344 345 /** 346 * Returns the filter set on the service Solicitation uuid mask. 347 */ 348 @Nullable getServiceSolicitationUuidMask()349 public ParcelUuid getServiceSolicitationUuidMask() { 350 return mServiceSolicitationUuidMask; 351 } 352 353 @Nullable getDeviceAddress()354 public String getDeviceAddress() { 355 return mDeviceAddress; 356 } 357 358 /** 359 * @hide 360 */ 361 @SystemApi getAddressType()362 public @AddressType int getAddressType() { 363 return mAddressType; 364 } 365 366 /** 367 * @hide 368 */ 369 @SystemApi 370 @Nullable getIrk()371 public byte[] getIrk() { 372 return mIrk; 373 } 374 375 @Nullable getServiceData()376 public byte[] getServiceData() { 377 return mServiceData; 378 } 379 380 @Nullable getServiceDataMask()381 public byte[] getServiceDataMask() { 382 return mServiceDataMask; 383 } 384 385 @Nullable getServiceDataUuid()386 public ParcelUuid getServiceDataUuid() { 387 return mServiceDataUuid; 388 } 389 390 /** 391 * Returns the manufacturer id. -1 if the manufacturer filter is not set. 392 */ getManufacturerId()393 public int getManufacturerId() { 394 return mManufacturerId; 395 } 396 397 @Nullable getManufacturerData()398 public byte[] getManufacturerData() { 399 return mManufacturerData; 400 } 401 402 @Nullable getManufacturerDataMask()403 public byte[] getManufacturerDataMask() { 404 return mManufacturerDataMask; 405 } 406 407 /** 408 * Returns the advertising data type of this filter. 409 * Returns {@link ScanRecord#DATA_TYPE_NONE} if the type is not set. 410 * The values of advertising data type are defined in the Bluetooth Generic Access Profile 411 * (https://www.bluetooth.com/specifications/assigned-numbers/) 412 */ 413 @AdvertisingDataType getAdvertisingDataType()414 public int getAdvertisingDataType() { 415 return mAdvertisingDataType; 416 } 417 418 /** 419 * Returns the advertising data of this filter. 420 */ getAdvertisingData()421 public @Nullable byte[] getAdvertisingData() { 422 return mAdvertisingData; 423 } 424 425 /** 426 * Returns the advertising data mask of this filter. 427 */ getAdvertisingDataMask()428 public @Nullable byte[] getAdvertisingDataMask() { 429 return mAdvertisingDataMask; 430 } 431 432 /** 433 * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match 434 * if it matches all the field filters. 435 */ matches(ScanResult scanResult)436 public boolean matches(ScanResult scanResult) { 437 if (scanResult == null) { 438 return false; 439 } 440 BluetoothDevice device = scanResult.getDevice(); 441 // Device match. 442 if (mDeviceAddress != null 443 && (device == null || !mDeviceAddress.equals(device.getAddress()))) { 444 return false; 445 } 446 447 ScanRecord scanRecord = scanResult.getScanRecord(); 448 449 // Scan record is null but there exist filters on it. 450 if (scanRecord == null 451 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null 452 || mServiceData != null || mServiceSolicitationUuid != null 453 || mAdvertisingData != null)) { 454 return false; 455 } 456 457 // Local name match. 458 if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { 459 return false; 460 } 461 462 // UUID match. 463 if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, 464 scanRecord.getServiceUuids())) { 465 return false; 466 } 467 468 // solicitation UUID match. 469 if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids( 470 mServiceSolicitationUuid, mServiceSolicitationUuidMask, 471 scanRecord.getServiceSolicitationUuids())) { 472 return false; 473 } 474 475 // Service data match 476 if (mServiceDataUuid != null) { 477 if (!matchesPartialData(mServiceData, mServiceDataMask, 478 scanRecord.getServiceData(mServiceDataUuid))) { 479 return false; 480 } 481 } 482 483 // Manufacturer data match. 484 if (mManufacturerId >= 0) { 485 if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, 486 scanRecord.getManufacturerSpecificData(mManufacturerId))) { 487 return false; 488 } 489 } 490 491 // Advertising data type match 492 if (mAdvertisingDataType > 0) { 493 byte[] advertisingData = scanRecord.getAdvertisingDataMap().get(mAdvertisingDataType); 494 if (advertisingData == null || !matchesPartialData(mAdvertisingData, 495 mAdvertisingDataMask, advertisingData)) { 496 return false; 497 } 498 } 499 500 // All filters match. 501 return true; 502 } 503 504 /** 505 * Check if the uuid pattern is contained in a list of parcel uuids. 506 * 507 * @hide 508 */ matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)509 public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, 510 List<ParcelUuid> uuids) { 511 if (uuid == null) { 512 return true; 513 } 514 if (uuids == null) { 515 return false; 516 } 517 518 for (ParcelUuid parcelUuid : uuids) { 519 UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); 520 if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { 521 return true; 522 } 523 } 524 return false; 525 } 526 527 // Check if the uuid pattern matches the particular service uuid. matchesServiceUuid(UUID uuid, UUID mask, UUID data)528 private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { 529 return BluetoothLeUtils.maskedEquals(data, uuid, mask); 530 } 531 532 /** 533 * Check if the solicitation uuid pattern is contained in a list of parcel uuids. 534 * 535 */ matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)536 private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, 537 ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) { 538 if (solicitationUuid == null) { 539 return true; 540 } 541 if (solicitationUuids == null) { 542 return false; 543 } 544 545 for (ParcelUuid parcelSolicitationUuid : solicitationUuids) { 546 UUID solicitationUuidMask = parcelSolicitationUuidMask == null 547 ? null : parcelSolicitationUuidMask.getUuid(); 548 if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask, 549 parcelSolicitationUuid.getUuid())) { 550 return true; 551 } 552 } 553 return false; 554 } 555 556 // Check if the solicitation uuid pattern matches the particular service solicitation uuid. matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)557 private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid, 558 UUID solicitationUuidMask, UUID data) { 559 return BluetoothLeUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask); 560 } 561 562 // Check whether the data pattern matches the parsed data. matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)563 private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { 564 if (parsedData == null || parsedData.length < data.length) { 565 return false; 566 } 567 if (dataMask == null) { 568 for (int i = 0; i < data.length; ++i) { 569 if (parsedData[i] != data[i]) { 570 return false; 571 } 572 } 573 return true; 574 } 575 for (int i = 0; i < data.length; ++i) { 576 if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { 577 return false; 578 } 579 } 580 return true; 581 } 582 583 @Override toString()584 public String toString() { 585 return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" 586 + mDeviceAddress + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask 587 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid 588 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask 589 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) 590 + ", mServiceData=" + Arrays.toString(mServiceData) + ", mServiceDataMask=" 591 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId 592 + ", mManufacturerData=" + Arrays.toString(mManufacturerData) 593 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) 594 + ", mAdvertisingDataType=" + mAdvertisingDataType + ", mAdvertisingData=" 595 + Arrays.toString(mAdvertisingData) + ", mAdvertisingDataMask=" 596 + Arrays.toString(mAdvertisingDataMask) + "]"; 597 } 598 599 @Override hashCode()600 public int hashCode() { 601 return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, 602 Arrays.hashCode(mManufacturerData), 603 Arrays.hashCode(mManufacturerDataMask), 604 mServiceDataUuid, 605 Arrays.hashCode(mServiceData), 606 Arrays.hashCode(mServiceDataMask), 607 mServiceUuid, mServiceUuidMask, 608 mServiceSolicitationUuid, mServiceSolicitationUuidMask, 609 mAdvertisingDataType, 610 Arrays.hashCode(mAdvertisingData), 611 Arrays.hashCode(mAdvertisingDataMask)); 612 } 613 614 @Override equals(@ullable Object obj)615 public boolean equals(@Nullable Object obj) { 616 if (this == obj) { 617 return true; 618 } 619 if (obj == null || getClass() != obj.getClass()) { 620 return false; 621 } 622 ScanFilter other = (ScanFilter) obj; 623 return Objects.equals(mDeviceName, other.mDeviceName) 624 && Objects.equals(mDeviceAddress, other.mDeviceAddress) 625 && mManufacturerId == other.mManufacturerId 626 && Objects.deepEquals(mManufacturerData, other.mManufacturerData) 627 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) 628 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid) 629 && Objects.deepEquals(mServiceData, other.mServiceData) 630 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) 631 && Objects.equals(mServiceUuid, other.mServiceUuid) 632 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask) 633 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid) 634 && Objects.equals(mServiceSolicitationUuidMask, 635 other.mServiceSolicitationUuidMask) 636 && mAdvertisingDataType == other.mAdvertisingDataType 637 && Objects.deepEquals(mAdvertisingData, other.mAdvertisingData) 638 && Objects.deepEquals(mAdvertisingDataMask, other.mAdvertisingDataMask); 639 } 640 641 /** 642 * Checks if the scanfilter is empty 643 * 644 * @hide 645 */ isAllFieldsEmpty()646 public boolean isAllFieldsEmpty() { 647 return EMPTY.equals(this); 648 } 649 650 /** 651 * Builder class for {@link ScanFilter}. 652 */ 653 public static final class Builder { 654 655 /** 656 * @hide 657 */ 658 @SystemApi 659 public static final int LEN_IRK_OCTETS = 16; 660 661 private String mDeviceName; 662 private String mDeviceAddress; 663 private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC; 664 private byte[] mIrk; 665 666 private ParcelUuid mServiceUuid; 667 private ParcelUuid mUuidMask; 668 669 private ParcelUuid mServiceSolicitationUuid; 670 private ParcelUuid mServiceSolicitationUuidMask; 671 672 private ParcelUuid mServiceDataUuid; 673 private byte[] mServiceData; 674 private byte[] mServiceDataMask; 675 676 private int mManufacturerId = -1; 677 private byte[] mManufacturerData; 678 private byte[] mManufacturerDataMask; 679 680 private int mAdvertisingDataType = ScanRecord.DATA_TYPE_NONE; 681 private byte[] mAdvertisingData; 682 private byte[] mAdvertisingDataMask; 683 684 /** 685 * Set filter on device name. 686 */ setDeviceName(String deviceName)687 public Builder setDeviceName(String deviceName) { 688 mDeviceName = deviceName; 689 return this; 690 } 691 692 /** 693 * Set a scan filter on the remote device address. 694 * <p> 695 * The address passed to this API must be in big endian byte order. It needs to be in the 696 * format of "01:02:03:AB:CD:EF". The device address can be validated using 697 * {@link BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to 698 * {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC}. 699 * 700 * @param deviceAddress the remote device Bluetooth address for the filter 701 * @throws IllegalArgumentException if the {@code deviceAddress} is invalid 702 */ setDeviceAddress(String deviceAddress)703 public Builder setDeviceAddress(String deviceAddress) { 704 if (deviceAddress == null) { 705 mDeviceAddress = deviceAddress; 706 return this; 707 } 708 return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); 709 } 710 711 /** 712 * Set a scan filter on the remote device address with an address type. 713 * <p> 714 * The address passed to this API must be in big endian byte order. It needs to be in the 715 * format of "01:02:03:AB:CD:EF". The device address can be validated using 716 * {@link BluetoothAdapter#checkBluetoothAddress}. 717 * 718 * @param deviceAddress the remote device Bluetooth address for the filter 719 * @param addressType indication of the type of address 720 * 721 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid 722 * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not 723 * either {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} or 724 * {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} 725 * @throws NullPointerException if {@code deviceAddress} is null 726 * 727 * @hide 728 */ 729 @NonNull 730 @SystemApi setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType)731 public Builder setDeviceAddress(@NonNull String deviceAddress, 732 @AddressType int addressType) { 733 return setDeviceAddressInternal(deviceAddress, addressType, null); 734 } 735 736 /** 737 * Set a scan filter on the remote device address with an address type and the Identity 738 * Resolving Key (IRK). 739 * <p> 740 * The address passed to this API must be either a public or random static address in big 741 * endian byte order. It needs to be in the format of "01:02:03:AB:CD:EF". The device 742 * address can be validated using {@link BluetoothAdapter#checkBluetoothAddress}. 743 * <p> 744 * The IRK is used to resolve a static address from a private address. The IRK must be 745 * provided in little endian byte order. 746 * 747 * @param deviceAddress the remote device Bluetooth address for the filter 748 * @param addressType indication of the type of address 749 * @param irk non-null little endian byte array representing the Identity Resolving Key 750 * 751 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid 752 * @throws IllegalArgumentException if the {@code irk} is invalid length 753 * @throws IllegalArgumentException If the {@code addressType} is an invalid length or is 754 * not PUBLIC or RANDOM STATIC 755 * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null 756 * 757 * @hide 758 */ 759 @NonNull 760 @SystemApi setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType, @NonNull byte[] irk)761 public Builder setDeviceAddress(@NonNull String deviceAddress, 762 @AddressType int addressType, 763 @NonNull byte[] irk) { 764 requireNonNull(irk); 765 if (irk.length != LEN_IRK_OCTETS) { 766 throw new IllegalArgumentException("'irk' is invalid length!"); 767 } 768 return setDeviceAddressInternal(deviceAddress, addressType, irk); 769 } 770 771 /** 772 * Set filter on Address with AddressType and the Identity Resolving Key (IRK). 773 * 774 * <p>Internal setter for the device address 775 * 776 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 777 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 778 * BluetoothAdapter#checkBluetoothAddress}. 779 * @param addressType indication of the type of address 780 * @param irk non-null little endian byte array representing the Identity Resolving Key; 781 * nullable internally. 782 * 783 * @throws IllegalArgumentException if the {@code deviceAddress} is invalid 784 * @throws IllegalArgumentException if the {@code addressType} is not PUBLIC or RANDOM 785 * STATIC when an IRK is present 786 * @throws NullPointerException if {@code deviceAddress} is null 787 * 788 * @hide 789 */ 790 @NonNull setDeviceAddressInternal(@onNull String deviceAddress, @AddressType int addressType, @Nullable byte[] irk)791 private Builder setDeviceAddressInternal(@NonNull String deviceAddress, 792 @AddressType int addressType, 793 @Nullable byte[] irk) { 794 795 // Make sure our deviceAddress is valid! 796 requireNonNull(deviceAddress); 797 if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { 798 throw new IllegalArgumentException("invalid device address " + deviceAddress); 799 } 800 801 // Verify type range 802 if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC 803 || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) { 804 throw new IllegalArgumentException("'addressType' is invalid!"); 805 } 806 807 // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address. 808 if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) { 809 // Don't want a bad combination of address and irk! 810 if (irk != null) { 811 // Since there are 3 possible RANDOM subtypes we must check to make sure 812 // the correct type of address is used. 813 if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) { 814 throw new IllegalArgumentException( 815 "Invalid combination: IRK requires either a PUBLIC or " 816 + "RANDOM (STATIC) Address"); 817 } 818 } 819 } 820 821 // PUBLIC doesn't require extra work 822 // Without an IRK any address may be accepted 823 824 mDeviceAddress = deviceAddress; 825 mAddressType = addressType; 826 mIrk = irk; 827 return this; 828 } 829 830 /** 831 * Set filter on service uuid. 832 */ setServiceUuid(ParcelUuid serviceUuid)833 public Builder setServiceUuid(ParcelUuid serviceUuid) { 834 mServiceUuid = serviceUuid; 835 mUuidMask = null; // clear uuid mask 836 return this; 837 } 838 839 /** 840 * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the 841 * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the 842 * bit in {@code serviceUuid}, and 0 to ignore that bit. 843 * 844 * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code 845 * uuidMask} is not {@code null}. 846 */ setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)847 public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { 848 if (mUuidMask != null && mServiceUuid == null) { 849 throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); 850 } 851 mServiceUuid = serviceUuid; 852 mUuidMask = uuidMask; 853 return this; 854 } 855 856 857 /** 858 * Set filter on service solicitation uuid. 859 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)860 public @NonNull Builder setServiceSolicitationUuid( 861 @Nullable ParcelUuid serviceSolicitationUuid) { 862 mServiceSolicitationUuid = serviceSolicitationUuid; 863 if (serviceSolicitationUuid == null) { 864 mServiceSolicitationUuidMask = null; 865 } 866 return this; 867 } 868 869 870 /** 871 * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the 872 * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to 873 * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to 874 * ignore that bit. 875 * 876 * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null. 877 * @param solicitationUuidMask can be null or a mask with no restriction. 878 * 879 * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but 880 * {@code serviceSolicitationUuidMask} is not {@code null}. 881 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)882 public @NonNull Builder setServiceSolicitationUuid( 883 @Nullable ParcelUuid serviceSolicitationUuid, 884 @Nullable ParcelUuid solicitationUuidMask) { 885 if (solicitationUuidMask != null && serviceSolicitationUuid == null) { 886 throw new IllegalArgumentException( 887 "SolicitationUuid is null while SolicitationUuidMask is not null!"); 888 } 889 mServiceSolicitationUuid = serviceSolicitationUuid; 890 mServiceSolicitationUuidMask = solicitationUuidMask; 891 return this; 892 } 893 894 /** 895 * Set filtering on service data. 896 * 897 * @throws IllegalArgumentException If {@code serviceDataUuid} is null. 898 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)899 public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { 900 if (serviceDataUuid == null) { 901 throw new IllegalArgumentException("serviceDataUuid is null"); 902 } 903 mServiceDataUuid = serviceDataUuid; 904 mServiceData = serviceData; 905 mServiceDataMask = null; // clear service data mask 906 return this; 907 } 908 909 /** 910 * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to 911 * match the one in service data, otherwise set it to 0 to ignore that bit. 912 * <p> 913 * The {@code serviceDataMask} must have the same length of the {@code serviceData}. 914 * 915 * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code 916 * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code 917 * serviceDataMask} and {@code serviceData} has different length. 918 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)919 public Builder setServiceData(ParcelUuid serviceDataUuid, 920 byte[] serviceData, byte[] serviceDataMask) { 921 if (serviceDataUuid == null) { 922 throw new IllegalArgumentException("serviceDataUuid is null"); 923 } 924 if (mServiceDataMask != null) { 925 if (mServiceData == null) { 926 throw new IllegalArgumentException( 927 "serviceData is null while serviceDataMask is not null"); 928 } 929 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two 930 // byte array need to be the same. 931 if (mServiceData.length != mServiceDataMask.length) { 932 throw new IllegalArgumentException( 933 "size mismatch for service data and service data mask"); 934 } 935 } 936 mServiceDataUuid = serviceDataUuid; 937 mServiceData = serviceData; 938 mServiceDataMask = serviceDataMask; 939 return this; 940 } 941 942 /** 943 * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. 944 * 945 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. 946 */ setManufacturerData(int manufacturerId, byte[] manufacturerData)947 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { 948 if (manufacturerData != null && manufacturerId < 0) { 949 throw new IllegalArgumentException("invalid manufacture id"); 950 } 951 mManufacturerId = manufacturerId; 952 mManufacturerData = manufacturerData; 953 mManufacturerDataMask = null; // clear manufacturer data mask 954 return this; 955 } 956 957 /** 958 * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs 959 * to match the one in manufacturer data, otherwise set it to 0. 960 * <p> 961 * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. 962 * 963 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code 964 * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code 965 * manufacturerData} and {@code manufacturerDataMask} have different length. 966 */ setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)967 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, 968 byte[] manufacturerDataMask) { 969 if (manufacturerData != null && manufacturerId < 0) { 970 throw new IllegalArgumentException("invalid manufacture id"); 971 } 972 if (mManufacturerDataMask != null) { 973 if (mManufacturerData == null) { 974 throw new IllegalArgumentException( 975 "manufacturerData is null while manufacturerDataMask is not null"); 976 } 977 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths 978 // of the two byte array need to be the same. 979 if (mManufacturerData.length != mManufacturerDataMask.length) { 980 throw new IllegalArgumentException( 981 "size mismatch for manufacturerData and manufacturerDataMask"); 982 } 983 } 984 mManufacturerId = manufacturerId; 985 mManufacturerData = manufacturerData; 986 mManufacturerDataMask = manufacturerDataMask; 987 return this; 988 } 989 990 /** 991 * Set filter on advertising data with specific advertising data type. 992 * For any bit in the mask, set it the 1 if it needs to match the one in 993 * advertising data, otherwise set it to 0. 994 * <p> 995 * The values of {@code advertisingDataType} are assigned by Bluetooth SIG. For more 996 * details refer to Bluetooth Generic Access Profile. 997 * (https://www.bluetooth.com/specifications/assigned-numbers/) 998 * The {@code advertisingDataMask} must have the same length of {@code advertisingData}. 999 * 1000 * @throws IllegalArgumentException If the {@code advertisingDataType} is invalid, {@code 1001 * advertisingData} or {@code advertisingDataMask} is null or {@code 1002 * advertisingData} and {@code advertisingDataMask} have different length. 1003 */ setAdvertisingDataTypeWithData( @dvertisingDataType int advertisingDataType, @NonNull byte[] advertisingData, @NonNull byte[] advertisingDataMask)1004 public @NonNull Builder setAdvertisingDataTypeWithData( 1005 @AdvertisingDataType int advertisingDataType, @NonNull byte[] advertisingData, 1006 @NonNull byte[] advertisingDataMask) { 1007 if (advertisingDataType < 0) { 1008 throw new IllegalArgumentException("invalid advertising data type"); 1009 } 1010 if (mAdvertisingDataMask != null) { 1011 if (mAdvertisingData == null) { 1012 throw new IllegalArgumentException( 1013 "mAdvertisingData is null while mAdvertisingDataMask is not null"); 1014 } 1015 // Since the mAdvertisingDataMask is a bit mask for mAdvertisingData, the lengths 1016 // of the two byte array need to be the same. 1017 if (mAdvertisingData.length != mAdvertisingDataMask.length) { 1018 throw new IllegalArgumentException( 1019 "size mismatch for mAdvertisingData and mAdvertisingDataMask"); 1020 } 1021 } 1022 mAdvertisingDataType = advertisingDataType; 1023 mAdvertisingData = advertisingData; 1024 mAdvertisingDataMask = advertisingDataMask; 1025 return this; 1026 } 1027 1028 1029 /** 1030 * Set filter on advertising data with specific advertising data type. 1031 * <p> 1032 * The values of {@code advertisingDataType} are assigned by Bluetooth SIG. For more 1033 * details refer to Bluetooth Generic Access Profile. 1034 * (https://www.bluetooth.com/specifications/assigned-numbers/) 1035 * @throws IllegalArgumentException If the {@code advertisingDataType} is invalid 1036 */ setAdvertisingDataType( @dvertisingDataType int advertisingDataType)1037 public @NonNull Builder setAdvertisingDataType( 1038 @AdvertisingDataType int advertisingDataType) { 1039 if (advertisingDataType < 0) { 1040 throw new IllegalArgumentException("invalid advertising data type"); 1041 } 1042 mAdvertisingDataType = advertisingDataType; 1043 return this; 1044 } 1045 1046 /** 1047 * Build {@link ScanFilter}. 1048 * 1049 * @throws IllegalArgumentException If the filter cannot be built. 1050 */ build()1051 public ScanFilter build() { 1052 return new ScanFilter(mDeviceName, mDeviceAddress, mServiceUuid, mUuidMask, 1053 mServiceSolicitationUuid, mServiceSolicitationUuidMask, mServiceDataUuid, 1054 mServiceData, mServiceDataMask, mManufacturerId, mManufacturerData, 1055 mManufacturerDataMask, mAddressType, mIrk, mAdvertisingDataType, 1056 mAdvertisingData, mAdvertisingDataMask); 1057 } 1058 } 1059 } 1060