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 android.annotation.NonNull; 20 import android.annotation.Nullable; 21 import android.bluetooth.BluetoothAdapter; 22 import android.bluetooth.BluetoothDevice; 23 import android.os.Parcel; 24 import android.os.ParcelUuid; 25 import android.os.Parcelable; 26 27 import com.android.internal.util.BitUtils; 28 29 import java.util.Arrays; 30 import java.util.List; 31 import java.util.Objects; 32 import java.util.UUID; 33 34 /** 35 * Criteria for filtering result from Bluetooth LE scans. A {@link ScanFilter} allows clients to 36 * restrict scan results to only those that are of interest to them. 37 * <p> 38 * Current filtering on the following fields are supported: 39 * <li>Service UUIDs which identify the bluetooth gatt services running on the device. 40 * <li>Name of remote Bluetooth LE device. 41 * <li>Mac address of the remote device. 42 * <li>Service data which is the data associated with a service. 43 * <li>Manufacturer specific data which is the data associated with a particular manufacturer. 44 * 45 * @see ScanResult 46 * @see BluetoothLeScanner 47 */ 48 public final class ScanFilter implements Parcelable { 49 50 @Nullable 51 private final String mDeviceName; 52 53 @Nullable 54 private final String mDeviceAddress; 55 56 @Nullable 57 private final ParcelUuid mServiceUuid; 58 @Nullable 59 private final ParcelUuid mServiceUuidMask; 60 61 @Nullable 62 private final ParcelUuid mServiceSolicitationUuid; 63 @Nullable 64 private final ParcelUuid mServiceSolicitationUuidMask; 65 66 @Nullable 67 private final ParcelUuid mServiceDataUuid; 68 @Nullable 69 private final byte[] mServiceData; 70 @Nullable 71 private final byte[] mServiceDataMask; 72 73 private final int mManufacturerId; 74 @Nullable 75 private final byte[] mManufacturerData; 76 @Nullable 77 private final byte[] mManufacturerDataMask; 78 79 /** @hide */ 80 public static final ScanFilter EMPTY = new ScanFilter.Builder().build(); 81 82 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)83 private ScanFilter(String name, String deviceAddress, ParcelUuid uuid, 84 ParcelUuid uuidMask, ParcelUuid solicitationUuid, 85 ParcelUuid solicitationUuidMask, ParcelUuid serviceDataUuid, 86 byte[] serviceData, byte[] serviceDataMask, 87 int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask) { 88 mDeviceName = name; 89 mServiceUuid = uuid; 90 mServiceUuidMask = uuidMask; 91 mServiceSolicitationUuid = solicitationUuid; 92 mServiceSolicitationUuidMask = solicitationUuidMask; 93 mDeviceAddress = deviceAddress; 94 mServiceDataUuid = serviceDataUuid; 95 mServiceData = serviceData; 96 mServiceDataMask = serviceDataMask; 97 mManufacturerId = manufacturerId; 98 mManufacturerData = manufacturerData; 99 mManufacturerDataMask = manufacturerDataMask; 100 } 101 102 @Override describeContents()103 public int describeContents() { 104 return 0; 105 } 106 107 @Override writeToParcel(Parcel dest, int flags)108 public void writeToParcel(Parcel dest, int flags) { 109 dest.writeInt(mDeviceName == null ? 0 : 1); 110 if (mDeviceName != null) { 111 dest.writeString(mDeviceName); 112 } 113 dest.writeInt(mDeviceAddress == null ? 0 : 1); 114 if (mDeviceAddress != null) { 115 dest.writeString(mDeviceAddress); 116 } 117 dest.writeInt(mServiceUuid == null ? 0 : 1); 118 if (mServiceUuid != null) { 119 dest.writeParcelable(mServiceUuid, flags); 120 dest.writeInt(mServiceUuidMask == null ? 0 : 1); 121 if (mServiceUuidMask != null) { 122 dest.writeParcelable(mServiceUuidMask, flags); 123 } 124 } 125 dest.writeInt(mServiceSolicitationUuid == null ? 0 : 1); 126 if (mServiceSolicitationUuid != null) { 127 dest.writeParcelable(mServiceSolicitationUuid, flags); 128 dest.writeInt(mServiceSolicitationUuidMask == null ? 0 : 1); 129 if (mServiceSolicitationUuidMask != null) { 130 dest.writeParcelable(mServiceSolicitationUuidMask, flags); 131 } 132 } 133 dest.writeInt(mServiceDataUuid == null ? 0 : 1); 134 if (mServiceDataUuid != null) { 135 dest.writeParcelable(mServiceDataUuid, flags); 136 dest.writeInt(mServiceData == null ? 0 : 1); 137 if (mServiceData != null) { 138 dest.writeInt(mServiceData.length); 139 dest.writeByteArray(mServiceData); 140 141 dest.writeInt(mServiceDataMask == null ? 0 : 1); 142 if (mServiceDataMask != null) { 143 dest.writeInt(mServiceDataMask.length); 144 dest.writeByteArray(mServiceDataMask); 145 } 146 } 147 } 148 dest.writeInt(mManufacturerId); 149 dest.writeInt(mManufacturerData == null ? 0 : 1); 150 if (mManufacturerData != null) { 151 dest.writeInt(mManufacturerData.length); 152 dest.writeByteArray(mManufacturerData); 153 154 dest.writeInt(mManufacturerDataMask == null ? 0 : 1); 155 if (mManufacturerDataMask != null) { 156 dest.writeInt(mManufacturerDataMask.length); 157 dest.writeByteArray(mManufacturerDataMask); 158 } 159 } 160 } 161 162 /** 163 * A {@link android.os.Parcelable.Creator} to create {@link ScanFilter} from parcel. 164 */ 165 public static final @android.annotation.NonNull Creator<ScanFilter> CREATOR = 166 new Creator<ScanFilter>() { 167 168 @Override 169 public ScanFilter[] newArray(int size) { 170 return new ScanFilter[size]; 171 } 172 173 @Override 174 public ScanFilter createFromParcel(Parcel in) { 175 Builder builder = new Builder(); 176 if (in.readInt() == 1) { 177 builder.setDeviceName(in.readString()); 178 } 179 if (in.readInt() == 1) { 180 builder.setDeviceAddress(in.readString()); 181 } 182 if (in.readInt() == 1) { 183 ParcelUuid uuid = in.readParcelable(ParcelUuid.class.getClassLoader()); 184 builder.setServiceUuid(uuid); 185 if (in.readInt() == 1) { 186 ParcelUuid uuidMask = in.readParcelable( 187 ParcelUuid.class.getClassLoader()); 188 builder.setServiceUuid(uuid, uuidMask); 189 } 190 } 191 if (in.readInt() == 1) { 192 ParcelUuid solicitationUuid = in.readParcelable( 193 ParcelUuid.class.getClassLoader()); 194 builder.setServiceSolicitationUuid(solicitationUuid); 195 if (in.readInt() == 1) { 196 ParcelUuid solicitationUuidMask = in.readParcelable( 197 ParcelUuid.class.getClassLoader()); 198 builder.setServiceSolicitationUuid(solicitationUuid, 199 solicitationUuidMask); 200 } 201 } 202 if (in.readInt() == 1) { 203 ParcelUuid servcieDataUuid = 204 in.readParcelable(ParcelUuid.class.getClassLoader()); 205 if (in.readInt() == 1) { 206 int serviceDataLength = in.readInt(); 207 byte[] serviceData = new byte[serviceDataLength]; 208 in.readByteArray(serviceData); 209 if (in.readInt() == 0) { 210 builder.setServiceData(servcieDataUuid, serviceData); 211 } else { 212 int serviceDataMaskLength = in.readInt(); 213 byte[] serviceDataMask = new byte[serviceDataMaskLength]; 214 in.readByteArray(serviceDataMask); 215 builder.setServiceData( 216 servcieDataUuid, serviceData, serviceDataMask); 217 } 218 } 219 } 220 221 int manufacturerId = in.readInt(); 222 if (in.readInt() == 1) { 223 int manufacturerDataLength = in.readInt(); 224 byte[] manufacturerData = new byte[manufacturerDataLength]; 225 in.readByteArray(manufacturerData); 226 if (in.readInt() == 0) { 227 builder.setManufacturerData(manufacturerId, manufacturerData); 228 } else { 229 int manufacturerDataMaskLength = in.readInt(); 230 byte[] manufacturerDataMask = new byte[manufacturerDataMaskLength]; 231 in.readByteArray(manufacturerDataMask); 232 builder.setManufacturerData(manufacturerId, manufacturerData, 233 manufacturerDataMask); 234 } 235 } 236 237 return builder.build(); 238 } 239 }; 240 241 /** 242 * Returns the filter set the device name field of Bluetooth advertisement data. 243 */ 244 @Nullable getDeviceName()245 public String getDeviceName() { 246 return mDeviceName; 247 } 248 249 /** 250 * Returns the filter set on the service uuid. 251 */ 252 @Nullable getServiceUuid()253 public ParcelUuid getServiceUuid() { 254 return mServiceUuid; 255 } 256 257 @Nullable getServiceUuidMask()258 public ParcelUuid getServiceUuidMask() { 259 return mServiceUuidMask; 260 } 261 262 /** 263 * Returns the filter set on the service Solicitation uuid. 264 */ 265 @Nullable getServiceSolicitationUuid()266 public ParcelUuid getServiceSolicitationUuid() { 267 return mServiceSolicitationUuid; 268 } 269 270 /** 271 * Returns the filter set on the service Solicitation uuid mask. 272 */ 273 @Nullable getServiceSolicitationUuidMask()274 public ParcelUuid getServiceSolicitationUuidMask() { 275 return mServiceSolicitationUuidMask; 276 } 277 278 @Nullable getDeviceAddress()279 public String getDeviceAddress() { 280 return mDeviceAddress; 281 } 282 283 @Nullable getServiceData()284 public byte[] getServiceData() { 285 return mServiceData; 286 } 287 288 @Nullable getServiceDataMask()289 public byte[] getServiceDataMask() { 290 return mServiceDataMask; 291 } 292 293 @Nullable getServiceDataUuid()294 public ParcelUuid getServiceDataUuid() { 295 return mServiceDataUuid; 296 } 297 298 /** 299 * Returns the manufacturer id. -1 if the manufacturer filter is not set. 300 */ getManufacturerId()301 public int getManufacturerId() { 302 return mManufacturerId; 303 } 304 305 @Nullable getManufacturerData()306 public byte[] getManufacturerData() { 307 return mManufacturerData; 308 } 309 310 @Nullable getManufacturerDataMask()311 public byte[] getManufacturerDataMask() { 312 return mManufacturerDataMask; 313 } 314 315 /** 316 * Check if the scan filter matches a {@code scanResult}. A scan result is considered as a match 317 * if it matches all the field filters. 318 */ matches(ScanResult scanResult)319 public boolean matches(ScanResult scanResult) { 320 if (scanResult == null) { 321 return false; 322 } 323 BluetoothDevice device = scanResult.getDevice(); 324 // Device match. 325 if (mDeviceAddress != null 326 && (device == null || !mDeviceAddress.equals(device.getAddress()))) { 327 return false; 328 } 329 330 ScanRecord scanRecord = scanResult.getScanRecord(); 331 332 // Scan record is null but there exist filters on it. 333 if (scanRecord == null 334 && (mDeviceName != null || mServiceUuid != null || mManufacturerData != null 335 || mServiceData != null || mServiceSolicitationUuid != null)) { 336 return false; 337 } 338 339 // Local name match. 340 if (mDeviceName != null && !mDeviceName.equals(scanRecord.getDeviceName())) { 341 return false; 342 } 343 344 // UUID match. 345 if (mServiceUuid != null && !matchesServiceUuids(mServiceUuid, mServiceUuidMask, 346 scanRecord.getServiceUuids())) { 347 return false; 348 } 349 350 // solicitation UUID match. 351 if (mServiceSolicitationUuid != null && !matchesServiceSolicitationUuids( 352 mServiceSolicitationUuid, mServiceSolicitationUuidMask, 353 scanRecord.getServiceSolicitationUuids())) { 354 return false; 355 } 356 357 // Service data match 358 if (mServiceDataUuid != null) { 359 if (!matchesPartialData(mServiceData, mServiceDataMask, 360 scanRecord.getServiceData(mServiceDataUuid))) { 361 return false; 362 } 363 } 364 365 // Manufacturer data match. 366 if (mManufacturerId >= 0) { 367 if (!matchesPartialData(mManufacturerData, mManufacturerDataMask, 368 scanRecord.getManufacturerSpecificData(mManufacturerId))) { 369 return false; 370 } 371 } 372 // All filters match. 373 return true; 374 } 375 376 /** 377 * Check if the uuid pattern is contained in a list of parcel uuids. 378 * 379 * @hide 380 */ matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, List<ParcelUuid> uuids)381 public static boolean matchesServiceUuids(ParcelUuid uuid, ParcelUuid parcelUuidMask, 382 List<ParcelUuid> uuids) { 383 if (uuid == null) { 384 return true; 385 } 386 if (uuids == null) { 387 return false; 388 } 389 390 for (ParcelUuid parcelUuid : uuids) { 391 UUID uuidMask = parcelUuidMask == null ? null : parcelUuidMask.getUuid(); 392 if (matchesServiceUuid(uuid.getUuid(), uuidMask, parcelUuid.getUuid())) { 393 return true; 394 } 395 } 396 return false; 397 } 398 399 // Check if the uuid pattern matches the particular service uuid. matchesServiceUuid(UUID uuid, UUID mask, UUID data)400 private static boolean matchesServiceUuid(UUID uuid, UUID mask, UUID data) { 401 return BitUtils.maskedEquals(data, uuid, mask); 402 } 403 404 /** 405 * Check if the solicitation uuid pattern is contained in a list of parcel uuids. 406 * 407 */ matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids)408 private static boolean matchesServiceSolicitationUuids(ParcelUuid solicitationUuid, 409 ParcelUuid parcelSolicitationUuidMask, List<ParcelUuid> solicitationUuids) { 410 if (solicitationUuid == null) { 411 return true; 412 } 413 if (solicitationUuids == null) { 414 return false; 415 } 416 417 for (ParcelUuid parcelSolicitationUuid : solicitationUuids) { 418 UUID solicitationUuidMask = parcelSolicitationUuidMask == null 419 ? null : parcelSolicitationUuidMask.getUuid(); 420 if (matchesServiceUuid(solicitationUuid.getUuid(), solicitationUuidMask, 421 parcelSolicitationUuid.getUuid())) { 422 return true; 423 } 424 } 425 return false; 426 } 427 428 // Check if the solicitation uuid pattern matches the particular service solicitation uuid. matchesServiceSolicitationUuid(UUID solicitationUuid, UUID solicitationUuidMask, UUID data)429 private static boolean matchesServiceSolicitationUuid(UUID solicitationUuid, 430 UUID solicitationUuidMask, UUID data) { 431 return BitUtils.maskedEquals(data, solicitationUuid, solicitationUuidMask); 432 } 433 434 // Check whether the data pattern matches the parsed data. matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData)435 private boolean matchesPartialData(byte[] data, byte[] dataMask, byte[] parsedData) { 436 if (parsedData == null || parsedData.length < data.length) { 437 return false; 438 } 439 if (dataMask == null) { 440 for (int i = 0; i < data.length; ++i) { 441 if (parsedData[i] != data[i]) { 442 return false; 443 } 444 } 445 return true; 446 } 447 for (int i = 0; i < data.length; ++i) { 448 if ((dataMask[i] & parsedData[i]) != (dataMask[i] & data[i])) { 449 return false; 450 } 451 } 452 return true; 453 } 454 455 @Override toString()456 public String toString() { 457 return "BluetoothLeScanFilter [mDeviceName=" + mDeviceName + ", mDeviceAddress=" 458 + mDeviceAddress 459 + ", mUuid=" + mServiceUuid + ", mUuidMask=" + mServiceUuidMask 460 + ", mServiceSolicitationUuid=" + mServiceSolicitationUuid 461 + ", mServiceSolicitationUuidMask=" + mServiceSolicitationUuidMask 462 + ", mServiceDataUuid=" + Objects.toString(mServiceDataUuid) + ", mServiceData=" 463 + Arrays.toString(mServiceData) + ", mServiceDataMask=" 464 + Arrays.toString(mServiceDataMask) + ", mManufacturerId=" + mManufacturerId 465 + ", mManufacturerData=" + Arrays.toString(mManufacturerData) 466 + ", mManufacturerDataMask=" + Arrays.toString(mManufacturerDataMask) + "]"; 467 } 468 469 @Override hashCode()470 public int hashCode() { 471 return Objects.hash(mDeviceName, mDeviceAddress, mManufacturerId, 472 Arrays.hashCode(mManufacturerData), 473 Arrays.hashCode(mManufacturerDataMask), 474 mServiceDataUuid, 475 Arrays.hashCode(mServiceData), 476 Arrays.hashCode(mServiceDataMask), 477 mServiceUuid, mServiceUuidMask, 478 mServiceSolicitationUuid, mServiceSolicitationUuidMask); 479 } 480 481 @Override equals(Object obj)482 public boolean equals(Object obj) { 483 if (this == obj) { 484 return true; 485 } 486 if (obj == null || getClass() != obj.getClass()) { 487 return false; 488 } 489 ScanFilter other = (ScanFilter) obj; 490 return Objects.equals(mDeviceName, other.mDeviceName) 491 && Objects.equals(mDeviceAddress, other.mDeviceAddress) 492 && mManufacturerId == other.mManufacturerId 493 && Objects.deepEquals(mManufacturerData, other.mManufacturerData) 494 && Objects.deepEquals(mManufacturerDataMask, other.mManufacturerDataMask) 495 && Objects.equals(mServiceDataUuid, other.mServiceDataUuid) 496 && Objects.deepEquals(mServiceData, other.mServiceData) 497 && Objects.deepEquals(mServiceDataMask, other.mServiceDataMask) 498 && Objects.equals(mServiceUuid, other.mServiceUuid) 499 && Objects.equals(mServiceUuidMask, other.mServiceUuidMask) 500 && Objects.equals(mServiceSolicitationUuid, other.mServiceSolicitationUuid) 501 && Objects.equals(mServiceSolicitationUuidMask, 502 other.mServiceSolicitationUuidMask); 503 } 504 505 /** 506 * Checks if the scanfilter is empty 507 * 508 * @hide 509 */ isAllFieldsEmpty()510 public boolean isAllFieldsEmpty() { 511 return EMPTY.equals(this); 512 } 513 514 /** 515 * Builder class for {@link ScanFilter}. 516 */ 517 public static final class Builder { 518 519 private String mDeviceName; 520 private String mDeviceAddress; 521 522 private ParcelUuid mServiceUuid; 523 private ParcelUuid mUuidMask; 524 525 private ParcelUuid mServiceSolicitationUuid; 526 private ParcelUuid mServiceSolicitationUuidMask; 527 528 private ParcelUuid mServiceDataUuid; 529 private byte[] mServiceData; 530 private byte[] mServiceDataMask; 531 532 private int mManufacturerId = -1; 533 private byte[] mManufacturerData; 534 private byte[] mManufacturerDataMask; 535 536 /** 537 * Set filter on device name. 538 */ setDeviceName(String deviceName)539 public Builder setDeviceName(String deviceName) { 540 mDeviceName = deviceName; 541 return this; 542 } 543 544 /** 545 * Set filter on device address. 546 * 547 * @param deviceAddress The device Bluetooth address for the filter. It needs to be in the 548 * format of "01:02:03:AB:CD:EF". The device address can be validated using {@link 549 * BluetoothAdapter#checkBluetoothAddress}. 550 * @throws IllegalArgumentException If the {@code deviceAddress} is invalid. 551 */ setDeviceAddress(String deviceAddress)552 public Builder setDeviceAddress(String deviceAddress) { 553 if (deviceAddress != null && !BluetoothAdapter.checkBluetoothAddress(deviceAddress)) { 554 throw new IllegalArgumentException("invalid device address " + deviceAddress); 555 } 556 mDeviceAddress = deviceAddress; 557 return this; 558 } 559 560 /** 561 * Set filter on service uuid. 562 */ setServiceUuid(ParcelUuid serviceUuid)563 public Builder setServiceUuid(ParcelUuid serviceUuid) { 564 mServiceUuid = serviceUuid; 565 mUuidMask = null; // clear uuid mask 566 return this; 567 } 568 569 /** 570 * Set filter on partial service uuid. The {@code uuidMask} is the bit mask for the 571 * {@code serviceUuid}. Set any bit in the mask to 1 to indicate a match is needed for the 572 * bit in {@code serviceUuid}, and 0 to ignore that bit. 573 * 574 * @throws IllegalArgumentException If {@code serviceUuid} is {@code null} but {@code 575 * uuidMask} is not {@code null}. 576 */ setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask)577 public Builder setServiceUuid(ParcelUuid serviceUuid, ParcelUuid uuidMask) { 578 if (mUuidMask != null && mServiceUuid == null) { 579 throw new IllegalArgumentException("uuid is null while uuidMask is not null!"); 580 } 581 mServiceUuid = serviceUuid; 582 mUuidMask = uuidMask; 583 return this; 584 } 585 586 587 /** 588 * Set filter on service solicitation uuid. 589 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid)590 public @NonNull Builder setServiceSolicitationUuid( 591 @Nullable ParcelUuid serviceSolicitationUuid) { 592 mServiceSolicitationUuid = serviceSolicitationUuid; 593 if (serviceSolicitationUuid == null) { 594 mServiceSolicitationUuidMask = null; 595 } 596 return this; 597 } 598 599 600 /** 601 * Set filter on partial service Solicitation uuid. The {@code SolicitationUuidMask} is the 602 * bit mask for the {@code serviceSolicitationUuid}. Set any bit in the mask to 1 to 603 * indicate a match is needed for the bit in {@code serviceSolicitationUuid}, and 0 to 604 * ignore that bit. 605 * 606 * @param serviceSolicitationUuid can only be null if solicitationUuidMask is null. 607 * @param solicitationUuidMask can be null or a mask with no restriction. 608 * 609 * @throws IllegalArgumentException If {@code serviceSolicitationUuid} is {@code null} but 610 * {@code serviceSolicitationUuidMask} is not {@code null}. 611 */ setServiceSolicitationUuid( @ullable ParcelUuid serviceSolicitationUuid, @Nullable ParcelUuid solicitationUuidMask)612 public @NonNull Builder setServiceSolicitationUuid( 613 @Nullable ParcelUuid serviceSolicitationUuid, 614 @Nullable ParcelUuid solicitationUuidMask) { 615 if (solicitationUuidMask != null && serviceSolicitationUuid == null) { 616 throw new IllegalArgumentException( 617 "SolicitationUuid is null while SolicitationUuidMask is not null!"); 618 } 619 mServiceSolicitationUuid = serviceSolicitationUuid; 620 mServiceSolicitationUuidMask = solicitationUuidMask; 621 return this; 622 } 623 624 /** 625 * Set filtering on service data. 626 * 627 * @throws IllegalArgumentException If {@code serviceDataUuid} is null. 628 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData)629 public Builder setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData) { 630 if (serviceDataUuid == null) { 631 throw new IllegalArgumentException("serviceDataUuid is null"); 632 } 633 mServiceDataUuid = serviceDataUuid; 634 mServiceData = serviceData; 635 mServiceDataMask = null; // clear service data mask 636 return this; 637 } 638 639 /** 640 * Set partial filter on service data. For any bit in the mask, set it to 1 if it needs to 641 * match the one in service data, otherwise set it to 0 to ignore that bit. 642 * <p> 643 * The {@code serviceDataMask} must have the same length of the {@code serviceData}. 644 * 645 * @throws IllegalArgumentException If {@code serviceDataUuid} is null or {@code 646 * serviceDataMask} is {@code null} while {@code serviceData} is not or {@code 647 * serviceDataMask} and {@code serviceData} has different length. 648 */ setServiceData(ParcelUuid serviceDataUuid, byte[] serviceData, byte[] serviceDataMask)649 public Builder setServiceData(ParcelUuid serviceDataUuid, 650 byte[] serviceData, byte[] serviceDataMask) { 651 if (serviceDataUuid == null) { 652 throw new IllegalArgumentException("serviceDataUuid is null"); 653 } 654 if (mServiceDataMask != null) { 655 if (mServiceData == null) { 656 throw new IllegalArgumentException( 657 "serviceData is null while serviceDataMask is not null"); 658 } 659 // Since the mServiceDataMask is a bit mask for mServiceData, the lengths of the two 660 // byte array need to be the same. 661 if (mServiceData.length != mServiceDataMask.length) { 662 throw new IllegalArgumentException( 663 "size mismatch for service data and service data mask"); 664 } 665 } 666 mServiceDataUuid = serviceDataUuid; 667 mServiceData = serviceData; 668 mServiceDataMask = serviceDataMask; 669 return this; 670 } 671 672 /** 673 * Set filter on on manufacturerData. A negative manufacturerId is considered as invalid id. 674 * 675 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid. 676 */ setManufacturerData(int manufacturerId, byte[] manufacturerData)677 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData) { 678 if (manufacturerData != null && manufacturerId < 0) { 679 throw new IllegalArgumentException("invalid manufacture id"); 680 } 681 mManufacturerId = manufacturerId; 682 mManufacturerData = manufacturerData; 683 mManufacturerDataMask = null; // clear manufacturer data mask 684 return this; 685 } 686 687 /** 688 * Set filter on partial manufacture data. For any bit in the mask, set it the 1 if it needs 689 * to match the one in manufacturer data, otherwise set it to 0. 690 * <p> 691 * The {@code manufacturerDataMask} must have the same length of {@code manufacturerData}. 692 * 693 * @throws IllegalArgumentException If the {@code manufacturerId} is invalid, or {@code 694 * manufacturerData} is null while {@code manufacturerDataMask} is not, or {@code 695 * manufacturerData} and {@code manufacturerDataMask} have different length. 696 */ setManufacturerData(int manufacturerId, byte[] manufacturerData, byte[] manufacturerDataMask)697 public Builder setManufacturerData(int manufacturerId, byte[] manufacturerData, 698 byte[] manufacturerDataMask) { 699 if (manufacturerData != null && manufacturerId < 0) { 700 throw new IllegalArgumentException("invalid manufacture id"); 701 } 702 if (mManufacturerDataMask != null) { 703 if (mManufacturerData == null) { 704 throw new IllegalArgumentException( 705 "manufacturerData is null while manufacturerDataMask is not null"); 706 } 707 // Since the mManufacturerDataMask is a bit mask for mManufacturerData, the lengths 708 // of the two byte array need to be the same. 709 if (mManufacturerData.length != mManufacturerDataMask.length) { 710 throw new IllegalArgumentException( 711 "size mismatch for manufacturerData and manufacturerDataMask"); 712 } 713 } 714 mManufacturerId = manufacturerId; 715 mManufacturerData = manufacturerData; 716 mManufacturerDataMask = manufacturerDataMask; 717 return this; 718 } 719 720 /** 721 * Build {@link ScanFilter}. 722 * 723 * @throws IllegalArgumentException If the filter cannot be built. 724 */ build()725 public ScanFilter build() { 726 return new ScanFilter(mDeviceName, mDeviceAddress, 727 mServiceUuid, mUuidMask, mServiceSolicitationUuid, 728 mServiceSolicitationUuidMask, 729 mServiceDataUuid, mServiceData, mServiceDataMask, 730 mManufacturerId, mManufacturerData, mManufacturerDataMask); 731 } 732 } 733 } 734