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.os.Parcel; 28 import android.os.ParcelUuid; 29 import android.os.Parcelable; 30 31 import com.android.internal.util.BitUtils; 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 * 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 /** @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)91 private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, 92 ParcelUuid uuidMask, ParcelUuid solicitationUuid, 93 ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, 94 byte[] serviceData, byte[] serviceDataMask, 95 int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask, 96 @AddressType int addressType, @Nullable byte[] irk) { 97 mDeviceName = name; 98 mServiceUuid = uuid; 99 mServiceUuidMask = uuidMask; 100 mServiceSolicitationUuid = solicitationUuid; 101 mServiceSolicitationUuidMask = solicitationUuidMask; 102 mDeviceAddress = deviceAddress; 103 mServiceDataUuid = serviceDataUuid; 104 mServiceData = serviceData; 105 mServiceDataMask = serviceDataMask; 106 mManufacturerId = manufacturerId; 107 mManufacturerData = manufacturerData; 108 mManufacturerDataMask = manufacturerDataMask; 109 mAddressType = addressType; 110 mIrk = irk; 111 } 112 113 @Override describeContents()114 public int describeContents() { 115 return 0; 116 } 117 118 @Override writeToParcel(Parcel dest, int flags)119 public void writeToParcel(Parcel dest, int flags) { 120 dest.writeInt(mDeviceName == null ? 0 : 1); 121 if (mDeviceName != null) { 122 dest.writeString(mDeviceName); 123 } 124 dest.writeInt(mDeviceAddress == null ? 0 : 1); 125 if (mDeviceAddress != null) { 126 dest.writeString(mDeviceAddress); 127 } 128 dest.writeInt(mServiceUuid == null ? 0 : 1); 129 if (mServiceUuid != null) { 130 dest.writeParcelable(mServiceUuid, flags); 131 dest.writeInt(mServiceUuidMask == null ? 0 : 1); 132 if (mServiceUuidMask != null) { 133 dest.writeParcelable(mServiceUuidMask, flags); 134 } 135 } 136 dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1); 137 if (mServiceSolicitationUuid != null) { 138 dest.writeParcelable(mServiceSolicitationUuid, flags); 139 dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1); 140 if (mServiceSolicitationUuidMask != null) { 141 dest.writeParcelable(mServiceSolicitationUuidMask, flags); 142 } 143 } 144 dest.writeInt(mServiceDataUuid == null ? 0 : 1); 145 if (mServiceDataUuid != null) { 146 dest.writeParcelable(mServiceDataUuid, flags); 147 dest.writeInt(mServiceData == null ? 0 : 1); 148 if (mServiceData != null) { 149 dest.writeInt(mServiceData.length); 150 dest.writeByteArray(mServiceData); 151 152 dest.writeInt(mServiceDataMask == null ? 0 : 1); 153 if (mServiceDataMask != null) { 154 dest.writeInt(mServiceDataMask.length); 155 dest.writeByteArray(mServiceDataMask); 156 } 157 } 158 } 159 dest.writeInt(mManufacturerId); 160 dest.writeInt(mManufacturerData == null ? 0 : 1); 161 if (mManufacturerData != null) { 162 dest.writeInt(mManufacturerData.length); 163 dest.writeByteArray(mManufacturerData); 164 165 dest.writeInt(mManufacturerDataMask == null ? 0 : 1); 166 if (mManufacturerDataMask != null) { 167 dest.writeInt(mManufacturerDataMask.length); 168 dest.writeByteArray(mManufacturerDataMask); 169 } 170 } 171 172 // IRK 173 if (mDeviceAddress != null) { 174 dest.writeInt(mAddressType); 175 dest.writeInt(mIrk == null ? 0 : 1); 176 if (mIrk != null) { 177 dest.writeByteArray(mIrk); 178 } 179 } 180 } 181 182 /** 183 * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. 184 */ 185 public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR = 186 new Creator<ScanFilter>() { 187 188 @Override 189 public ScanFilter[] newArray(int size) { 190 return new ScanFilter[size]; 191 } 192 193 @Override 194 public ScanFilter createFromParcel(Parcel in) { 195 Builder builder = new Builder(); 196 if (in.readInt() == 1) { 197 builder.setDeviceName(in.readString()); 198 } 199 String address = null; 200 // If we have a non-null address 201 if (in.readInt() == 1) { 202 address = in.readString(); 203 } 204 if (in.readInt() == 1) { 205 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); 206 builder.setServiceUuid(uuid); 207 if (in.readInt() == 1) { 208 ParcelUuid uuidMask = in.readParcelable( 209 ParcelUuid.class.getClassLoader()); 210 builder.setServiceUuid(uuid, uuidMask); 211 } 212 } 213 if (in.readInt() == 1) { 214 ParcelUuid solicitationUuid = in.readParcelable( 215 ParcelUuid.class.getClassLoader()); 216 builder.setServiceSolicitationUuid(solicitationUuid); 217 if (in.readInt() == 1) { 218 ParcelUuid solicitationUuidMask = in.readParcelable( 219 ParcelUuid.class.getClassLoader()); 220 builder.setServiceSolicitationUuid(solicitationUuid, 221 solicitationUuidMask); 222 } 223 } 224 if (in.readInt() == 1) { 225 ParcelUuid servcieDataUuid = 226 in.readParcelable(ParcelUuid.class.getClassLoader()); 227 if (in.readInt() == 1) { 228 int serviceDataLength = in.readInt(); 229 byte[] serviceData = new byte[serviceDataLength]; 230 in.readByteArray(serviceData); 231 if (in.readInt() == 0) { 232 builder.setServiceData(servcieDataUuid, serviceData); 233 } else { 234 int serviceDataMaskLength = in.readInt(); 235 byte[] serviceDataMask = new byte[serviceDataMaskLength]; 236 in.readByteArray(serviceDataMask); 237 builder.setServiceData( 238 servcieDataUuid, serviceData, serviceDataMask); 239 } 240 } 241 } 242 243 int manufacturerId = in.readInt(); 244 if (in.readInt() == 1) { 245 int manufacturerDataLength = in.readInt(); 246 byte[] manufacturerData = new byte[manufacturerDataLength]; 247 in.readByteArray(manufacturerData); 248 if (in.readInt() == 0) { 249 builder.setManufacturerData(manufacturerId, manufacturerData); 250 } else { 251 int manufacturerDataMaskLength = in.readInt(); 252 byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; 253 in.readByteArray(manufacturerDataMask); 254 builder.setManufacturerData(manufacturerId, manufacturerData, 255 manufacturerDataMask); 256 } 257 } 258 259 // IRK 260 if (address != null) { 261 final int addressType = in.readInt(); 262 if (in.readInt() == 1) { 263 final byte[] irk = new byte[16]; 264 in.readByteArray(irk); 265 builder.setDeviceAddress(address, addressType, irk); 266 } else { 267 builder.setDeviceAddress(address, addressType); 268 } 269 } 270 return builder.build(); 271 } 272 }; 273 274 /** 275 * Returns the filter set the device name field of Bluetooth advertisement data. 276 */ 277 @Nullable getDeviceName()278 public String getDeviceName() { 279 return mDeviceName; 280 } 281 282 /** 283 * Returns the filter set on the service uuid. 284 */ 285 @Nullable getServiceUuid()286 public ParcelUuid getServiceUuid() { 287 return mServiceUuid; 288 } 289 290 @Nullable getServiceUuidMask()291 public ParcelUuid getServiceUuidMask() { 292 return mServiceUuidMask; 293 } 294 295 /** 296 * Returns the filter set on the service Solicitation uuid. 297 */ 298 @Nullable getServiceSolicitationUuid()299 public ParcelUuid getServiceSolicitationUuid() { 300 return mServiceSolicitationUuid; 301 } 302 303 /** 304 * Returns the filter set on the service Solicitation uuid mask. 305 */ 306 @Nullable getServiceSolicitationUuidMask()307 public ParcelUuid getServiceSolicitationUuidMask() { 308 return mServiceSolicitationUuidMask; 309 } 310 311 @Nullable getDeviceAddress()312 public String getDeviceAddress() { 313 return mDeviceAddress; 314 } 315 316 /** 317 * @hide 318 */ 319 @SystemApi getAddressType()320 public @AddressType int getAddressType() { 321 return mAddressType; 322 } 323 324 /** 325 * @hide 326 */ 327 @SystemApi 328 @Nullable getIrk()329 public byte[] getIrk() { 330 return mIrk; 331 } 332 333 @Nullable getServiceData()334 public byte[] getServiceData() { 335 return mServiceData; 336 } 337 338 @Nullable getServiceDataMask()339 public byte[] getServiceDataMask() { 340 return mServiceDataMask; 341 } 342 343 @Nullable getServiceDataUuid()344 public ParcelUuid getServiceDataUuid() { 345 return mServiceDataUuid; 346 } 347 348 /** 349 * Returns the manufacturer id. -1 if the manufacturer filter is not set. 350 */ getManufacturerId()351 public int getManufacturerId() { 352 return mManufacturerId; 353 } 354 355 @Nullable getManufacturerData()356 public byte[] getManufacturerData() { 357 return mManufacturerData; 358 } 359 360 @Nullable getManufacturerDataMask()361 public byte[] getManufacturerDataMask() { 362 return mManufacturerDataMask; 363 } 364 365 /** 366 * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match 367 * if it matches all the field filters. 368 */ matches(ScanResult scanResult)369 public boolean matches(ScanResult scanResult) { 370 if (scanResult == null) { 371 return false; 372 } 373 BluetoothDevice device = scanResult.getDevice(); 374 // Device match. 375 if (mDeviceAddress != null 376 && (device == null || !mDeviceAddress.equals(device.getAddress()))) { 377 return false; 378 } 379 380 ScanRecord scanRecord = scanResult.getScanRecord(); 381 382 // Scan record is null but there exist filters on it. 383 if (scanRecord == null 384 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null 385 || mServiceData != null || mServiceSolicitationUuid != null)) { 386 return false; 387 } 388 389 // Local name match. 390 if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { 391 return false; 392 } 393 394 // UUID match. 395 if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, 396 scanRecord.getServiceUuids())) { 397 return false; 398 } 399 400 // solicitation UUID match. 401 if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids( 402 mServiceSolicitationUuid, mServiceSolicitationUuidMask, 403 scanRecord.getServiceSolicitationUuids())) { 404 return false; 405 } 406 407 // Service data match 408 if (mServiceDataUuid != null) { 409 if (!matchesPartialData(mServiceData, mServiceDataMask, 410 scanRecord.getServiceData(mServiceDataUuid))) { 411 return false; 412 } 413 } 414 415 // Manufacturer data match. 416 if (mManufacturerId >= 0) { 417 if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, 418 scanRecord.getManufacturerSpecificData(mManufacturerId))) { 419 return false; 420 } 421 } 422 // All filters match. 423 return true; 424 } 425 426 /** 427 * Check if the uuid pattern is contained in a list of parcel uuids. 428 * 429 * @hide 430 */ matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)431 public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, 432 List<ParcelUuid> uuids) { 433 if (uuid == null) { 434 return true; 435 } 436 if (uuids == null) { 437 return false; 438 } 439 440 for (ParcelUuid parcelUuid : uuids) { 441 UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); 442 if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { 443 return true; 444 } 445 } 446 return false; 447 } 448 449 // Check if the uuid pattern matches the particular service uuid. matchesServiceUuid(UUID uuid, UUID mask, UUID data)450 private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { 451 return BitUtils.maskedEquals(data, uuid, mask); 452 } 453 454 /** 455 * Check if the solicitation uuid pattern is contained in a list of parcel uuids. 456 * 457 */ matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)458 private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, 459 ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) { 460 if (solicitationUuid == null) { 461 return true; 462 } 463 if (solicitationUuids == null) { 464 return false; 465 } 466 467 for (ParcelUuid parcelSolicitationUuid : solicitationUuids) { 468 UUID solicitationUuidMask = parcelSolicitationUuidMask == null 469 ? null : parcelSolicitationUuidMask.getUuid(); 470 if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask, 471 parcelSolicitationUuid.getUuid())) { 472 return true; 473 } 474 } 475 return false; 476 } 477 478 // Check if the solicitation uuid pattern matches the particular service solicitation uuid. matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)479 private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid, 480 UUID solicitationUuidMask, UUID data) { 481 return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask); 482 } 483 484 // Check whether the data pattern matches the parsed data. matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)485 private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { 486 if (parsedData == null || parsedData.length < data.length) { 487 return false; 488 } 489 if (dataMask == null) { 490 for (int i = 0; i < data.length; ++i) { 491 if (parsedData[i] != data[i]) { 492 return false; 493 } 494 } 495 return true; 496 } 497 for (int i = 0; i < data.length; ++i) { 498 if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { 499 return false; 500 } 501 } 502 return true; 503 } 504 505 @Override toString()506 public String toString() { 507 return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" 508 + mDeviceAddress 509 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask 510 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid 511 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask 512 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData=" 513 + Arrays.toString(mServiceData) + ", mServiceDataMask=" 514 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId 515 + ", mManufacturerData=" + Arrays.toString(mManufacturerData) 516 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]"; 517 } 518 519 @Override hashCode()520 public int hashCode() { 521 return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, 522 Arrays.hashCode(mManufacturerData), 523 Arrays.hashCode(mManufacturerDataMask), 524 mServiceDataUuid, 525 Arrays.hashCode(mServiceData), 526 Arrays.hashCode(mServiceDataMask), 527 mServiceUuid, mServiceUuidMask, 528 mServiceSolicitationUuid, mServiceSolicitationUuidMask); 529 } 530 531 @Override equals(@ullable Object obj)532 public boolean equals(@Nullable Object obj) { 533 if (this == obj) { 534 return true; 535 } 536 if (obj == null || getClass() != obj.getClass()) { 537 return false; 538 } 539 ScanFilter other = (ScanFilter) obj; 540 return Objects.equals(mDeviceName, other.mDeviceName) 541 && Objects.equals(mDeviceAddress, other.mDeviceAddress) 542 && mManufacturerId == other.mManufacturerId 543 && Objects.deepEquals(mManufacturerData, other.mManufacturerData) 544 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) 545 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid) 546 && Objects.deepEquals(mServiceData, other.mServiceData) 547 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) 548 && Objects.equals(mServiceUuid, other.mServiceUuid) 549 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask) 550 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid) 551 && Objects.equals(mServiceSolicitationUuidMask, 552 other.mServiceSolicitationUuidMask); 553 } 554 555 /** 556 * Checks if the scanfilter is empty 557 * 558 * @hide 559 */ isAllFieldsEmpty()560 public boolean isAllFieldsEmpty() { 561 return EMPTY.equals(this); 562 } 563 564 /** 565 * Builder class for {@link ScanFilter}. 566 */ 567 public static final class Builder { 568 569 /** 570 * @hide 571 */ 572 @SystemApi 573 public static final int LEN_IRK_OCTETS = 16; 574 575 private String mDeviceName; 576 private String mDeviceAddress; 577 private @AddressType int mAddressType = BluetoothDevice.ADDRESS_TYPE_PUBLIC; 578 private byte[] mIrk; 579 580 private ParcelUuid mServiceUuid; 581 private ParcelUuid mUuidMask; 582 583 private ParcelUuid mServiceSolicitationUuid; 584 private ParcelUuid mServiceSolicitationUuidMask; 585 586 private ParcelUuid mServiceDataUuid; 587 private byte[] mServiceData; 588 private byte[] mServiceDataMask; 589 590 private int mManufacturerId = -1; 591 private byte[] mManufacturerData; 592 private byte[] mManufacturerDataMask; 593 594 /** 595 * Set filter on device name. 596 */ setDeviceName(String deviceName)597 public Builder setDeviceName(String deviceName) { 598 mDeviceName = deviceName; 599 return this; 600 } 601 602 /** 603 * Set filter on device address. 604 * 605 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 606 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 607 * BluetoothAdapter#checkBluetoothAddress}. The @AddressType is defaulted to {@link 608 * BluetoothDevice#ADDRESS_TYPE_PUBLIC} 609 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. 610 */ setDeviceAddress(String deviceAddress)611 public Builder setDeviceAddress(String deviceAddress) { 612 if (deviceAddress == null) { 613 mDeviceAddress = deviceAddress; 614 return this; 615 } 616 return setDeviceAddress(deviceAddress, BluetoothDevice.ADDRESS_TYPE_PUBLIC); 617 } 618 619 /** 620 * Set filter on Address with AddressType 621 * 622 * <p>This key is used to resolve a private address from a public address. 623 * 624 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 625 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 626 * BluetoothAdapter#checkBluetoothAddress}. May be any type of address. 627 * @param addressType indication of the type of address 628 * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} 629 * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} 630 * 631 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. 632 * @throws IllegalArgumentException If the {@code addressType} is invalid length 633 * @throws NullPointerException if {@code deviceAddress} is null. 634 * 635 * @hide 636 */ 637 @NonNull 638 @SystemApi setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType)639 public Builder setDeviceAddress(@NonNull String deviceAddress, 640 @AddressType int addressType) { 641 return setDeviceAddressInternal(deviceAddress, addressType, null); 642 } 643 644 /** 645 * Set filter on Address with AddressType and the Identity Resolving Key (IRK). 646 * 647 * <p>The IRK is used to resolve a {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} from 648 * a PRIVATE_ADDRESS type. 649 * 650 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 651 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 652 * BluetoothAdapter#checkBluetoothAddress}. This Address type must only be PUBLIC OR RANDOM 653 * STATIC. 654 * @param addressType indication of the type of address 655 * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} 656 * or {@link BluetoothDevice#ADDRESS_TYPE_RANDOM} 657 * @param irk non-null byte array representing the Identity Resolving Key 658 * 659 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. 660 * @throws IllegalArgumentException if the {@code irk} is invalid length. 661 * @throws IllegalArgumentException If the {@code addressType} is invalid length or is not 662 * PUBLIC or RANDOM STATIC when an IRK is present. 663 * @throws NullPointerException if {@code deviceAddress} or {@code irk} is null. 664 * 665 * @hide 666 */ 667 @NonNull 668 @SystemApi setDeviceAddress(@onNull String deviceAddress, @AddressType int addressType, @NonNull byte[] irk)669 public Builder setDeviceAddress(@NonNull String deviceAddress, 670 @AddressType int addressType, 671 @NonNull byte[] irk) { 672 requireNonNull(irk); 673 if (irk.length != LEN_IRK_OCTETS) { 674 throw new IllegalArgumentException("'irk' is invalid length!"); 675 } 676 return setDeviceAddressInternal(deviceAddress, addressType, irk); 677 } 678 679 /** 680 * Set filter on Address with AddressType and the Identity Resolving Key (IRK). 681 * 682 * <p>Internal setter for the device address 683 * 684 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 685 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 686 * BluetoothAdapter#checkBluetoothAddress}. 687 * @param addressType indication of the type of address 688 * e.g. {@link BluetoothDevice#ADDRESS_TYPE_PUBLIC} 689 * @param irk non-null byte array representing the Identity Resolving Address; nullable 690 * internally. 691 * 692 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. 693 * @throws IllegalArgumentException If the {@code addressType} is invalid length. 694 * @throws NullPointerException if {@code deviceAddress} is null. 695 * 696 * @hide 697 */ 698 @NonNull setDeviceAddressInternal(@onNull String deviceAddress, @AddressType int addressType, @Nullable byte[] irk)699 private Builder setDeviceAddressInternal(@NonNull String deviceAddress, 700 @AddressType int addressType, 701 @Nullable byte[] irk) { 702 703 // Make sure our deviceAddress is valid! 704 requireNonNull(deviceAddress); 705 if (!BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { 706 throw new IllegalArgumentException("invalid device address " + deviceAddress); 707 } 708 709 // Verify type range 710 if (addressType < BluetoothDevice.ADDRESS_TYPE_PUBLIC 711 || addressType > BluetoothDevice.ADDRESS_TYPE_RANDOM) { 712 throw new IllegalArgumentException("'addressType' is invalid!"); 713 } 714 715 // IRK can only be used for a PUBLIC or RANDOM (STATIC) Address. 716 if (addressType == BluetoothDevice.ADDRESS_TYPE_RANDOM) { 717 // Don't want a bad combination of address and irk! 718 if (irk != null) { 719 // Since there are 3 possible RANDOM subtypes we must check to make sure 720 // the correct type of address is used. 721 if (!BluetoothAdapter.isAddressRandomStatic(deviceAddress)) { 722 throw new IllegalArgumentException( 723 "Invalid combination: IRK requires either a PUBLIC or " 724 + "RANDOM (STATIC) Address"); 725 } 726 } 727 } 728 729 // PUBLIC doesn't require extra work 730 // Without an IRK any address may be accepted 731 732 mDeviceAddress = deviceAddress; 733 mAddressType = addressType; 734 mIrk = irk; 735 return this; 736 } 737 738 /** 739 * Set filter on service uuid. 740 */ setServiceUuid(ParcelUuid serviceUuid)741 public Builder setServiceUuid(ParcelUuid serviceUuid) { 742 mServiceUuid = serviceUuid; 743 mUuidMask = null; // clear uuid mask 744 return this; 745 } 746 747 /** 748 * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the 749 * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the 750 * bit in {@code serviceUuid}, and 0 to ignore that bit. 751 * 752 * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code 753 * uuidMask} is not {@code null}. 754 */ setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)755 public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { 756 if (mUuidMask != null && mServiceUuid == null) { 757 throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); 758 } 759 mServiceUuid = serviceUuid; 760 mUuidMask = uuidMask; 761 return this; 762 } 763 764 765 /** 766 * Set filter on service solicitation uuid. 767 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)768 public @NonNull Builder setServiceSolicitationUuid( 769 @Nullable ParcelUuid serviceSolicitationUuid) { 770 mServiceSolicitationUuid = serviceSolicitationUuid; 771 if (serviceSolicitationUuid == null) { 772 mServiceSolicitationUuidMask = null; 773 } 774 return this; 775 } 776 777 778 /** 779 * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the 780 * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to 781 * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to 782 * ignore that bit. 783 * 784 * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null. 785 * @param solicitationUuidMask can be null or a mask with no restriction. 786 * 787 * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but 788 * {@code serviceSolicitationUuidMask} is not {@code null}. 789 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)790 public @NonNull Builder setServiceSolicitationUuid( 791 @Nullable ParcelUuid serviceSolicitationUuid, 792 @Nullable ParcelUuid solicitationUuidMask) { 793 if (solicitationUuidMask != null && serviceSolicitationUuid == null) { 794 throw new IllegalArgumentException( 795 "SolicitationUuid is null while SolicitationUuidMask is not null!"); 796 } 797 mServiceSolicitationUuid = serviceSolicitationUuid; 798 mServiceSolicitationUuidMask = solicitationUuidMask; 799 return this; 800 } 801 802 /** 803 * Set filtering on service data. 804 * 805 * @throws IllegalArgumentException If {@code serviceDataUuid} is null. 806 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)807 public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { 808 if (serviceDataUuid == null) { 809 throw new IllegalArgumentException("serviceDataUuid is null"); 810 } 811 mServiceDataUuid = serviceDataUuid; 812 mServiceData = serviceData; 813 mServiceDataMask = null; // clear service data mask 814 return this; 815 } 816 817 /** 818 * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to 819 * match the one in service data, otherwise set it to 0 to ignore that bit. 820 * <p> 821 * The {@code serviceDataMask} must have the same length of the {@code serviceData}. 822 * 823 * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code 824 * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code 825 * serviceDataMask} and {@code serviceData} has different length. 826 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)827 public Builder setServiceData(ParcelUuid serviceDataUuid, 828 byte[] serviceData, byte[] serviceDataMask) { 829 if (serviceDataUuid == null) { 830 throw new IllegalArgumentException("serviceDataUuid is null"); 831 } 832 if (mServiceDataMask != null) { 833 if (mServiceData == null) { 834 throw new IllegalArgumentException( 835 "serviceData is null while serviceDataMask is not null"); 836 } 837 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two 838 // byte array need to be the same. 839 if (mServiceData.length != mServiceDataMask.length) { 840 throw new IllegalArgumentException( 841 "size mismatch for service data and service data mask"); 842 } 843 } 844 mServiceDataUuid = serviceDataUuid; 845 mServiceData = serviceData; 846 mServiceDataMask = serviceDataMask; 847 return this; 848 } 849 850 /** 851 * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. 852 * 853 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. 854 */ setManufacturerData(int manufacturerId, byte[] manufacturerData)855 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { 856 if (manufacturerData != null && manufacturerId < 0) { 857 throw new IllegalArgumentException("invalid manufacture id"); 858 } 859 mManufacturerId = manufacturerId; 860 mManufacturerData = manufacturerData; 861 mManufacturerDataMask = null; // clear manufacturer data mask 862 return this; 863 } 864 865 /** 866 * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs 867 * to match the one in manufacturer data, otherwise set it to 0. 868 * <p> 869 * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. 870 * 871 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code 872 * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code 873 * manufacturerData} and {@code manufacturerDataMask} have different length. 874 */ setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)875 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, 876 byte[] manufacturerDataMask) { 877 if (manufacturerData != null && manufacturerId < 0) { 878 throw new IllegalArgumentException("invalid manufacture id"); 879 } 880 if (mManufacturerDataMask != null) { 881 if (mManufacturerData == null) { 882 throw new IllegalArgumentException( 883 "manufacturerData is null while manufacturerDataMask is not null"); 884 } 885 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths 886 // of the two byte array need to be the same. 887 if (mManufacturerData.length != mManufacturerDataMask.length) { 888 throw new IllegalArgumentException( 889 "size mismatch for manufacturerData and manufacturerDataMask"); 890 } 891 } 892 mManufacturerId = manufacturerId; 893 mManufacturerData = manufacturerData; 894 mManufacturerDataMask = manufacturerDataMask; 895 return this; 896 } 897 898 /** 899 * Build {@link ScanFilter}. 900 * 901 * @throws IllegalArgumentException If the filter cannot be built. 902 */ build()903 public ScanFilter build() { 904 return new ScanFilter(mDeviceName, mDeviceAddress, 905 mServiceUuid, mUuidMask, mServiceSolicitationUuid, 906 mServiceSolicitationUuidMask, 907 mServiceDataUuid, mServiceData, mServiceDataMask, 908 mManufacturerId, mManufacturerData, mManufacturerDataMask, 909 mAddressType, mIrk); 910 } 911 } 912 } 913