Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Filtering noise #65

Merged
merged 2 commits into from
Nov 17, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
Expand All @@ -199,7 +200,9 @@ public void onBatchScanResults(@NonNull final List<ScanResult> 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();
Expand Down Expand Up @@ -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.
* <p>
* This implementation considers as noise devices that:
* <ul>
* <li>Are not connectable (Android Oreo or newer only),</li>
* <li>Are far away (RSSI < -80),</li>
* <li>Advertise as beacons (iBeacons, Nordic Beacons, Microsoft Advertising Beacons,
* Eddystone),</li>
* <li>Advertise with AirDrop footprint,</li>
* </ul>
* 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;
}
}