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