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