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