From af873b315352787a5490d4f3ddaa1da1056abf59 Mon Sep 17 00:00:00 2001 From: Rohit Sangwan Date: Mon, 1 Sep 2025 13:01:56 +0530 Subject: [PATCH] Implement exclusion filter (#175) * Implement exclusion filter * Fix web filters * Apply Ai Suggestions * Fix scanFilterTest * Resolve Ai comments * Update readme and changelog * Update CHANGELOG.md Co-authored-by: Foti Dim * Update exclusion filter doc * Fix wording --------- Co-authored-by: Foti Dim Co-authored-by: Foti Dim --- CHANGELOG.md | 1 + README.md | 14 ++ example/macos/Podfile | 2 +- example/macos/Podfile.lock | 4 +- .../macos/Runner.xcodeproj/project.pbxproj | 6 +- example/pubspec.lock | 2 +- lib/src/models/scan_filter.dart | 21 ++- .../universal_ble_linux.dart | 4 +- .../universal_ble_pigeon_channel.dart | 10 +- .../universal_ble_web/universal_ble_web.dart | 28 +++- lib/src/utils/universal_ble_filter_util.dart | 108 ++++++++----- test/manufacturer_data_filter_test.dart | 35 ++++- test/scan_filter_test.dart | 143 +++++++++++++++--- 13 files changed, 300 insertions(+), 78 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e141197..260d441 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ ## 0.21.1 * Fix device name resolution on Windows +* Add `exclusionFilters` to filter out devices from scan results ## 0.21.0 * BREAKING CHANGE: `connectionTimeout` argument from `connect`, `isPaired` and `pair` API renamed to `timeout` diff --git a/README.md b/README.md index 32443e4..f939988 100644 --- a/README.md +++ b/README.md @@ -154,6 +154,20 @@ Use the `withNamePrefix` parameter to filter devices by names (case sensitive). List withNamePrefix; ``` +##### Exclusion Filter + +Use exclusion filters to exclude specific devices from scan results: + +```dart +exclusionFilters: [ + ExclusionFilter( + namePrefix: 'EXCLUDED_NAME', + services: ['EXCLUDED_SERVICE_UUID'], + manufacturerDataFilter: [ManufacturerDataFilter(companyIdentifier: 0x004c)], + ), +] +``` + ### Connecting #### Connect diff --git a/example/macos/Podfile b/example/macos/Podfile index c795730..b52666a 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -1,4 +1,4 @@ -platform :osx, '10.14' +platform :osx, '10.15' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 7ec7ff3..bf1d37e 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -21,9 +21,9 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: device_info_plus: 5401765fde0b8d062a2f8eb65510fb17e77cf07f - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 + FlutterMacOS: d0db08ddef1a9af05a5ec4b724367152bb0500b1 universal_ble: cf52a7b3fd2e7c14d6d7262e9fdadb72ab6b88a6 -PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 +PODFILE CHECKSUM: 9ebaf0ce3d369aaa26a9ea0e159195ed94724cf3 COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index ea4127b..0c2e8a5 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -553,7 +553,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; @@ -632,7 +632,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = macosx; @@ -679,7 +679,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - MACOSX_DEPLOYMENT_TARGET = 10.14; + MACOSX_DEPLOYMENT_TARGET = 10.15; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; diff --git a/example/pubspec.lock b/example/pubspec.lock index 63de3a6..da0e99c 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -402,7 +402,7 @@ packages: path: ".." relative: true source: path - version: "0.21.0" + version: "0.21.1" vector_math: dependency: transitive description: diff --git a/lib/src/models/scan_filter.dart b/lib/src/models/scan_filter.dart index 31455ef..24e85b9 100644 --- a/lib/src/models/scan_filter.dart +++ b/lib/src/models/scan_filter.dart @@ -4,15 +4,34 @@ class ScanFilter { List withServices; List withManufacturerData; List withNamePrefix; + List exclusionFilters; ScanFilter({ this.withServices = const [], this.withManufacturerData = const [], this.withNamePrefix = const [], + this.exclusionFilters = const [], }); @override String toString() { - return 'ScanFilter(withServices: $withServices, withManufacturerData: $withManufacturerData, withNamePrefix: $withNamePrefix)'; + return 'ScanFilter(withServices: $withServices, withManufacturerData: $withManufacturerData, withNamePrefix: $withNamePrefix, exclusionFilters: $exclusionFilters)'; } } + +class ExclusionFilter { + List services; + List manufacturerDataFilter; + String? namePrefix; + + ExclusionFilter({ + this.services = const [], + this.manufacturerDataFilter = const [], + this.namePrefix, + }); + + bool get hasValidFilters => + services.isNotEmpty || + manufacturerDataFilter.isNotEmpty || + (namePrefix?.isNotEmpty ?? false); +} diff --git a/lib/src/universal_ble_linux/universal_ble_linux.dart b/lib/src/universal_ble_linux/universal_ble_linux.dart index e854b60..e204849 100644 --- a/lib/src/universal_ble_linux/universal_ble_linux.dart +++ b/lib/src/universal_ble_linux/universal_ble_linux.dart @@ -465,7 +465,7 @@ class UniversalBleLinux extends UniversalBlePlatform { void _onDeviceAdd(BlueZDevice device) { BleDevice bleDevice = device.toBleDevice(); - if (!_bleFilter.matchesDevice(bleDevice)) { + if (!_bleFilter.shouldAcceptDevice(bleDevice)) { return; } @@ -485,7 +485,7 @@ class UniversalBleLinux extends UniversalBlePlatform { e.contains(BluezProperty.manufacturerData) || e.contains(BluezProperty.uuids)) .listen((_) { - if (_bleFilter.matchesDevice(bleDevice)) { + if (_bleFilter.shouldAcceptDevice(bleDevice)) { updateScanResult(device.toBleDevice()); } }); diff --git a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart index 1a5fd86..6dc1666 100644 --- a/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart +++ b/lib/src/universal_ble_pigeon/universal_ble_pigeon_channel.dart @@ -1,12 +1,14 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:universal_ble/src/universal_ble_pigeon/universal_ble.g.dart'; +import 'package:universal_ble/src/utils/universal_ble_filter_util.dart'; import 'package:universal_ble/universal_ble.dart'; class UniversalBlePigeonChannel extends UniversalBlePlatform { static UniversalBlePigeonChannel? _instance; static UniversalBlePigeonChannel get instance => _instance ??= UniversalBlePigeonChannel._(); + late final UniversalBleFilterUtil _bleFilter = UniversalBleFilterUtil(); UniversalBlePigeonChannel._() { _setupListeners(); @@ -42,6 +44,7 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { PlatformConfig? platformConfig, }) async { await _ensureInitialized(); + _bleFilter.scanFilter = scanFilter; await _channel.startScan( scanFilter.toUniversalScanFilter(), ); @@ -136,7 +139,12 @@ class UniversalBlePigeonChannel extends UniversalBlePlatform { /// To set listeners void _setupListeners() { UniversalBleCallbackChannel.setUp(_UniversalBleCallbackHandler( - scanResult: updateScanResult, + scanResult: (bleDevice) { + // Only check for exclusion filter here, + // scan filter handled natively on platform side + if (_bleFilter.matchesExclusionFilter(bleDevice)) return; + updateScanResult(bleDevice); + }, availabilityChange: updateAvailability, connectionChanged: updateConnection, valueChanged: updateCharacteristicValue, diff --git a/lib/src/universal_ble_web/universal_ble_web.dart b/lib/src/universal_ble_web/universal_ble_web.dart index ec5e1e5..4d02037 100644 --- a/lib/src/universal_ble_web/universal_ble_web.dart +++ b/lib/src/universal_ble_web/universal_ble_web.dart @@ -348,6 +348,7 @@ class UniversalBleWeb extends UniversalBlePlatform { WebOptions? webOptions, ) { List filters = []; + List exclusionFilters = []; List optionalManufacturerData = []; List optionalServices = []; @@ -389,6 +390,25 @@ class UniversalBleWeb extends UniversalBlePlatform { for (var name in scanFilter.withNamePrefix) { filters.add(RequestFilterBuilder(namePrefix: name)); } + + // Add exclusion filters + for (var exclusionFilter in scanFilter.exclusionFilters) { + exclusionFilters.add(RequestFilterBuilder( + services: exclusionFilter.services.isEmpty + ? null + : exclusionFilter.services.toValidUUIDList(), + namePrefix: exclusionFilter.namePrefix, + manufacturerData: exclusionFilter.manufacturerDataFilter.isEmpty + ? null + : exclusionFilter.manufacturerDataFilter.map((e) { + return ManufacturerDataFilterBuilder( + companyIdentifier: e.companyIdentifier, + dataPrefix: e.payloadPrefix, + mask: e.payloadMask, + ); + }).toList(), + )); + } } if (optionalServices.isEmpty) { @@ -396,8 +416,13 @@ class UniversalBleWeb extends UniversalBlePlatform { "OptionalServices list is empty on web, you have to specify services in the ScanFilter in order to be able to access those after connecting", ); } + if (filters.isEmpty && exclusionFilters.isNotEmpty) { + UniversalLogger.logError( + "Web platform requires inclusion filters when using exclusion filters. Please add withServices, withNamePrefix, or withManufacturerData filters.", + ); + } - if (filters.isEmpty) { + if (filters.isEmpty && exclusionFilters.isEmpty) { return RequestOptionsBuilder.acceptAllDevices( optionalServices: optionalServices, optionalManufacturerData: optionalManufacturerData, @@ -407,6 +432,7 @@ class UniversalBleWeb extends UniversalBlePlatform { filters, optionalServices: optionalServices, optionalManufacturerData: optionalManufacturerData, + exclusionFilters: exclusionFilters.isEmpty ? null : exclusionFilters, ); } } diff --git a/lib/src/utils/universal_ble_filter_util.dart b/lib/src/utils/universal_ble_filter_util.dart index e4d3345..174955a 100644 --- a/lib/src/utils/universal_ble_filter_util.dart +++ b/lib/src/utils/universal_ble_filter_util.dart @@ -7,7 +7,42 @@ import 'package:universal_ble/universal_ble.dart'; class UniversalBleFilterUtil { ScanFilter? scanFilter; - bool matchesDevice(BleDevice device) { + bool shouldAcceptDevice(BleDevice device) => + !matchesExclusionFilter(device) && matchedScanFilter(device); + + bool matchesExclusionFilter(BleDevice device) { + final exclusionFilters = scanFilter?.exclusionFilters; + if (exclusionFilters == null || exclusionFilters.isEmpty) return false; + + final deviceName = device.name?.toLowerCase() ?? ''; + final deviceServices = device.services.toValidUUIDList(); + + for (var filter in exclusionFilters) { + if (!filter.hasValidFilters) continue; + + // Check name prefix + final filterNamePrefix = filter.namePrefix?.toLowerCase(); + if (filterNamePrefix != null && filterNamePrefix.isNotEmpty) { + if (!deviceName.startsWith(filterNamePrefix)) continue; + } + // Check services + final filterServices = filter.services.toValidUUIDList(); + if (filterServices.isNotEmpty && + !filterServices.any(deviceServices.contains)) { + continue; + } + // Check manufacturer data + if (!manufacturerDataMatches(filter.manufacturerDataFilter, device)) { + continue; + } + // Device matches an exclusion filter + return true; + } + return false; + } + + /// Returns true if the device matches the filter + bool matchedScanFilter(BleDevice device) { final filter = scanFilter; if (filter == null) return true; @@ -23,66 +58,67 @@ class UniversalBleFilterUtil { } // Else check one of the filter passes - return hasNamePrefixFilter && nameMatches(filter, device) || - hasServiceFilter && servicesMatch(filter, device) || - hasManufacturerDataFilter && manufacturerDataMatches(filter, device); + return (hasNamePrefixFilter && nameMatches(filter, device)) || + (hasServiceFilter && servicesMatch(filter, device)) || + (hasManufacturerDataFilter && + manufacturerDataMatches(filter.withManufacturerData, device)); } bool nameMatches(ScanFilter scanFilter, BleDevice device) { - var namePrefixFilter = scanFilter.withNamePrefix; + final namePrefixFilter = scanFilter.withNamePrefix; if (namePrefixFilter.isEmpty) return true; - String? name = device.name; + final name = device.name; if (name == null || name.isEmpty) return false; return namePrefixFilter.any(name.startsWith); } bool servicesMatch(ScanFilter scanFilter, BleDevice device) { - var serviceFilters = scanFilter.withServices; + final serviceFilters = scanFilter.withServices.toValidUUIDList(); if (serviceFilters.isEmpty) return true; - List serviceUuids = device.services; + final serviceUuids = device.services.toValidUUIDList(); if (serviceUuids.isEmpty) { return false; } return serviceFilters.any(serviceUuids.contains); } - bool manufacturerDataMatches(ScanFilter scanFilter, BleDevice device) { - final manufacturerDataFilters = scanFilter.withManufacturerData; - if (manufacturerDataFilters.isEmpty) return true; + bool manufacturerDataMatches( + List filters, + BleDevice device, + ) { + if (filters.isEmpty) return true; - List deviceDataList = device.manufacturerDataList; + final deviceDataList = device.manufacturerDataList; if (deviceDataList.isEmpty) return false; - return deviceDataList - .any((deviceData) => manufacturerDataFilters.any((filter) { - // Early return if company identifiers don't match - if (filter.companyIdentifier != deviceData.companyId) { - return false; - } + return deviceDataList.any((deviceData) { + return filters.any((filter) { + // Early return if company identifiers don't match + if (filter.companyIdentifier != deviceData.companyId) { + return false; + } - final payloadPrefix = filter.payloadPrefix; - final payload = deviceData.payload; + final payloadPrefix = filter.payloadPrefix; + final payload = deviceData.payload; - // Handle cases where payload prefix is null or empty - if (payloadPrefix == null || payloadPrefix.isEmpty) { - return true; - } + // Handle cases where payload prefix is null or empty + if (payloadPrefix == null || payloadPrefix.isEmpty) { + return true; + } - // Validate payload lengths - if (payload.isEmpty || payloadPrefix.length > payload.length) { - return false; - } + if (payload.isEmpty || payloadPrefix.length > payload.length) { + return false; + } - final filterMask = filter.payloadMask; - - // Choose comparison strategy based on filter mask - return filterMask != null && - filterMask.length == payloadPrefix.length - ? _compareWithMask(payloadPrefix, payload, filterMask) - : _compareWithoutMask(payloadPrefix, payload); - })); + final filterMask = filter.payloadMask; + // Choose comparison strategy based on filter mask + return filterMask != null && filterMask.length == payloadPrefix.length + ? _compareWithMask(payloadPrefix, payload, filterMask) + : _compareWithoutMask(payloadPrefix, payload); + }); + }); } bool _compareWithMask(Uint8List prefix, Uint8List payload, Uint8List mask) { diff --git a/test/manufacturer_data_filter_test.dart b/test/manufacturer_data_filter_test.dart index a84cd44..39c75a1 100644 --- a/test/manufacturer_data_filter_test.dart +++ b/test/manufacturer_data_filter_test.dart @@ -27,7 +27,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), true); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + true); }); test('should not match when device has no manufacturer data', () { @@ -46,7 +49,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), false); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + false); }); test( @@ -69,7 +75,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), true); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + true); }); group('Payload Prefix Tests', () { @@ -94,7 +103,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), true); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + true); }); test('should match with multiple manufacturer data entries', () { @@ -119,7 +131,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), true); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + true); }); }); @@ -146,7 +161,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), true); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + true); }); test('should not match when masked values are different', () { @@ -171,7 +189,10 @@ void main() { services: [], ); - expect(filterUtil.manufacturerDataMatches(scanFilter, device), false); + expect( + filterUtil.manufacturerDataMatches( + scanFilter.withManufacturerData, device), + false); }); }); }); diff --git a/test/scan_filter_test.dart b/test/scan_filter_test.dart index 754d64d..e3a58d0 100644 --- a/test/scan_filter_test.dart +++ b/test/scan_filter_test.dart @@ -9,7 +9,7 @@ void main() { var device1 = BleDevice( deviceId: '1', name: '1_device', - services: ['1_ser'], + services: ['20a1'], manufacturerDataList: [ ManufacturerData(0x01, Uint8List.fromList([1, 2, 3])), ], @@ -17,7 +17,7 @@ void main() { var device2 = BleDevice( deviceId: '2', name: '2_device', - services: ['2_ser'], + services: ['20a2'], manufacturerDataList: [ ManufacturerData(0x02, Uint8List.fromList([1, 2, 3])) ], @@ -25,7 +25,7 @@ void main() { var device3 = BleDevice( deviceId: '3', name: '3_device', - services: ['3_ser'], + services: ['20a3'], manufacturerDataList: [ ManufacturerData(0x03, Uint8List.fromList([1, 2, 3])) ], @@ -52,7 +52,7 @@ void main() { test('Test isServicesMatchingFilters', () { var scanFilter = ScanFilter( - withServices: ['1_ser', 'random', '3_ser'], + withServices: ['20a1', '20a6', '20a3'], ); expect( universalBleFilter.servicesMatch(scanFilter, device1), @@ -84,21 +84,21 @@ void main() { ]); expect( universalBleFilter.manufacturerDataMatches( - scanFilter, + scanFilter.withManufacturerData, device1, ), isTrue, ); expect( universalBleFilter.manufacturerDataMatches( - scanFilter, + scanFilter.withManufacturerData, device2, ), isTrue, ); expect( universalBleFilter.manufacturerDataMatches( - scanFilter, + scanFilter.withManufacturerData, device3, ), isFalse, @@ -110,7 +110,7 @@ void main() { test('Test filterDevice: Have filter for all', () { universalBleFilter.scanFilter = ScanFilter( withNamePrefix: ['1'], - withServices: ['3_ser'], + withServices: ['20a3'], withManufacturerData: [ ManufacturerDataFilter( companyIdentifier: 0x02, @@ -118,15 +118,15 @@ void main() { ], ); expect( - universalBleFilter.matchesDevice(device1), + universalBleFilter.matchedScanFilter(device1), isTrue, ); expect( - universalBleFilter.matchesDevice(device2), + universalBleFilter.matchedScanFilter(device2), isTrue, ); expect( - universalBleFilter.matchesDevice(device3), + universalBleFilter.matchedScanFilter(device3), isTrue, ); }); @@ -135,15 +135,15 @@ void main() { withNamePrefix: ['1'], ); expect( - universalBleFilter.matchesDevice(device1), + universalBleFilter.matchedScanFilter(device1), isTrue, ); expect( - universalBleFilter.matchesDevice(device2), + universalBleFilter.matchedScanFilter(device2), isFalse, ); expect( - universalBleFilter.matchesDevice(device3), + universalBleFilter.matchedScanFilter(device3), isFalse, ); }); @@ -152,47 +152,144 @@ void main() { withNamePrefix: ['1', '2'], ); expect( - universalBleFilter.matchesDevice(device1), + universalBleFilter.matchedScanFilter(device1), isTrue, ); expect( - universalBleFilter.matchesDevice(device2), + universalBleFilter.matchedScanFilter(device2), isTrue, ); expect( - universalBleFilter.matchesDevice(device3), + universalBleFilter.matchedScanFilter(device3), isFalse, ); }); test('Test filterDevice: Empty Filter', () { universalBleFilter.scanFilter = ScanFilter(); expect( - universalBleFilter.matchesDevice(device1), + universalBleFilter.matchedScanFilter(device1), isTrue, ); expect( - universalBleFilter.matchesDevice(device2), + universalBleFilter.matchedScanFilter(device2), isTrue, ); expect( - universalBleFilter.matchesDevice(device3), + universalBleFilter.matchedScanFilter(device3), isTrue, ); }); test('Test filterDevice: Null Filter', () { universalBleFilter.scanFilter = ScanFilter(); expect( - universalBleFilter.matchesDevice(device1), + universalBleFilter.matchedScanFilter(device1), isTrue, ); expect( - universalBleFilter.matchesDevice(device2), + universalBleFilter.matchedScanFilter(device2), isTrue, ); expect( - universalBleFilter.matchesDevice(device3), + universalBleFilter.matchedScanFilter(device3), isTrue, ); }); }); + + group("Test Exclusion Filter", () { + test('Test exclusionFilter: Have filter for 1', () { + universalBleFilter.scanFilter = ScanFilter(exclusionFilters: [ + ExclusionFilter( + namePrefix: '1', + services: ['20a1'], + manufacturerDataFilter: [ + ManufacturerDataFilter( + companyIdentifier: 0x01, + ) + ], + ) + ]); + expect( + universalBleFilter.matchesExclusionFilter(device1), + isTrue, + ); + expect( + universalBleFilter.matchesExclusionFilter(device2), + isFalse, + ); + expect( + universalBleFilter.matchesExclusionFilter(device3), + isFalse, + ); + }); + + test('Test exclusionFilter: Filter for two', () { + universalBleFilter.scanFilter = ScanFilter(exclusionFilters: [ + ExclusionFilter( + namePrefix: '1', + services: ['20a1'], + manufacturerDataFilter: [ + ManufacturerDataFilter( + companyIdentifier: 0x01, + ) + ], + ), + ExclusionFilter( + namePrefix: '2', + services: ['20a2'], + manufacturerDataFilter: [ + ManufacturerDataFilter( + companyIdentifier: 0x02, + ) + ], + ), + ]); + expect( + universalBleFilter.matchesExclusionFilter(device1), + isTrue, + ); + expect( + universalBleFilter.matchesExclusionFilter(device2), + isTrue, + ); + expect( + universalBleFilter.matchesExclusionFilter(device3), + isFalse, + ); + }); + + test('Test exclusionFilter: Invalid filter for last', () { + universalBleFilter.scanFilter = ScanFilter(exclusionFilters: [ + ExclusionFilter( + namePrefix: '3', + services: ['20a2'], + manufacturerDataFilter: [ + ManufacturerDataFilter( + companyIdentifier: 0x01, + ) + ], + ), + ]); + expect( + universalBleFilter.matchesExclusionFilter(device1), + isFalse, + ); + expect( + universalBleFilter.matchesExclusionFilter(device2), + isFalse, + ); + expect( + universalBleFilter.matchesExclusionFilter(device3), + isFalse, + ); + }); + + test('Test exclusionFilter: Empty Filter', () { + universalBleFilter.scanFilter = ScanFilter(); + expect( + universalBleFilter.matchesExclusionFilter(device1), + isFalse, + ); + }); + }); }