From ec98019b681a227a5e6c96c1d7708cec48b1e27a Mon Sep 17 00:00:00 2001
From: cpmeister <mistercpp2000@gmail.com>
Date: Sat, 4 Apr 2020 00:51:07 -0700
Subject: [PATCH] [bluetooth] Refactor and unify BluetoothAdapter
 implementation logic (#7129)

* Refactor and unify BluetoothAdapter implementation logic

Signed-off-by: Connor Petty <mistercpp2000+gitsignoff@gmail.com>
Signed-off-by: CSchlipp <christian@schlipp.de>
---
 .../bluegiga/BlueGigaBluetoothDevice.java     |  17 +-
 .../handler/BlueGigaBridgeHandler.java        | 138 ++---------
 .../internal/BlueGigaConfiguration.java       |   8 +-
 .../main/resources/ESH-INF/thing/bluegiga.xml |  18 +-
 .../bluetooth/bluez/BlueZBluetoothDevice.java |  11 +-
 .../handler/BlueZAdapterConfiguration.java    |   5 +-
 .../bluez/handler/BlueZBridgeHandler.java     | 146 +-----------
 .../main/resources/ESH-INF/thing/bluez.xml    |  18 +-
 .../AbstractBluetoothBridgeHandler.java       | 223 ++++++++++++++++++
 ...seBluetoothBridgeHandlerConfiguration.java |  29 +++
 .../bluetooth/BeaconBluetoothHandler.java     |   3 -
 .../binding/bluetooth/BluetoothAdapter.java   |   2 +-
 .../binding/bluetooth/BluetoothDevice.java    |  35 ++-
 .../internal/BluetoothAddressLocker.java      |  55 -----
 .../internal/BluetoothDiscoveryProcess.java   |  31 +--
 .../bluetooth/MockBluetoothAdapter.java       |   4 +-
 16 files changed, 366 insertions(+), 377 deletions(-)
 create mode 100644 bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/AbstractBluetoothBridgeHandler.java
 create mode 100644 bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BaseBluetoothBridgeHandlerConfiguration.java
 delete mode 100644 bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothAddressLocker.java

diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java
index e90aa8044f465..a8889a249ff5c 100644
--- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java
+++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/BlueGigaBluetoothDevice.java
@@ -12,7 +12,6 @@
  */
 package org.openhab.binding.bluetooth.bluegiga;
 
-import java.time.ZonedDateTime;
 import java.util.Map;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -81,8 +80,6 @@ private enum BlueGigaProcedure {
     // The connection handle if the device is connected
     private int connection = -1;
 
-    private ZonedDateTime lastSeenTime = ZonedDateTime.now();
-
     private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
 
     private @Nullable ScheduledFuture<?> connectTimer;
@@ -524,6 +521,7 @@ private void handleAttributeValueEvent(BlueGigaAttributeValueEvent event) {
     /**
      * Clean up and release memory.
      */
+    @Override
     public void dispose() {
         if (connectionState == ConnectionState.CONNECTED) {
             disconnect();
@@ -536,19 +534,6 @@ public void dispose() {
         connection = -1;
     }
 
-    /**
-     * Return last seen Time
-     *
-     * @return last seen Time
-     */
-    public ZonedDateTime getLastSeenTime() {
-        return lastSeenTime;
-    }
-
-    private void updateLastSeenTime() {
-        this.lastSeenTime = ZonedDateTime.now();
-    }
-
     private void cancelTimer(@Nullable ScheduledFuture<?> task) {
         if (task != null) {
             task.cancel(true);
diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/handler/BlueGigaBridgeHandler.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/handler/BlueGigaBridgeHandler.java
index d1e326949c908..d34446fe5cc51 100644
--- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/handler/BlueGigaBridgeHandler.java
+++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/handler/BlueGigaBridgeHandler.java
@@ -17,13 +17,10 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.time.ZonedDateTime;
 import java.util.Map;
 import java.util.Optional;
-import java.util.Set;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.Future;
 import java.util.concurrent.ScheduledExecutorService;
 import java.util.concurrent.ScheduledFuture;
@@ -34,25 +31,17 @@
 import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.smarthome.core.common.ThreadPoolManager;
 import org.eclipse.smarthome.core.thing.Bridge;
-import org.eclipse.smarthome.core.thing.ChannelUID;
 import org.eclipse.smarthome.core.thing.Thing;
 import org.eclipse.smarthome.core.thing.ThingStatus;
 import org.eclipse.smarthome.core.thing.ThingStatusDetail;
-import org.eclipse.smarthome.core.thing.ThingUID;
-import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
-import org.eclipse.smarthome.core.types.Command;
 import org.eclipse.smarthome.io.transport.serial.PortInUseException;
 import org.eclipse.smarthome.io.transport.serial.SerialPort;
 import org.eclipse.smarthome.io.transport.serial.SerialPortIdentifier;
 import org.eclipse.smarthome.io.transport.serial.SerialPortManager;
 import org.eclipse.smarthome.io.transport.serial.UnsupportedCommOperationException;
-import org.openhab.binding.bluetooth.BluetoothAdapter;
+import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
 import org.openhab.binding.bluetooth.BluetoothAddress;
 import org.openhab.binding.bluetooth.BluetoothBindingConstants;
-import org.openhab.binding.bluetooth.BluetoothDevice;
-import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
-import org.openhab.binding.bluetooth.BluetoothDeviceListener;
-import org.openhab.binding.bluetooth.BluetoothDiscoveryListener;
 import org.openhab.binding.bluetooth.bluegiga.BlueGigaAdapterConstants;
 import org.openhab.binding.bluetooth.bluegiga.BlueGigaBluetoothDevice;
 import org.openhab.binding.bluetooth.bluegiga.internal.BlueGigaCommand;
@@ -117,8 +106,8 @@
  * @author Pauli Anttila - Many improvements
  */
 @NonNullByDefault
-public class BlueGigaBridgeHandler extends BaseBridgeHandler
-        implements BluetoothAdapter, BlueGigaEventListener, BlueGigaHandlerListener {
+public class BlueGigaBridgeHandler extends AbstractBluetoothBridgeHandler<BlueGigaBluetoothDevice>
+        implements BlueGigaEventListener, BlueGigaHandlerListener {
 
     private final Logger logger = LoggerFactory.getLogger(BlueGigaBridgeHandler.class);
 
@@ -159,20 +148,12 @@ public class BlueGigaBridgeHandler extends BaseBridgeHandler
     // Map of open connections
     private final Map<Integer, BluetoothAddress> connections = new ConcurrentHashMap<>();
 
-    // Set of discovery listeners
-    protected final Set<BluetoothDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
-
-    // List of device listeners
-    protected final ConcurrentHashMap<BluetoothAddress, BluetoothDeviceListener> deviceListeners = new ConcurrentHashMap<>();
-
     private volatile boolean initComplete = false;
 
     private @Nullable ScheduledFuture<?> initTask;
     private @Nullable ScheduledFuture<?> removeInactiveDevicesTask;
     private @Nullable ScheduledFuture<?> discoveryTask;
 
-    private volatile boolean activeScanEnabled = false;
-
     private @Nullable Future<?> passiveScanIdleTimer;
 
     public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager) {
@@ -180,19 +161,9 @@ public BlueGigaBridgeHandler(Bridge bridge, SerialPortManager serialPortManager)
         this.serialPortManager = serialPortManager;
     }
 
-    @Override
-    public ThingUID getUID() {
-        // being a BluetoothAdapter, we use the UID of our bridge
-        return getThing().getUID();
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-        // No commands supported for the bridge
-    }
-
     @Override
     public void initialize() {
+        super.initialize();
         Optional<BlueGigaConfiguration> cfg = Optional.of(getConfigAs(BlueGigaConfiguration.class));
         if (cfg.isPresent()) {
             configuration = cfg.get();
@@ -204,7 +175,12 @@ public void initialize() {
 
     @Override
     public void dispose() {
-        stop(true);
+        stop();
+        stopScheduledTasks();
+        if (initTask != null) {
+            initTask.cancel(true);
+        }
+        super.dispose();
     }
 
     private void start() {
@@ -212,7 +188,7 @@ private void start() {
             if (!initComplete) {
                 logger.debug("Initialize BlueGiga");
                 logger.debug("Using configuration: {}", configuration);
-                stop(false);
+                stop();
                 if (openSerialPort(configuration.port, 115200)) {
                     serialHandler = Optional.of(new BlueGigaSerialHandler(inputStream.get(), outputStream.get()));
                     transactionManager = Optional.of(new BlueGigaTransactionManager(serialHandler.get(), executor));
@@ -258,7 +234,7 @@ private void start() {
         }
     }
 
-    private void stop(boolean exit) {
+    private void stop() {
         if (transactionManager.isPresent()) {
             transactionManager.get().removeEventListener(this);
             transactionManager.get().close();
@@ -273,17 +249,6 @@ private void stop(boolean exit) {
         initComplete = false;
         connections.clear();
         closeSerialPort();
-
-        if (exit) {
-            stopScheduledTasks();
-            if (initTask != null) {
-                initTask.cancel(true);
-            }
-            devices.forEach((address, device) -> {
-                device.dispose();
-            });
-            devices.clear();
-        }
     }
 
     private void schedulePassiveScan() {
@@ -308,8 +273,6 @@ private void cancelScheduledPassiveScan() {
     private void startScheduledTasks() {
         schedulePassiveScan();
         logger.debug("Start scheduled task to remove inactive devices");
-        removeInactiveDevicesTask = scheduler.scheduleWithFixedDelay(this::removeInactiveDevices, 1, 1,
-                TimeUnit.MINUTES);
         discoveryTask = scheduler.scheduleWithFixedDelay(this::refreshDiscoveredDevices, 0, 10, TimeUnit.SECONDS);
     }
 
@@ -325,38 +288,6 @@ private void stopScheduledTasks() {
         }
     }
 
-    private void removeInactiveDevices() {
-        logger.debug("Check inactive devices, count {}", devices.size());
-        devices.forEach((address, device) -> {
-            if (shouldRemove(device)) {
-                logger.debug("Removing device '{}' due to inactivity, last seen: {}", address,
-                        device.getLastSeenTime());
-                device.dispose();
-                devices.remove(address);
-            }
-        });
-    }
-
-    private void refreshDiscoveredDevices() {
-        logger.debug("Refreshing Bluetooth device list...");
-        devices.forEach((address, device) -> {
-            deviceDiscovered(device);
-        });
-    }
-
-    private boolean shouldRemove(BlueGigaBluetoothDevice device) {
-        // we can't remove devices with listeners since that means they have a handler.
-        if (device.hasListeners()) {
-            return false;
-        }
-        // devices that are connected won't receive any scan notifications so we can't remove them for being idle
-        if (device.getConnectionState() == ConnectionState.CONNECTED) {
-            return false;
-        }
-
-        return device.getLastSeenTime().plusMinutes(5).isBefore(ZonedDateTime.now());
-    }
-
     private BlueGigaGetConnectionsResponse readMaxConnections() throws BlueGigaException {
         return sendCommandWithoutChecks(new BlueGigaGetConnectionsCommand(), BlueGigaGetConnectionsResponse.class);
     }
@@ -448,24 +379,20 @@ private void closeSerialPort() {
 
     @Override
     public void scanStart() {
+        super.scanStart();
         logger.debug("Start active scan");
-        activeScanEnabled = true;
         // Stop the passive scan
         cancelScheduledPassiveScan();
         bgEndProcedure();
 
         // Start a active scan
         bgStartScanning(true, configuration.activeScanInterval, configuration.activeScanWindow);
-
-        for (BluetoothDevice device : devices.values()) {
-            deviceDiscovered(device);
-        }
     }
 
     @Override
     public void scanStop() {
+        super.scanStop();
         logger.debug("Stop active scan");
-        activeScanEnabled = false;
 
         // Stop the active scan
         bgEndProcedure();
@@ -484,21 +411,9 @@ public BluetoothAddress getAddress() {
         }
     }
 
-    @SuppressWarnings({ "null", "unused" })
     @Override
-    public BluetoothDevice getDevice(BluetoothAddress address) {
-        BlueGigaBluetoothDevice device = devices.get(address);
-        if (device == null) {
-            // This method always needs to return a device, even if we don't currently know about it.
-            device = new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
-            devices.put(address, device);
-        }
-        return device;
-    }
-
-    @Override
-    public boolean hasDevice(BluetoothAddress address) {
-        return devices.containsKey(address);
+    protected BlueGigaBluetoothDevice createDevice(BluetoothAddress address) {
+        return new BlueGigaBluetoothDevice(this, address, BluetoothAddressType.UNKNOWN);
     }
 
     /**
@@ -566,17 +481,6 @@ public boolean bgDisconnect(int connectionHandle) {
         }
     }
 
-    /**
-     * Device discovered. This simply passes the discover information to the discovery service for processing.
-     */
-    public void deviceDiscovered(BluetoothDevice device) {
-        if (configuration.discovery || activeScanEnabled) {
-            for (BluetoothDiscoveryListener listener : discoveryListeners) {
-                listener.deviceDiscovered(device);
-            }
-        }
-    }
-
     /**
      * Start a read of all primary services using {@link BlueGigaReadByGroupTypeCommand}
      *
@@ -795,16 +699,6 @@ public void removeEventListener(BlueGigaEventListener listener) {
         });
     }
 
-    @Override
-    public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
-        discoveryListeners.add(listener);
-    }
-
-    @Override
-    public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
-        discoveryListeners.remove(listener);
-    }
-
     @Override
     public void bluegigaEventReceived(@Nullable BlueGigaResponse event) {
         if (event instanceof BlueGigaScanResponseEvent) {
diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/internal/BlueGigaConfiguration.java b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/internal/BlueGigaConfiguration.java
index 418820a7699dd..3fa5a1f2c72c2 100644
--- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/internal/BlueGigaConfiguration.java
+++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/java/org/openhab/binding/bluetooth/bluegiga/internal/BlueGigaConfiguration.java
@@ -13,6 +13,7 @@
 package org.openhab.binding.bluetooth.bluegiga.internal;
 
 import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.openhab.binding.bluetooth.BaseBluetoothBridgeHandlerConfiguration;
 
 /**
  * Configuration class for {@link BlueGigaConfiguration} device.
@@ -20,8 +21,7 @@
  * @author Pauli Anttila - Initial contribution
  */
 @NonNullByDefault
-public class BlueGigaConfiguration {
-    public boolean discovery;
+public class BlueGigaConfiguration extends BaseBluetoothBridgeHandlerConfiguration {
     public String port = "";
     public int passiveScanIdleTime;
     public int passiveScanInterval;
@@ -39,7 +39,7 @@ public String toString() {
                 "[discovery=%b, port=%s, passiveScanIdleTime=%d, passiveScanInterval=%d, passiveScanWindow=%d"
                         + ", activeScanInterval=%d, activeScanWindow=%d, connIntervalMin=%d, connIntervalMax=%d"
                         + ", connLatency=%d, connTimeout=%d]",
-                discovery, port, passiveScanIdleTime, passiveScanInterval, passiveScanWindow, activeScanInterval,
-                activeScanWindow, connIntervalMin, connIntervalMax, connLatency, connTimeout);
+                backgroundDiscovery, port, passiveScanIdleTime, passiveScanInterval, passiveScanWindow,
+                activeScanInterval, activeScanWindow, connIntervalMin, connIntervalMax, connLatency, connTimeout);
     }
 }
diff --git a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/ESH-INF/thing/bluegiga.xml b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/ESH-INF/thing/bluegiga.xml
index f208b32e4186a..ddef05a56a1f3 100644
--- a/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/ESH-INF/thing/bluegiga.xml
+++ b/bundles/org.openhab.binding.bluetooth.bluegiga/src/main/resources/ESH-INF/thing/bluegiga.xml
@@ -14,12 +14,24 @@
 				<context>serial-port</context>
 				<description>Serial Port</description>
 			</parameter>
-			<parameter name="discovery" type="boolean">
-				<label>Device Discovery</label>
-				<description>Whether this adapter actively participates in Bluetooth device discovery</description>
+			<parameter name="backgroundDiscovery" type="boolean">
+				<label>Background Discovery</label>
+				<description>Whether this adapter performs background discovery of Bluetooth devices</description>
 				<advanced>true</advanced>
 				<default>false</default>
 			</parameter>
+			<parameter name="inactiveDeviceCleanupInterval" type="integer" min="1" unit="s">
+				<label>Device Cleanup Interval</label>
+				<description>How often device cleanup is performed</description>
+				<advanced>true</advanced>
+				<default>60</default>
+			</parameter>
+			<parameter name="inactiveDeviceCleanupThreshold" type="integer" min="1" unit="s">
+				<label>Device Cleanup Threshold</label>
+				<description>Timespan a device can remain radio silent before it is eligible for cleanup</description>
+				<advanced>true</advanced>
+				<default>300</default>
+			</parameter>
 			<parameter name="passiveScanIdleTime" type="integer" min="100" max="60000">
 				<label>Passive Scan Idle Time</label>
 				<description>Passive scan idle time defines the time how long to wait in milliseconds before start passive scan.</description>
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java
index 9e4a0a513d435..dd63b4cd21d04 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/BlueZBluetoothDevice.java
@@ -51,8 +51,6 @@ public class BlueZBluetoothDevice extends BluetoothDevice {
 
     private final ScheduledExecutorService scheduler = ThreadPoolManager.getScheduledPool("bluetooth");
 
-    private long lastSeenTime;
-
     /**
      * Constructor
      *
@@ -376,17 +374,10 @@ private BluetoothGattDescriptor getTinybDescriptorByUUID(String uuid) {
         return null;
     }
 
-    private void updateLastSeenTime() {
-        this.lastSeenTime = System.currentTimeMillis();
-    }
-
-    public long getTimeSinceSeen(TimeUnit unit) {
-        return unit.convert(System.currentTimeMillis() - this.lastSeenTime, TimeUnit.MILLISECONDS);
-    }
-
     /**
      * Clean up and release memory.
      */
+    @Override
     public void dispose() {
         if (device == null) {
             return;
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java
index 43e874c230a18..b85ce17496e9e 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZAdapterConfiguration.java
@@ -12,14 +12,15 @@
  */
 package org.openhab.binding.bluetooth.bluez.handler;
 
+import org.openhab.binding.bluetooth.BaseBluetoothBridgeHandlerConfiguration;
+
 /**
  * Configuration properties class.
  *
  * @author Hilbrand Bouwkamp - Initial contribution
  */
-public class BlueZAdapterConfiguration {
+public class BlueZAdapterConfiguration extends BaseBluetoothBridgeHandlerConfiguration {
 
     public String address;
 
-    public Boolean discovery;
 }
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java
index 60d32287446d0..ef0b12d886324 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/java/org/openhab/binding/bluetooth/bluez/handler/BlueZBridgeHandler.java
@@ -13,28 +13,15 @@
 package org.openhab.binding.bluetooth.bluez.handler;
 
 import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.CopyOnWriteArraySet;
 import java.util.concurrent.ScheduledFuture;
 import java.util.concurrent.TimeUnit;
 
-import org.eclipse.jdt.annotation.DefaultLocation;
 import org.eclipse.jdt.annotation.NonNullByDefault;
-import org.eclipse.jdt.annotation.Nullable;
 import org.eclipse.smarthome.core.thing.Bridge;
-import org.eclipse.smarthome.core.thing.ChannelUID;
 import org.eclipse.smarthome.core.thing.ThingStatus;
 import org.eclipse.smarthome.core.thing.ThingStatusDetail;
-import org.eclipse.smarthome.core.thing.ThingUID;
-import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
-import org.eclipse.smarthome.core.types.Command;
-import org.openhab.binding.bluetooth.BluetoothAdapter;
+import org.openhab.binding.bluetooth.AbstractBluetoothBridgeHandler;
 import org.openhab.binding.bluetooth.BluetoothAddress;
-import org.openhab.binding.bluetooth.BluetoothDevice;
-import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
-import org.openhab.binding.bluetooth.BluetoothDiscoveryListener;
 import org.openhab.binding.bluetooth.bluez.BlueZBluetoothDevice;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -52,30 +39,15 @@
  * @author Connor Petty - Simplified device scan logic
  */
 @NonNullByDefault
-public class BlueZBridgeHandler extends BaseBridgeHandler implements BluetoothAdapter {
+public class BlueZBridgeHandler extends AbstractBluetoothBridgeHandler<BlueZBluetoothDevice> {
 
     private final Logger logger = LoggerFactory.getLogger(BlueZBridgeHandler.class);
 
     private @NonNullByDefault({}) tinyb.BluetoothAdapter adapter;
 
-    private final Map<String, tinyb.BluetoothDevice> tinybDeviceCache = new ConcurrentHashMap<>();
-
     // Our BT address
     private @NonNullByDefault({}) BluetoothAddress adapterAddress;
 
-    // Internal flag for the discovery configuration
-    private boolean discoveryConfigActive = true;
-    // Actual discovery status.
-    private boolean discoveryActive = true;
-
-    // Map of Bluetooth devices known to this bridge.
-    // This contains the devices from the most recent scan
-    private final @NonNullByDefault({
-            DefaultLocation.RETURN_TYPE }) Map<BluetoothAddress, BlueZBluetoothDevice> devices = new ConcurrentHashMap<>();
-
-    // Set of discovery listeners
-    protected final Set<BluetoothDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
-
     private @NonNullByDefault({}) ScheduledFuture<?> discoveryJob;
 
     /**
@@ -89,6 +61,7 @@ public BlueZBridgeHandler(Bridge bridge) {
 
     @Override
     public void initialize() {
+        super.initialize();
         BluetoothManager manager;
         try {
             manager = BluetoothManager.getBluetoothManager();
@@ -115,11 +88,6 @@ public void initialize() {
             return;
         }
 
-        discoveryActive = discoveryConfigActive = Boolean.TRUE.equals(configuration.discovery);
-        if (discoveryConfigActive) {
-            logger.debug("Deactivated discovery participation.");
-        }
-
         logger.debug("Creating BlueZ adapter with address '{}'", adapterAddress);
 
         for (tinyb.BluetoothAdapter adapter : manager.getAdapters()) {
@@ -136,25 +104,6 @@ public void initialize() {
         updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.CONFIGURATION_ERROR, "No adapter for this address found.");
     }
 
-    @Override
-    public ThingUID getUID() {
-        return getThing().getUID();
-    }
-
-    @Override
-    public void handleCommand(ChannelUID channelUID, Command command) {
-    }
-
-    @Override
-    public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
-        discoveryListeners.add(listener);
-    }
-
-    @Override
-    public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
-        discoveryListeners.remove(listener);
-    }
-
     private void startDiscovery() {
         // we need to make sure the adapter is powered first
         if (!adapter.getPowered()) {
@@ -167,36 +116,23 @@ private void startDiscovery() {
     }
 
     private void refreshDevices() {
-        try {
+        refreshTry: try {
             logger.debug("Refreshing Bluetooth device list...");
             List<tinyb.BluetoothDevice> tinybDevices = adapter.getDevices();
             logger.debug("Found {} Bluetooth devices.", tinybDevices.size());
             for (tinyb.BluetoothDevice tinybDevice : tinybDevices) {
                 BlueZBluetoothDevice device = getDevice(new BluetoothAddress(tinybDevice.getAddress()));
                 device.updateTinybDevice(tinybDevice);
-                notifyDiscoveryListeners(device);
-            }
-            // clean up orphaned entries
-            synchronized (devices) {
-                for (BlueZBluetoothDevice device : devices.values()) {
-                    if (shouldRemove(device)) {
-                        logger.debug("Removing device '{}' due to inactivity", device.getAddress());
-                        device.dispose();
-                        devices.remove(device.getAddress());
-                        discoveryListeners.forEach(listener -> listener.deviceRemoved(device));
-                    }
-                }
+                deviceDiscovered(device);
             }
             // For whatever reason, bluez will sometimes turn off scanning. So we just make sure it keeps running.
             startDiscovery();
-            // everything went fine, so lets switch to online
-            updateStatus(ThingStatus.ONLINE);
         } catch (BluetoothException ex) {
             String message = ex.getMessage();
             if (message != null) {
                 if (message.contains("Operation already in progress")) {
                     // we shouldn't go offline in this case
-                    return;
+                    break refreshTry;
                 }
                 int idx = message.lastIndexOf(':');
                 if (idx != -1) {
@@ -204,33 +140,9 @@ private void refreshDevices() {
                 }
             }
             updateStatus(ThingStatus.OFFLINE, ThingStatusDetail.COMMUNICATION_ERROR, message);
+            return;
         }
-    }
-
-    private boolean shouldRemove(BlueZBluetoothDevice device) {
-        // we can't remove devices with listeners since that means they have a handler.
-        if (device.hasListeners()) {
-            return false;
-        }
-        // devices that are connected won't receive any scan notifications so we can't remove them for being idle
-        if (device.getConnectionState() == ConnectionState.CONNECTED) {
-            return false;
-        }
-        // we remove devices we haven't seen in a while
-        return device.getTimeSinceSeen(TimeUnit.MINUTES) > 5;
-    }
-
-    @Override
-    public void scanStart() {
-        // Enable scanning even while discovery is disabled in config. This allows manual starting discovery.
-        discoveryActive = true;
-    }
-
-    @Override
-    public void scanStop() {
-        // Set active discovery state back to the configured discovery state.
-        discoveryActive = discoveryConfigActive;
-        // We need to keep the adapter in discovery mode as we otherwise won't get any RSSI updates either
+        updateStatus(ThingStatus.ONLINE);
     }
 
     @Override
@@ -239,21 +151,8 @@ public BluetoothAddress getAddress() {
     }
 
     @Override
-    public BlueZBluetoothDevice getDevice(BluetoothAddress bluetoothAddress) {
-        synchronized (devices) {
-            BlueZBluetoothDevice device = devices.get(bluetoothAddress);
-            if (device == null) {
-                device = new BlueZBluetoothDevice(this, bluetoothAddress);
-                device.initialize();
-                devices.put(bluetoothAddress, device);
-            }
-            return device;
-        }
-    }
-
-    @Override
-    public boolean hasDevice(BluetoothAddress address) {
-        return devices.containsKey(address);
+    protected BlueZBluetoothDevice createDevice(BluetoothAddress address) {
+        return new BlueZBluetoothDevice(this, address);
     }
 
     @Override
@@ -265,30 +164,7 @@ public void dispose() {
         if (adapter != null && adapter.getDiscovering()) {
             adapter.stopDiscovery();
         }
-        synchronized (devices) {
-            for (BlueZBluetoothDevice device : devices.values()) {
-                device.dispose();
-            }
-            devices.clear();
-        }
-    }
-
-    private void notifyDiscoveryListeners(BluetoothDevice device) {
-        if (discoveryActive) {
-            if (deviceReachable(device)) {
-                for (BluetoothDiscoveryListener listener : discoveryListeners) {
-                    listener.deviceDiscovered(device);
-                }
-            } else {
-                logger.trace("Not notifying listeners for device '{}', because it is not reachable.",
-                        device.getAddress());
-            }
-        }
-    }
-
-    private boolean deviceReachable(BluetoothDevice device) {
-        Integer rssi = device.getRssi();
-        return rssi != null && rssi != 0;
+        super.dispose();
     }
 
 }
diff --git a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/ESH-INF/thing/bluez.xml b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/ESH-INF/thing/bluez.xml
index 2236bbe2b12ae..ec8fdc6a9e502 100644
--- a/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/ESH-INF/thing/bluez.xml
+++ b/bundles/org.openhab.binding.bluetooth.bluez/src/main/resources/ESH-INF/thing/bluez.xml
@@ -14,12 +14,24 @@
 				<label>Address</label>
 				<description>The Bluetooth address of the adapter in format XX:XX:XX:XX:XX:XX</description>
 			</parameter>
-			<parameter name="discovery" type="boolean">
-				<label>Device Discovery</label>
-				<description>Whether Bluetooth device discovery is always enabled for this adapter</description>
+			<parameter name="backgroundDiscovery" type="boolean">
+				<label>Background Discovery</label>
+				<description>Whether this adapter performs background discovery of Bluetooth devices</description>
 				<advanced>true</advanced>
 				<default>false</default>
 			</parameter>
+			<parameter name="inactiveDeviceCleanupInterval" type="integer" min="1" unit="s">
+				<label>Device Cleanup Interval</label>
+				<description>How often device cleanup is performed</description>
+				<advanced>true</advanced>
+				<default>60</default>
+			</parameter>
+			<parameter name="inactiveDeviceCleanupThreshold" type="integer" min="1" unit="s">
+				<label>Device Cleanup Threshold</label>
+				<description>Timespan a device can remain radio silent before it is eligible for cleanup</description>
+				<advanced>true</advanced>
+				<default>300</default>
+			</parameter>
 		</config-description>
 
 	</bridge-type>
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/AbstractBluetoothBridgeHandler.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/AbstractBluetoothBridgeHandler.java
new file mode 100644
index 0000000000000..5ba2eb780ce32
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/AbstractBluetoothBridgeHandler.java
@@ -0,0 +1,223 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth;
+
+import java.time.ZonedDateTime;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ScheduledFuture;
+import java.util.concurrent.TimeUnit;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+import org.eclipse.jdt.annotation.Nullable;
+import org.eclipse.smarthome.core.thing.Bridge;
+import org.eclipse.smarthome.core.thing.ChannelUID;
+import org.eclipse.smarthome.core.thing.Thing;
+import org.eclipse.smarthome.core.thing.ThingUID;
+import org.eclipse.smarthome.core.thing.binding.BaseBridgeHandler;
+import org.eclipse.smarthome.core.types.Command;
+import org.openhab.binding.bluetooth.BluetoothDevice.ConnectionState;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * This is a abstract superclass for BluetoothAdapter implementations. This class takes care of inactive device cleanup
+ * as well as handling background and active discovery logic.
+ *
+ * Subclasses will primarily be responsible for device discovery
+ *
+ * @author Connor Petty - Initial contribution from refactored code
+ */
+@NonNullByDefault
+public abstract class AbstractBluetoothBridgeHandler<BD extends BluetoothDevice> extends BaseBridgeHandler
+        implements BluetoothAdapter {
+
+    private final Logger logger = LoggerFactory.getLogger(AbstractBluetoothBridgeHandler.class);
+
+    // Set of discovery listeners
+    private final Set<BluetoothDiscoveryListener> discoveryListeners = new CopyOnWriteArraySet<>();
+
+    // Map of Bluetooth devices known to this bridge.
+    // This contains the devices from the most recent scan
+    private final Map<BluetoothAddress, BD> devices = new HashMap<>();
+
+    // Actual discovery status.
+    protected volatile boolean activeScanEnabled = false;
+
+    private BaseBluetoothBridgeHandlerConfiguration config = new BaseBluetoothBridgeHandlerConfiguration();
+
+    private @Nullable ScheduledFuture<?> inactiveRemovalJob;
+
+    /**
+     * Constructor
+     *
+     * @param bridge the bridge definition for this handler
+     */
+    public AbstractBluetoothBridgeHandler(Bridge bridge) {
+        super(bridge);
+    }
+
+    @Override
+    public ThingUID getUID() {
+        return getThing().getUID();
+    }
+
+    @Override
+    public void initialize() {
+        config = getConfigAs(BaseBluetoothBridgeHandlerConfiguration.class);
+
+        int intervalSecs = config.inactiveDeviceCleanupInterval;
+        inactiveRemovalJob = scheduler.scheduleWithFixedDelay(this::removeInactiveDevices, intervalSecs, intervalSecs,
+                TimeUnit.SECONDS);
+    }
+
+    @Override
+    public void dispose() {
+        ScheduledFuture<?> inactiveRemovalJob = this.inactiveRemovalJob;
+        if (inactiveRemovalJob != null) {
+            inactiveRemovalJob.cancel(true);
+        }
+        this.inactiveRemovalJob = null;
+
+        synchronized (devices) {
+            for (BD device : devices.values()) {
+                removeDevice(device);
+            }
+        }
+    }
+
+    @Override
+    public void handleCommand(ChannelUID channelUID, Command command) {
+    }
+
+    private void removeInactiveDevices() {
+        // clean up orphaned entries
+        synchronized (devices) {
+            for (BD device : devices.values()) {
+                if (shouldRemove(device)) {
+                    logger.debug("Removing device '{}' due to inactivity", device.getAddress());
+                    removeDevice(device);
+                }
+            }
+        }
+    }
+
+    protected void removeDevice(BluetoothDevice device) {
+        device.dispose();
+        synchronized (devices) {
+            devices.remove(device.getAddress());
+        }
+        discoveryListeners.forEach(listener -> listener.deviceRemoved(device));
+    }
+
+    private boolean shouldRemove(BluetoothDevice device) {
+        // we can't remove devices with listeners since that means they have a handler.
+        if (device.hasListeners()) {
+            return false;
+        }
+        // devices that are connected won't receive any scan notifications so we can't remove them for being idle
+        if (device.getConnectionState() == ConnectionState.CONNECTED) {
+            return false;
+        }
+
+        // we remove devices we haven't seen in a while
+        return ZonedDateTime.now().minusSeconds(config.inactiveDeviceCleanupThreshold)
+                .isAfter(device.getLastSeenTime());
+    }
+
+    @Override
+    public void addDiscoveryListener(BluetoothDiscoveryListener listener) {
+        discoveryListeners.add(listener);
+    }
+
+    @Override
+    public void removeDiscoveryListener(@Nullable BluetoothDiscoveryListener listener) {
+        discoveryListeners.remove(listener);
+    }
+
+    @Override
+    public void scanStart() {
+        // Enable scanning even while discovery is disabled in config. This allows manual starting discovery.
+        activeScanEnabled = true;
+        refreshDiscoveredDevices();
+    }
+
+    protected void refreshDiscoveredDevices() {
+        logger.debug("Refreshing Bluetooth device list...");
+        synchronized (devices) {
+            devices.values().forEach(this::deviceDiscovered);
+        }
+    }
+
+    @Override
+    public void scanStop() {
+        // Set active discovery state back to the configured discovery state.
+        activeScanEnabled = false;
+        // We need to keep the adapter in discovery mode as we otherwise won't get any RSSI updates either
+    }
+
+    @Override
+    public BD getDevice(BluetoothAddress address) {
+        synchronized (devices) {
+            if (devices.containsKey(address)) {
+                return devices.get(address);
+            }
+            BD device = createDevice(address);
+            device.updateLastSeenTime();
+            devices.put(address, device);
+            return device;
+        }
+    }
+
+    protected abstract BD createDevice(BluetoothAddress address);
+
+    @Override
+    public boolean hasHandlerForDevice(BluetoothAddress address) {
+        String addrStr = address.toString();
+        /*
+         * This type of search is inefficient and won't scale as the number of bluetooth Thing children increases on
+         * this bridge. But implementing a more efficient search would require a bit more overhead.
+         * Luckily though, it is reasonable to assume that the number of Thing children will remain small.
+         */
+        for (Thing childThing : getThing().getThings()) {
+            Object childAddr = childThing.getConfiguration().get(BluetoothBindingConstants.CONFIGURATION_ADDRESS);
+            if (addrStr.equals(childAddr)) {
+                return childThing.getHandler() != null;
+            }
+        }
+        return false;
+    }
+
+    public void deviceDiscovered(BluetoothDevice device) {
+        if (hasHandlerForDevice(device.getAddress())) {
+            // no point in discovering a device that already has a handler
+            return;
+        }
+        if (config.backgroundDiscovery || activeScanEnabled) {
+            if (deviceReachable(device)) {
+                discoveryListeners.forEach(listener -> listener.deviceDiscovered(device));
+            } else {
+                logger.trace("Not notifying listeners for device '{}', because it is not reachable.",
+                        device.getAddress());
+            }
+        }
+    }
+
+    private boolean deviceReachable(BluetoothDevice device) {
+        Integer rssi = device.getRssi();
+        return rssi != null && rssi != 0;
+    }
+
+}
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BaseBluetoothBridgeHandlerConfiguration.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BaseBluetoothBridgeHandlerConfiguration.java
new file mode 100644
index 0000000000000..904db763c74f0
--- /dev/null
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BaseBluetoothBridgeHandlerConfiguration.java
@@ -0,0 +1,29 @@
+/**
+ * Copyright (c) 2010-2020 Contributors to the openHAB project
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License 2.0 which is available at
+ * http://www.eclipse.org/legal/epl-2.0
+ *
+ * SPDX-License-Identifier: EPL-2.0
+ */
+package org.openhab.binding.bluetooth;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * This is the base configuration that all bluetooth bridge implementations will use.
+ * Bridges may choose to use a subclass of this class as their configuration in order to
+ * support more options.
+ *
+ * @author Connor Petty - Initial contribution from refactored code
+ */
+@NonNullByDefault
+public class BaseBluetoothBridgeHandlerConfiguration {
+    public boolean backgroundDiscovery = false;
+    public int inactiveDeviceCleanupInterval = 60;
+    public int inactiveDeviceCleanupThreshold = 300;
+}
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BeaconBluetoothHandler.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BeaconBluetoothHandler.java
index f08714d33f516..3149716c0a52c 100644
--- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BeaconBluetoothHandler.java
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BeaconBluetoothHandler.java
@@ -26,7 +26,6 @@
 import org.eclipse.smarthome.core.types.Command;
 import org.eclipse.smarthome.core.types.RefreshType;
 import org.eclipse.smarthome.core.types.UnDefType;
-import org.openhab.binding.bluetooth.discovery.internal.BluetoothAddressLocker;
 import org.openhab.binding.bluetooth.notification.BluetoothConnectionStatusNotification;
 import org.openhab.binding.bluetooth.notification.BluetoothScanNotification;
 
@@ -81,11 +80,9 @@ public void initialize() {
 
         try {
             deviceLock.lock();
-            BluetoothAddressLocker.lock(address);
             device = adapter.getDevice(address);
             device.addListener(this);
         } finally {
-            BluetoothAddressLocker.unlock(address);
             deviceLock.unlock();
         }
 
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothAdapter.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothAdapter.java
index fabdb2286555b..da3ff8fe92b6f 100644
--- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothAdapter.java
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothAdapter.java
@@ -81,6 +81,6 @@ public interface BluetoothAdapter extends Identifiable<ThingUID> {
      * @param address the {@link BluetoothAddress} to check for
      * @return true if this adapter has a {@link BluetoothDevice} with that address
      */
-    boolean hasDevice(BluetoothAddress address);
+    boolean hasHandlerForDevice(BluetoothAddress address);
 
 }
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java
index cc8c35eb74bc7..dc2a9e706f52b 100644
--- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/BluetoothDevice.java
@@ -12,6 +12,7 @@
  */
 package org.openhab.binding.bluetooth;
 
+import java.time.ZonedDateTime;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -126,6 +127,11 @@ protected enum BluetoothEventType {
      */
     protected @Nullable Integer txPower = null;
 
+    /**
+     * Last time when activity occurred on this device.
+     */
+    protected ZonedDateTime lastSeenTime;
+
     /**
      * The event listeners will be notified of device updates
      */
@@ -140,10 +146,29 @@ protected enum BluetoothEventType {
     public BluetoothDevice(BluetoothAdapter adapter, BluetoothAddress address) {
         this.address = address;
         this.adapter = adapter;
+        this.lastSeenTime = ZonedDateTime.now();
+    }
+
+    /**
+     * Returns the last time this device was active
+     *
+     * @return The last time this device was active
+     */
+    public ZonedDateTime getLastSeenTime() {
+        return lastSeenTime;
     }
 
     /**
-     * Returns the the name of the Bluetooth device.
+     * Updates the last activity timestamp for this device.
+     * Should be called whenever activity occurs on this device.
+     *
+     */
+    public void updateLastSeenTime() {
+        lastSeenTime = ZonedDateTime.now();
+    }
+
+    /**
+     * Returns the name of the Bluetooth device.
      *
      * @return The devices name
      */
@@ -594,6 +619,14 @@ public boolean hasListeners() {
         return !eventListeners.isEmpty();
     }
 
+    /**
+     * Releases resources that this device is using.
+     *
+     */
+    protected void dispose() {
+
+    }
+
     /**
      * Notify the listeners of an event
      *
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothAddressLocker.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothAddressLocker.java
deleted file mode 100644
index 0800722572e54..0000000000000
--- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothAddressLocker.java
+++ /dev/null
@@ -1,55 +0,0 @@
-/**
- * Copyright (c) 2010-2020 Contributors to the openHAB project
- *
- * See the NOTICE file(s) distributed with this work for additional
- * information.
- *
- * This program and the accompanying materials are made available under the
- * terms of the Eclipse Public License 2.0 which is available at
- * http://www.eclipse.org/legal/epl-2.0
- *
- * SPDX-License-Identifier: EPL-2.0
- */
-package org.openhab.binding.bluetooth.discovery.internal;
-
-import java.util.Map;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantLock;
-
-import org.openhab.binding.bluetooth.BluetoothAddress;
-
-/**
- * The {@link BluetoothAddressLocker} handles global locking of BluetoothAddress.
- * This is used to make sure that devices with handlers are not connected to during discovery.
- *
- * @author Connor Petty - Initial Contribution
- */
-public class BluetoothAddressLocker {
-
-    private static Map<BluetoothAddress, LockReference> locks = new ConcurrentHashMap<>();
-
-    public static void lock(BluetoothAddress address) {
-        locks.compute(address, (addr, oldRef) -> {
-            LockReference ref = oldRef;
-            if (ref == null) {
-                ref = new LockReference();
-            }
-            ref.count++;
-            return ref;
-        }).lock.lock();
-    }
-
-    public static void unlock(BluetoothAddress address) {
-        locks.computeIfPresent(address, (addr, ref) -> {
-            ref.count--;
-            ref.lock.unlock();
-            return ref.count <= 0 ? null : ref;
-        });
-    }
-
-    private static class LockReference {
-        private int count = 0;
-        private Lock lock = new ReentrantLock();
-    }
-}
diff --git a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryProcess.java b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryProcess.java
index dad01c4c91e65..99d53c327e9c8 100644
--- a/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryProcess.java
+++ b/bundles/org.openhab.binding.bluetooth/src/main/java/org/openhab/binding/bluetooth/discovery/internal/BluetoothDiscoveryProcess.java
@@ -107,24 +107,19 @@ public DiscoveryResult get() {
         DiscoveryResult result = null;
         if (!connectionParticipants.isEmpty()) {
             BluetoothAddress address = device.getAddress();
-            try {
-                BluetoothAddressLocker.lock(address);
-                if (isAddressAvailable(address)) {
-                    result = findConnectionResult(connectionParticipants);
-                    // make sure to disconnect before letting go of the device
-                    if (device.getConnectionState() == ConnectionState.CONNECTED) {
-                        try {
-                            if (!device.disconnect()) {
-                                logger.debug("Failed to disconnect from device {}", address);
-                            }
-                        } catch (RuntimeException ex) {
-                            logger.warn("Error occurred during bluetooth discovery for device {} on adapter {}",
-                                    address, device.getAdapter().getAddress(), ex);
+            if (isAddressAvailable(address)) {
+                result = findConnectionResult(connectionParticipants);
+                // make sure to disconnect before letting go of the device
+                if (device.getConnectionState() == ConnectionState.CONNECTED) {
+                    try {
+                        if (!device.disconnect()) {
+                            logger.debug("Failed to disconnect from device {}", address);
                         }
+                    } catch (RuntimeException ex) {
+                        logger.warn("Error occurred during bluetooth discovery for device {} on adapter {}", address,
+                                device.getAdapter().getAddress(), ex);
                     }
                 }
-            } finally {
-                BluetoothAddressLocker.unlock(address);
             }
         }
         if (result == null) {
@@ -135,11 +130,7 @@ public DiscoveryResult get() {
 
     private boolean isAddressAvailable(BluetoothAddress address) {
         // if a device with this address has a handler on any of the adapters, we abandon discovery
-        return adapters.stream().filter(adapter -> adapter.hasDevice(address))
-                // get adapter's corresponding device
-                .map(adapter -> adapter.getDevice(address))
-                // make sure nothing is listening to any of them
-                .noneMatch(d -> d.hasListeners());
+        return adapters.stream().noneMatch(adapter -> adapter.hasHandlerForDevice(address));
     }
 
     private DiscoveryResult createDefaultResult(BluetoothDevice device) {
diff --git a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothAdapter.java b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothAdapter.java
index bd831142415a5..d5ace31518b34 100644
--- a/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothAdapter.java
+++ b/bundles/org.openhab.binding.bluetooth/src/test/java/org/openhab/binding/bluetooth/MockBluetoothAdapter.java
@@ -63,8 +63,8 @@ public MockBluetoothDevice getDevice(BluetoothAddress address) {
     }
 
     @Override
-    public boolean hasDevice(BluetoothAddress address) {
-        return devices.containsKey(address);
+    public boolean hasHandlerForDevice(BluetoothAddress address) {
+        return false;
     }
 
 }