From 83e5aa864c1c1b64d41bc515bdcd474308ce3b37 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 17 Nov 2021 14:50:57 +0100 Subject: [PATCH 1/2] Unused import --- app/src/main/java/no/nordicsemi/android/blinky/utils/Utils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/no/nordicsemi/android/blinky/utils/Utils.java b/app/src/main/java/no/nordicsemi/android/blinky/utils/Utils.java index 33e6790a..6950aa84 100644 --- a/app/src/main/java/no/nordicsemi/android/blinky/utils/Utils.java +++ b/app/src/main/java/no/nordicsemi/android/blinky/utils/Utils.java @@ -33,7 +33,6 @@ import android.preference.PreferenceManager; import androidx.annotation.NonNull; -import androidx.annotation.RequiresApi; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.location.LocationManagerCompat; From 3e8edde9636e6f9a4cbee5def25639c9e4153bd0 Mon Sep 17 00:00:00 2001 From: Aleksander Nowakowski Date: Wed, 17 Nov 2021 14:51:20 +0100 Subject: [PATCH 2/2] Filtering noise --- .../android/blinky/utils/FilterUtils.java | 67 +++++++++++++++++++ .../blinky/viewmodels/ScannerViewModel.java | 45 ++++++++++++- 2 files changed, 110 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/no/nordicsemi/android/blinky/utils/FilterUtils.java diff --git a/app/src/main/java/no/nordicsemi/android/blinky/utils/FilterUtils.java b/app/src/main/java/no/nordicsemi/android/blinky/utils/FilterUtils.java new file mode 100644 index 00000000..4fd35cbd --- /dev/null +++ b/app/src/main/java/no/nordicsemi/android/blinky/utils/FilterUtils.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2018, Nordic Semiconductor + * + * SPDX-License-Identifier: Apache-2.0 + */ + +package no.nordicsemi.android.blinky.utils; + +import android.os.ParcelUuid; + +import no.nordicsemi.android.support.v18.scanner.ScanRecord; +import no.nordicsemi.android.support.v18.scanner.ScanResult; + +public class FilterUtils { + private static final ParcelUuid EDDYSTONE_UUID + = ParcelUuid.fromString("0000FEAA-0000-1000-8000-00805f9b34fb"); + + private static final int COMPANY_ID_MICROSOFT = 0x0006; + private static final int COMPANY_ID_APPLE = 0x004C; + private static final int COMPANY_ID_NORDIC_SEMI = 0x0059; + + @SuppressWarnings("RedundantIfStatement") + public static boolean isBeacon(final ScanResult result) { + if (result != null && result.getScanRecord() != null) { + final ScanRecord record = result.getScanRecord(); + + final byte[] appleData = record.getManufacturerSpecificData(COMPANY_ID_APPLE); + if (appleData != null) { + // iBeacons + if (appleData.length == 23 && appleData[0] == 0x02 && appleData[1] == 0x15) + return true; + } + + final byte[] nordicData = record.getManufacturerSpecificData(COMPANY_ID_NORDIC_SEMI); + if (nordicData != null) { + // Nordic Beacons + if (nordicData.length == 23 && nordicData[0] == 0x02 && nordicData[1] == 0x15) + return true; + } + + final byte[] microsoftData = record.getManufacturerSpecificData(COMPANY_ID_MICROSOFT); + if (microsoftData != null) { + // Microsoft Advertising Beacon + if (microsoftData[0] == 0x01) // Scenario Type = Advertising Beacon + return true; + } + + // Eddystone + final byte[] eddystoneData = record.getServiceData(EDDYSTONE_UUID); + if (eddystoneData != null) + return true; + } + + return false; + } + + public static boolean isAirDrop(final ScanResult result) { + if (result != null && result.getScanRecord() != null) { + final ScanRecord record = result.getScanRecord(); + + // iPhones and iMacs advertise with AirDrop packets + final byte[] appleData = record.getManufacturerSpecificData(COMPANY_ID_APPLE); + return appleData != null && appleData.length > 1 && appleData[0] == 0x10; + } + return false; + } +} diff --git a/app/src/main/java/no/nordicsemi/android/blinky/viewmodels/ScannerViewModel.java b/app/src/main/java/no/nordicsemi/android/blinky/viewmodels/ScannerViewModel.java index c60d9000..1118c691 100644 --- a/app/src/main/java/no/nordicsemi/android/blinky/viewmodels/ScannerViewModel.java +++ b/app/src/main/java/no/nordicsemi/android/blinky/viewmodels/ScannerViewModel.java @@ -37,6 +37,7 @@ import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; +import no.nordicsemi.android.blinky.utils.FilterUtils; import no.nordicsemi.android.blinky.utils.Utils; import no.nordicsemi.android.support.v18.scanner.BluetoothLeScannerCompat; import no.nordicsemi.android.support.v18.scanner.ScanCallback; @@ -181,7 +182,7 @@ public void onScanResult(final int callbackType, @NonNull final ScanResult resul if (Utils.isLocationRequired(getApplication()) && !Utils.isLocationEnabled(getApplication())) Utils.markLocationNotRequired(getApplication()); - if (devicesLiveData.deviceDiscovered(result)) { + if (!isNoise(result) && devicesLiveData.deviceDiscovered(result)) { devicesLiveData.applyFilter(); scannerStateLiveData.recordFound(); } @@ -199,7 +200,9 @@ public void onBatchScanResults(@NonNull final List results) { boolean atLeastOneMatchedFilter = false; for (final ScanResult result : results) - atLeastOneMatchedFilter = devicesLiveData.deviceDiscovered(result) || atLeastOneMatchedFilter; + atLeastOneMatchedFilter = + (!isNoise(result) && devicesLiveData.deviceDiscovered(result)) + || atLeastOneMatchedFilter; if (atLeastOneMatchedFilter) { devicesLiveData.applyFilter(); scannerStateLiveData.recordFound(); @@ -272,4 +275,42 @@ public void onReceive(final Context context, final Intent intent) { } } }; + + /** + * This method returns true if the scan result may be considered as noise. + * This is to make the device list on the scanner screen shorter. + *

+ * This implementation considers as noise devices that: + *

    + *
  • Are not connectable (Android Oreo or newer only),
  • + *
  • Are far away (RSSI < -80),
  • + *
  • Advertise as beacons (iBeacons, Nordic Beacons, Microsoft Advertising Beacons, + * Eddystone),
  • + *
  • Advertise with AirDrop footprint,
  • + *
+ * Noise devices will no the shown on the scanner screen even with all filters disabled. + * + * @param result the scan result. + * @return true, if the device may be dismissed, false otherwise. + */ + @SuppressWarnings({"BooleanMethodIsAlwaysInverted", "RedundantIfStatement"}) + private boolean isNoise(@NonNull final ScanResult result) { + // Do not show non-connectable devices. + // Only Android Oreo or newer can say if a device is connectable. On older Android versions + // the Support Scanner Library assumes all devices are connectable (compatibility mode). + if (!result.isConnectable()) + return true; + + // Very distant devices are noise. + if (result.getRssi() < -80) + return true; + + if (FilterUtils.isBeacon(result)) + return true; + + if (FilterUtils.isAirDrop(result)) + return true; + + return false; + } }