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