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