Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.sputnikdev.bluetooth.manager.impl.DeviceGovernorImpl Maven / Gradle / Ivy
package org.sputnikdev.bluetooth.manager.impl;
/*-
* #%L
* org.sputnikdev:bluetooth-manager
* %%
* Copyright (C) 2017 Sputnik Dev
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sputnikdev.bluetooth.Filter;
import org.sputnikdev.bluetooth.RssiKalmanFilter;
import org.sputnikdev.bluetooth.URL;
import org.sputnikdev.bluetooth.manager.AdapterGovernor;
import org.sputnikdev.bluetooth.manager.BluetoothObjectType;
import org.sputnikdev.bluetooth.manager.BluetoothObjectVisitor;
import org.sputnikdev.bluetooth.manager.BluetoothSmartDeviceListener;
import org.sputnikdev.bluetooth.manager.CharacteristicGovernor;
import org.sputnikdev.bluetooth.manager.DeviceGovernor;
import org.sputnikdev.bluetooth.manager.GattCharacteristic;
import org.sputnikdev.bluetooth.manager.GattService;
import org.sputnikdev.bluetooth.manager.GenericBluetoothDeviceListener;
import org.sputnikdev.bluetooth.manager.NotReadyException;
import org.sputnikdev.bluetooth.manager.transport.Characteristic;
import org.sputnikdev.bluetooth.manager.transport.Device;
import org.sputnikdev.bluetooth.manager.transport.Notification;
import org.sputnikdev.bluetooth.manager.transport.Service;
import java.time.Duration;
import java.time.Instant;
import java.time.temporal.ChronoField;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Function;
import java.util.stream.Collectors;
/**
*
* @author Vlad Kolotov
*/
class DeviceGovernorImpl extends AbstractBluetoothObjectGovernor implements DeviceGovernor {
private Logger logger = LoggerFactory.getLogger(DeviceGovernorImpl.class);
static final int DEFAULT_RSSI_REPORTING_RATE = 0;
static final int DEFAULT_ONLINE_TIMEOUT = 30;
static final int RUMP_UP_TIMEOUT = 60;
static final short DEFAULT_TX_POWER = -55;
static final double DEFAULT_SIGNAL_PROPAGATION_EXPONENT = 4.0; // indoors
private final List genericBluetoothDeviceListeners = new CopyOnWriteArrayList<>();
private final List bluetoothSmartDeviceListeners = new CopyOnWriteArrayList<>();
private final CompletableFutureService servicesResolvedService =
new CompletableFutureService<>();
private ConnectionNotification connectionNotification;
private BlockedNotification blockedNotification;
private ServicesResolvedNotification servicesResolvedNotification;
private RSSINotification rssiNotification;
private ServiceDataNotification serviceDataNotification;
private ManufacturerDataNotification manufacturerDataNotification;
private boolean connectionControl;
private boolean blockedControl;
private boolean online;
private int onlineTimeout = DEFAULT_ONLINE_TIMEOUT;
private final Lock rssiUpdateLock = new ReentrantLock();
private Filter rssiFilter = new RssiKalmanFilter();
private boolean rssiFilteringEnabled = true;
private long rssiReportingRate = DEFAULT_RSSI_REPORTING_RATE;
private Instant rssiLastNotified = Instant.now().minusSeconds(60);
private short measuredTxPower;
private double signalPropagationExponent;
private Instant lastAdvertised;
DeviceGovernorImpl(BluetoothManagerImpl bluetoothManager, URL url) {
super(bluetoothManager, url);
}
@Override
void init(Device device) {
logger.debug("Initializing device governor: {}", url);
enableRSSINotifications(device);
enableConnectionNotifications(device);
enableServicesResolvedNotifications(device);
enableBlockedNotifications(device);
enableManufacturerDataNotifications(device);
enableServiceDataNotifications(device);
logger.trace("Device governor initialization performed: {}", url);
}
@Override
void update(Device device) {
logger.trace("Updating device governor: {}", url);
AdapterGovernor adapterGovernor = bluetoothManager.getAdapterGovernor(getURL());
boolean adapterReady = adapterGovernor.isReady();
boolean adapterPowered = adapterGovernor.isPowered();
logger.trace("Checking if device adapter is ready / powered: {} : {} / {}",
url, adapterReady, adapterPowered);
if (adapterReady && adapterPowered) {
updateBlocked(device);
if (!blockedControl) {
// Note: BlueGiga and TinyB devices work in different way:
// TinyB would have thrown an exception if the device was out of range (or turned off)
// BlueGiga would not thrown any exception by now
// therefore we need to check if BlueGiga device is still alive by querying the device RSSI
// Further note: TinyB device when connected constantly returns the very last known RSSI
boolean connected = updateConnected(device);
if (connected) {
logger.debug("Checking if device is still alive by getting its RSSI: {}", url);
notifyRSSIChanged(getRSSI());
updateLastInteracted();
} else {
// if not connected, there is not any easy way to check if the native object is still
// alive simply because multiple adapters can be used and one of them is connected to the device,
// hence other corresponding native devices (through other adapters) do not receive
// any notifications and also do not receive any advertisements,
// in other words they are dead but not exactly
// There is a workaround in place to detect such state,
// see {@link CombinedDeviceGovernorImpl#update()} method
}
}
}
updateOnline(isOnline());
logger.trace("Device governor update performed: {}", url);
}
/**
* This method is called by {@link CombinedDeviceGovernorImpl#update()} to check if all delegates are alive.
* Note: this is a trade off between bad design and stability.
* @return true if stale, false otherwise
*/
boolean checkIfStale() {
if (isReady() && Instant.now().minusSeconds(RUMP_UP_TIMEOUT).isAfter(getReady())) {
AdapterGovernor adapterGovernor = bluetoothManager.getAdapterGovernor(getURL());
if (adapterGovernor.isDiscovering()) {
int staleTimeout = onlineTimeout * 2;
boolean stale = lastAdvertised == null || !checkOnline(staleTimeout);
String lastAdvertisedFmt = Optional.ofNullable(lastAdvertised)
.map(advertised -> Math.abs(Duration.between(Instant.now(), advertised).getSeconds()) + "s")
.orElse("never");
String lastInteractedFmt = Optional.ofNullable(getLastInteracted())
.map(interacted -> Math.abs(Duration.between(Instant.now(), interacted).getSeconds()) + "s")
.orElse("never");
logger.debug("Device {} last advertised ({}) ago and last interacted ({}) ago. "
+ "Stale timeout: {}s. Device is considered stale: {}",
url, lastAdvertisedFmt, lastInteractedFmt, staleTimeout, stale);
//TODO there might be a problem when client switches on discovery for adapter, devices will be reset
return stale;
}
}
return false;
}
@Override
void reset(Device device) {
logger.debug("Resetting device governor: {}", url);
updateOnline(false);
try {
logger.trace("Disable device notifications: {}", url);
device.disableConnectedNotifications();
device.disableServicesResolvedNotifications();
device.disableRSSINotifications();
device.disableBlockedNotifications();
device.disableServiceDataNotifications();
device.disableManufacturerDataNotifications();
logger.trace("Disconnecting device: {}", url);
if (device.isConnected()) {
device.disconnect();
notifyConnected(false);
}
} catch (Exception ex) {
logger.warn("Error occurred while resetting device: {} : {} ", url, ex.getMessage());
}
connectionNotification = null;
servicesResolvedNotification = null;
rssiNotification = null;
blockedNotification = null;
serviceDataNotification = null;
manufacturerDataNotification = null;
logger.trace("Device governor reset performed: {}", url);
}
@Override
public void dispose() {
super.dispose();
logger.trace("Disposing device governor: {}", url);
genericBluetoothDeviceListeners.clear();
bluetoothSmartDeviceListeners.clear();
logger.debug("Device governor disposed: {}", url);
}
@Override
public int getBluetoothClass() throws NotReadyException {
return interact("getBluetoothClass", Device::getBluetoothClass);
}
@Override
public boolean isBleEnabled() throws NotReadyException {
return interact("isBleEnabled", Device::isBleEnabled);
}
@Override
public String getName() throws NotReadyException {
return interact("getName", Device::getName);
}
@Override
public String getAlias() throws NotReadyException {
return interact("getAlias", Device::getAlias);
}
@Override
public void setAlias(String alias) throws NotReadyException {
interact("setAlias", Device::setAlias, alias);
}
@Override
public String getDisplayName() throws NotReadyException {
String alias = getAlias();
return alias != null ? alias : getName();
}
@Override
public boolean getConnectionControl() {
return connectionControl;
}
public void setConnectionControl(boolean connectionControl) {
logger.debug("Setting connection control: {} : {} / {}", url, this.connectionControl, connectionControl);
boolean changed = this.connectionControl != connectionControl;
if (changed) {
this.connectionControl = connectionControl;
scheduleUpdate();
}
}
@Override
public boolean getBlockedControl() {
return blockedControl;
}
@Override
public void setBlockedControl(boolean blockedControl) {
logger.debug("Setting blocked control: {} : {}", url, blockedControl);
this.blockedControl = blockedControl;
}
@Override
public boolean isConnected() throws NotReadyException {
return isReady() && interact("isConnected", Device::isConnected);
}
@Override
public boolean isBlocked() throws NotReadyException {
return interact("isBlocked", Device::isBlocked);
}
@Override
public boolean isOnline() {
return checkOnline(onlineTimeout);
}
@Override
public int getOnlineTimeout() {
return onlineTimeout;
}
@Override
public void setOnlineTimeout(int onlineTimeout) {
this.onlineTimeout = onlineTimeout;
}
@Override
public short getRSSI() throws NotReadyException {
return interact("getRSSI", Device::getRSSI);
}
@Override
public void setRssiFilter(Class extends Filter> filter) {
rssiFilter = createFilter(filter);
}
@Override
public Filter getRssiFilter() {
return rssiFilter;
}
@Override
public boolean isRssiFilteringEnabled() {
return rssiFilteringEnabled;
}
@Override
public void setRssiFilteringEnabled(boolean rssiFilteringEnabled) {
this.rssiFilteringEnabled = rssiFilteringEnabled;
}
@Override
public long getRssiReportingRate() {
return rssiReportingRate;
}
@Override
public Instant getLastAdvertised() {
return lastAdvertised;
}
@Override
public short getTxPower() {
return interact("getTxPower", Device::getTxPower);
}
@Override
public short getMeasuredTxPower() {
return measuredTxPower;
}
@Override
public void setMeasuredTxPower(short txPower) {
measuredTxPower = txPower;
}
@Override
public double getSignalPropagationExponent() {
return signalPropagationExponent;
}
@Override
public void setSignalPropagationExponent(double signalPropagationExponent) {
this.signalPropagationExponent = signalPropagationExponent;
}
@Override
public double getEstimatedDistance() {
short rssi = 0;
if (rssiFilteringEnabled && rssiFilter != null) {
rssi = rssiFilter.current();
}
if (rssi == 0 && isReady()) {
rssi = getRSSI();
}
if (rssi == 0) {
return 0;
}
double estimated = Math.pow(10d,
((double) getTxPowerInternal() - rssi) / (10 * getPropagationExponentInternal()));
logger.debug("Estimated distance: {} : {}", url, estimated);
return estimated;
}
@Override
public URL getLocation() {
return url.getAdapterURL();
}
@Override
public void setRssiReportingRate(long rssiReportingRate) {
this.rssiReportingRate = rssiReportingRate;
}
@Override
public void addBluetoothSmartDeviceListener(BluetoothSmartDeviceListener bluetoothSmartDeviceListener) {
bluetoothSmartDeviceListeners.add(bluetoothSmartDeviceListener);
}
@Override
@SuppressWarnings({"unchecked", "rawtypes"})
public CompletableFuture whenServicesResolved(Function function) {
return servicesResolvedService.submit(this, (Function) function);
}
@Override
public void removeBluetoothSmartDeviceListener(BluetoothSmartDeviceListener bluetoothSmartDeviceListener) {
bluetoothSmartDeviceListeners.remove(bluetoothSmartDeviceListener);
}
@Override
public void addGenericBluetoothDeviceListener(GenericBluetoothDeviceListener genericBluetoothDeviceListener) {
genericBluetoothDeviceListeners.add(genericBluetoothDeviceListener);
}
@Override
public void removeGenericBluetoothDeviceListener(GenericBluetoothDeviceListener listener) {
genericBluetoothDeviceListeners.remove(listener);
}
@Override
public boolean isServicesResolved() throws NotReadyException {
try {
return isReady() && interact("isServicesResolved", Device::isServicesResolved);
} catch (NotReadyException ignore) {
return false;
}
}
@Override
public List getResolvedServices() throws NotReadyException {
return interact("getResolvedServices", device -> {
List services = new ArrayList<>();
for (Service service : device.getServices()) {
List characteristics = new ArrayList<>();
for (Characteristic characteristic : service.getCharacteristics()) {
characteristics.add(convert(characteristic));
}
services.add(new GattService(service.getURL(), characteristics));
}
return services;
});
}
@Override
public Map> getServicesToCharacteristicsMap() throws NotReadyException {
return interact("getServicesToCharacteristicsMap", device -> {
Map> services = new HashMap<>();
for (Service service : device.getServices()) {
URL serviceURL = service.getURL();
services.put(serviceURL, (List) bluetoothManager.getGovernors(service.getCharacteristics()));
}
return services;
});
}
@Override
public List getCharacteristics() throws NotReadyException {
return BluetoothManagerUtils.getURLs(getAllCharacteristics());
}
@Override
public List getCharacteristicGovernors() throws NotReadyException {
return (List) bluetoothManager.getGovernors(getAllCharacteristics());
}
@Override
public String toString() {
String result = "[Device] " + getURL();
if (isReady()) {
String displayName = getDisplayName();
if (displayName != null) {
result += " [" + displayName + "]";
}
if (isBleEnabled()) {
result += " [BLE]";
}
}
return result;
}
@Override
public BluetoothObjectType getType() {
return BluetoothObjectType.DEVICE;
}
@Override
public void accept(BluetoothObjectVisitor visitor) throws Exception {
visitor.visit(this);
}
@Override
public Map getManufacturerData() {
return interact("getManufacturerData", Device::getManufacturerData);
}
@Override
public Map getServiceData() {
return interact("getServiceData", device -> {
return convert(device.getServiceData());
});
}
@Override
void notifyLastChanged() {
notifyLastChanged(BluetoothManagerUtils.max(getLastInteracted(), lastAdvertised));
}
void notifyConnected(boolean connected) {
logger.debug("Notifying device governor listener (connected): {} : {} : {}",
url, bluetoothSmartDeviceListeners.size(), connected);
bluetoothSmartDeviceListeners.forEach(listener -> {
try {
if (connected) {
listener.connected();
} else {
listener.disconnected();
}
} catch (Exception ex) {
logger.error("Execution error of a connection listener", ex);
}
});
}
void notifyBlocked(boolean blocked) {
logger.debug("Notifying device governor listener (blocked): {} : {} : {}",
url, genericBluetoothDeviceListeners.size(), blocked);
BluetoothManagerUtils.forEachSilently(genericBluetoothDeviceListeners,
listener -> listener.blocked(blocked), logger,"Execution error of a blocked listener");
}
void notifyServicesResolved(List services) {
logger.debug("Notifying device governor listener (services resolved): {} : {} : {}",
url, bluetoothSmartDeviceListeners.size(), services.size());
BluetoothManagerUtils.forEachSilently(bluetoothSmartDeviceListeners,
BluetoothSmartDeviceListener::servicesResolved, services, logger,
"Execution error of a service resolved listener");
servicesResolvedService.complete(this);
}
void notifyServicesUnresolved() {
logger.debug("Notifying device governor listener (services unresolved): {} : {}",
url, bluetoothSmartDeviceListeners.size());
BluetoothManagerUtils.forEachSilently(bluetoothSmartDeviceListeners,
BluetoothSmartDeviceListener::servicesUnresolved, logger,
"Execution error of a service unresolved listener");
}
void updateRSSI(short next) {
logger.trace("Updating RSSI: {} : {}", url, next);
Filter filter = rssiFilter;
if (rssiUpdateLock.tryLock()) {
try {
if (filter != null && rssiFilteringEnabled) {
// devices can report RSSI too fast that we can't handle it, so we skip some readings
notifyRSSIChanged(filter.next(next));
} else {
notifyRSSIChanged(next);
}
} finally {
rssiUpdateLock.unlock();
}
}
}
void notifyRSSIChanged(short next) {
if (rssiReportingRate == 0
|| System.currentTimeMillis() - rssiLastNotified.toEpochMilli() > rssiReportingRate) {
BluetoothManagerUtils.forEachSilently(genericBluetoothDeviceListeners,
listener -> listener.rssiChanged(next), logger,
"Execution error of a RSSI listener");
rssiLastNotified = Instant.now();
}
}
void notifyOnline(boolean online) {
logger.debug("Notifying device governor listener (online): {} : {} : {}",
url, genericBluetoothDeviceListeners.size(), online);
BluetoothManagerUtils.forEachSilently(genericBluetoothDeviceListeners,
listener -> {
if (online) {
listener.online();
} else {
listener.offline();
}
}, logger,"Execution error of an online listener");
}
void updateLastAdvertised() {
lastAdvertised = Instant.now();
}
private boolean checkOnline(int timeout) {
long advertised = lastAdvertised != null ? lastAdvertised.toEpochMilli() : 0;
long interacted = getLastInteracted() != null ? getLastInteracted().toEpochMilli() : 0;
return Instant.now().minusSeconds(timeout).isBefore(
Instant.ofEpochMilli(Math.max(advertised, interacted)));
}
private List getAllCharacteristics() throws NotReadyException {
return interact("getAllCharacteristics", device -> {
List characteristics = new ArrayList<>();
List services = device.getServices();
if (services != null) {
for (Service service : services) {
List chars = service.getCharacteristics();
if (chars != null) {
characteristics.addAll(chars);
}
}
}
return characteristics;
});
}
private void enableConnectionNotifications(Device bluetoothDevice) {
logger.debug("Enabling connection notification: {} : {}", getURL(), connectionNotification == null);
if (connectionNotification == null) {
connectionNotification = new ConnectionNotification();
bluetoothDevice.enableConnectedNotifications(connectionNotification);
}
}
private void enableBlockedNotifications(Device bluetoothDevice) {
logger.debug("Enabling blocked notification: {} : {}", getURL(), blockedNotification == null);
if (blockedNotification == null) {
blockedNotification = new BlockedNotification();
bluetoothDevice.enableBlockedNotifications(blockedNotification);
}
}
private void enableServicesResolvedNotifications(Device bluetoothDevice) {
logger.debug("Enabling services resolved notification: {} : {}",
getURL(), servicesResolvedNotification == null);
if (servicesResolvedNotification == null) {
servicesResolvedNotification = new ServicesResolvedNotification();
bluetoothDevice.enableServicesResolvedNotifications(servicesResolvedNotification);
}
}
private void enableRSSINotifications(Device bluetoothDevice) {
logger.debug("Enabling RSSI notification: {} : {}", getURL(), rssiNotification == null);
if (rssiNotification == null) {
rssiNotification = new RSSINotification();
bluetoothDevice.enableRSSINotifications(rssiNotification);
}
}
private void enableServiceDataNotifications(Device bluetoothDevice) {
logger.debug("Enabling service data notification: {} : {}", getURL(), serviceDataNotification == null);
if (serviceDataNotification == null) {
serviceDataNotification = new ServiceDataNotification();
bluetoothDevice.enableServiceDataNotifications(serviceDataNotification);
}
}
private void enableManufacturerDataNotifications(Device bluetoothDevice) {
logger.debug("Enabling manufacturer data notification: {} : {}",
getURL(), manufacturerDataNotification == null);
if (manufacturerDataNotification == null) {
manufacturerDataNotification = new ManufacturerDataNotification();
bluetoothDevice.enableManufacturerDataNotifications(manufacturerDataNotification);
}
}
private void updateCharacteristics() {
logger.debug("Updating device governor characteristics: {}", url);
bluetoothManager.updateDescendants(url);
}
private void resetCharacteristics() {
logger.debug("Resetting device governor characteristics: {}", url);
bluetoothManager.resetDescendants(url);
}
private static GattCharacteristic convert(Characteristic characteristic) {
return new GattCharacteristic(characteristic.getURL(), characteristic.getFlags());
}
private void updateOnline(boolean online) {
logger.trace("Updating device governor online state: {}", url);
if (online != this.online) {
logger.debug("Updating online state: {} : {} (current) / {} (new)", url, this.online, online);
notifyOnline(online);
}
this.online = online;
}
private void updateBlocked(Device device) {
logger.trace("Updating device governor blocked state: {}", url);
boolean blocked = device.isBlocked();
if (blockedControl != blocked) {
logger.debug("Updating blocked state: {} : {} (control) / {} (state)", url, blockedControl, blocked);
device.setBlocked(blockedControl);
}
}
private boolean updateConnected(Device device) {
logger.trace("Updating device governor connected state: {}", url);
boolean connected = device.isConnected();
logger.trace("Connected state: {} : {} (control) / {} (state)", url, connectionControl, connected);
if (connectionControl && !connected) {
logger.debug("Connecting device: {}", url);
connected = device.connect();
if (!connected) {
throw new NotReadyException("Could not connect to device: " + url);
}
} else if (!connectionControl && connected) {
logger.debug("Disconnecting device: {}", url);
resetCharacteristics();
device.disconnect();
connected = false;
}
return connected;
}
private short getTxPowerInternal() {
short txPower = measuredTxPower;
if (txPower == 0 && isReady()) {
try {
txPower = getTxPower();
} catch (NotReadyException ignore) { /* do nothing */ }
}
if (txPower == 0) {
txPower = DEFAULT_TX_POWER;
}
return txPower;
}
private double getPropagationExponentInternal() {
double propagationExponent = signalPropagationExponent;
if (propagationExponent == 0) {
AdapterGovernor adapterGovernor = bluetoothManager.getAdapterGovernor(getURL());
propagationExponent = adapterGovernor.getSignalPropagationExponent();
}
if (propagationExponent == 0) {
propagationExponent = DEFAULT_SIGNAL_PROPAGATION_EXPONENT;
}
return propagationExponent;
}
private Map convert(Map serviceData) {
return serviceData.entrySet().stream()
.collect(Collectors.toMap(entry -> url.copyWithService(entry.getKey()), Map.Entry::getValue)) ;
}
private class ConnectionNotification implements Notification {
@Override
public void notify(Boolean connected) {
logger.debug("Connected (notification): {} : {}", url, connected);
notifyConnected(connected);
updateLastInteracted();
}
}
private class BlockedNotification implements Notification {
@Override
public void notify(Boolean blocked) {
logger.debug("Blocked (notification): {} : {}", url, blocked);
notifyBlocked(blocked);
updateLastInteracted();
}
}
private class ServicesResolvedNotification implements Notification {
@Override
public void notify(Boolean serviceResolved) {
logger.debug("Services resolved (notification): {} : {}", url, serviceResolved);
if (serviceResolved) {
List gattServices = getResolvedServices();
updateCharacteristics();
if (gattServices != null && !gattServices.isEmpty()) {
notifyServicesResolved(gattServices);
}
updateLastInteracted();
} else {
logger.debug("Resetting characteristic governors due to services unresolved event: {}", url);
resetCharacteristics();
notifyServicesUnresolved();
}
}
}
private class RSSINotification implements Notification {
@Override
public void notify(Short rssi) {
updateRSSI(rssi);
updateLastAdvertised();
}
}
private class ServiceDataNotification implements Notification> {
@Override
public void notify(Map serviceData) {
logger.debug("Services data changed (notification): {} : {} : {}",
url, bluetoothSmartDeviceListeners.size(), serviceData.size());
BluetoothManagerUtils.forEachSilently(bluetoothSmartDeviceListeners,
listener -> listener.serviceDataChanged(convert(serviceData)), logger,
"Execution error of a service data listener");
updateLastAdvertised();
}
}
private class ManufacturerDataNotification implements Notification> {
@Override
public void notify(Map manufacturerData) {
logger.debug("Manufacturer data changed (notification): {} : {} : {}",
url, bluetoothSmartDeviceListeners.size(), manufacturerData.size());
BluetoothManagerUtils.forEachSilently(bluetoothSmartDeviceListeners,
listener -> listener.manufacturerDataChanged(manufacturerData), logger,
"Execution error of a manufacturer data listener");
updateLastAdvertised();
}
}
protected Filter createFilter(Class extends Filter> filter) {
try {
return filter != null ? filter.newInstance() : null;
} catch (InstantiationException | IllegalAccessException e) {
throw new IllegalStateException(e);
}
}
}