diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothListener.java b/components/net/sf/briar/plugins/bluetooth/BluetoothListener.java
new file mode 100644
index 0000000000000000000000000000000000000000..57723b2ab5cffd924a96446e0f89700a9a76f97b
--- /dev/null
+++ b/components/net/sf/briar/plugins/bluetooth/BluetoothListener.java
@@ -0,0 +1,86 @@
+package net.sf.briar.plugins.bluetooth;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.bluetooth.BluetoothStateException;
+import javax.bluetooth.DeviceClass;
+import javax.bluetooth.DiscoveryAgent;
+import javax.bluetooth.DiscoveryListener;
+import javax.bluetooth.RemoteDevice;
+import javax.bluetooth.ServiceRecord;
+import javax.bluetooth.UUID;
+
+import net.sf.briar.api.ContactId;
+
+class BluetoothListener implements DiscoveryListener {
+
+	private static final int[] ATTRIBUTES = { 0x100 }; // Service name
+
+	private final AtomicInteger searches = new AtomicInteger(1);
+	private final DiscoveryAgent discoveryAgent;
+	private final Map<String, ContactId> addresses;
+	private final Map<ContactId, String> uuids;
+	private final Map<ContactId, String> urls;
+
+	BluetoothListener(DiscoveryAgent discoveryAgent,
+			Map<String, ContactId> addresses, Map<ContactId, String> uuids) {
+		this.discoveryAgent = discoveryAgent;
+		this.addresses = addresses;
+		this.uuids = uuids;
+		urls = Collections.synchronizedMap(new HashMap<ContactId, String>());
+	}
+
+	public Map<ContactId, String> getUrls() {
+		return urls;
+	}
+
+	public void deviceDiscovered(RemoteDevice device, DeviceClass deviceClass) {
+		// Do we recognise the address?
+		ContactId contactId = addresses.get(device.getBluetoothAddress());
+		if(contactId == null) return;
+		// Do we have a UUID for this contact?
+		String uuid = uuids.get(contactId);
+		if(uuid == null) return;
+		UUID[] uuids = new UUID[] { new UUID(uuid, false) };
+		// Try to discover the services associated with the UUID
+		try {
+			discoveryAgent.searchServices(ATTRIBUTES, uuids, device, this);
+			searches.incrementAndGet();
+		} catch(BluetoothStateException e) {
+			// FIXME: Logging
+			e.printStackTrace();
+		}
+	}
+
+	public void inquiryCompleted(int discoveryType) {
+		if(searches.decrementAndGet() == 0) {
+			synchronized(this) {
+				notifyAll();
+			}
+		}
+	}
+
+	public void servicesDiscovered(int transaction, ServiceRecord[] services) {
+		for(ServiceRecord record : services) {
+			// Do we recognise the address?
+			RemoteDevice device = record.getHostDevice();
+			ContactId c = addresses.get(device.getBluetoothAddress());
+			if(c == null) continue;
+			// Store the URL
+			String url = record.getConnectionURL(
+					ServiceRecord.NOAUTHENTICATE_NOENCRYPT, false);
+			if(url != null) urls.put(c, url);
+		}
+	}
+
+	public void serviceSearchCompleted(int transaction, int response) {
+		if(searches.decrementAndGet() == 0) {
+			synchronized(this) {
+				notifyAll();
+			}
+		}
+	}
+}
diff --git a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
index c79d2e59060f6e85d034ada70021299c5bd33cf9..1c600eb37fe70ef69d1083e3acb418ac6be490ce 100644
--- a/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
+++ b/components/net/sf/briar/plugins/bluetooth/BluetoothPlugin.java
@@ -1,7 +1,12 @@
 package net.sf.briar.plugins.bluetooth;
 
 import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
 import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Random;
 import java.util.TreeMap;
 import java.util.concurrent.Executor;
 
@@ -20,6 +25,8 @@ import net.sf.briar.api.transport.stream.StreamTransportCallback;
 import net.sf.briar.api.transport.stream.StreamTransportConnection;
 import net.sf.briar.api.transport.stream.StreamTransportPlugin;
 import net.sf.briar.plugins.AbstractPlugin;
+import net.sf.briar.util.OsUtils;
+import net.sf.briar.util.StringUtils;
 
 class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 
@@ -27,16 +34,14 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 
 	private static final TransportId id = new TransportId(TRANSPORT_ID);
 
-	private final String uuid;
 	private final long pollingInterval;
 
 	private StreamTransportCallback callback = null;
 	private LocalDevice localDevice = null;
 	private StreamConnectionNotifier streamConnectionNotifier = null;
 
-	BluetoothPlugin(Executor executor, String uuid, long pollingInterval) {
+	BluetoothPlugin(Executor executor, long pollingInterval) {
 		super(executor);
-		this.uuid = uuid;
 		this.pollingInterval = pollingInterval;
 	}
 
@@ -51,7 +56,14 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 		super.start(localProperties, remoteProperties, config);
 		this.callback = callback;
 		// Initialise the Bluetooth stack
-		localDevice = LocalDevice.getLocalDevice();
+		try {
+			localDevice = LocalDevice.getLocalDevice();
+		} catch(UnsatisfiedLinkError e) {
+			// On Linux the user may need to install libbluetooth-dev
+			if(OsUtils.isLinux())
+				callback.showMessage("BLUETOOTH_INSTALL LIBS");
+			throw e;
+		}
 		executor.execute(createBinder());
 	}
 
@@ -72,19 +84,17 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 	}
 
 	private void bind() {
+		String uuid;
 		synchronized(this) {
 			if(!started) return;
+			uuid = config.get("uuid");
+			if(uuid == null) uuid = createAndSetUuid();
 		}
 		// Try to make the device discoverable (requires root on Linux)
 		try {
 			localDevice.setDiscoverable(DiscoveryAgent.GIAC);
 		} catch(BluetoothStateException e) {
 			// FIXME: Logging
-			try {
-				localDevice.setDiscoverable(DiscoveryAgent.LIAC);
-			} catch(BluetoothStateException e1) {
-				// FIXME: Logging
-			}
 		}
 		// Bind the port
 		String url = "btspp://localhost:" + uuid + ";name=" + uuid;
@@ -96,14 +106,33 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 			return;
 		}
 		synchronized(this) {
-			if(!started) return;
+			if(!started) {
+				try {
+					scn.close();
+				} catch(IOException e) {
+					// FIXME: Logging
+				}
+				return;
+			}
 			streamConnectionNotifier = scn;
-			setConnectionUrl(localDevice.getBluetoothAddress());
+			setLocalBluetoothAddress(localDevice.getBluetoothAddress());
 			startListener();
 		}
 	}
 
+	private String createAndSetUuid() {
+		assert started;
+		byte[] b = new byte[16];
+		new Random().nextBytes(b); // FIXME: Use a SecureRandom?
+		String uuid = StringUtils.toHexString(b);
+		Map<String, String> m = new TreeMap<String, String>(config);
+		m.put("uuid", uuid);
+		callback.setConfig(m);
+		return uuid;
+	}
+
 	private void startListener() {
+		assert started;
 		new Thread() {
 			@Override
 			public void run() {
@@ -142,11 +171,10 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 		}
 	}
 
-	private void setConnectionUrl(String address) {
-		// Update the local properties with the connection URL
-		String url = "btspp://" + address + ":" + uuid + ";name=" + uuid;
+	private void setLocalBluetoothAddress(String address) {
+		assert started;
 		Map<String, String> m = new TreeMap<String, String>(localProperties);
-		m.put("url", url);
+		m.put("address", address);
 		callback.setLocalProperties(m);
 	}
 
@@ -160,38 +188,74 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 
 	public synchronized void poll() {
 		if(!started) return;
-		for(ContactId c : remoteProperties.keySet()) {
-			executor.execute(createConnector(c));
-		}
+		executor.execute(createConnectors(remoteProperties.keySet()));
 	}
 
-	private Runnable createConnector(final ContactId c) {
+	private Runnable createConnectors(final Collection<ContactId> contacts) {
 		return new Runnable() {
 			public void run() {
-				connect(c);
+				connectAndCallBack(contacts);
 			}
 		};
 	}
 
-	private StreamTransportConnection connect(ContactId c) {
-		StreamTransportConnection conn = createAndConnectSocket(c);
-		if(conn != null) {
-			synchronized(this) {
-				if(started) callback.outgoingConnectionCreated(c, conn);
+	private void connectAndCallBack(Collection<ContactId> contacts) {
+		Map<ContactId, String> discovered = discover(contacts);
+		for(Entry<ContactId, String> e : discovered.entrySet()) {
+			ContactId c = e.getKey();
+			String url = e.getValue();
+			StreamTransportConnection conn = createConnection(c, url);
+			if(conn != null) {
+				synchronized(this) {
+					if(started) callback.outgoingConnectionCreated(c, conn);
+				}
 			}
 		}
-		return conn;
 	}
 
-	private StreamTransportConnection createAndConnectSocket(ContactId c) {
+	private Map<ContactId, String> discover(Collection<ContactId> contacts) {
+		DiscoveryAgent discoveryAgent;
+		Map<String, ContactId> addresses;
+		Map<ContactId, String> uuids;
+		synchronized(this) {
+			if(!started) return Collections.emptyMap();
+			if(localDevice == null) return Collections.emptyMap();
+			discoveryAgent = localDevice.getDiscoveryAgent();
+			addresses = new HashMap<String, ContactId>();
+			uuids = new HashMap<ContactId, String>();
+			for(Entry<ContactId, Map<String, String>> e
+					: remoteProperties.entrySet()) {
+				ContactId c = e.getKey();
+				Map<String, String> properties = e.getValue();
+				String address = properties.get("address");
+				String uuid = properties.get("uuid");
+				if(address != null && uuid != null) {
+					addresses.put(address, c);
+					uuids.put(c, uuid);
+				}
+			}
+		}
+		BluetoothListener listener =
+			new BluetoothListener(discoveryAgent, addresses, uuids);
+		try {
+			synchronized(listener) {
+				discoveryAgent.startInquiry(DiscoveryAgent.GIAC, listener);
+				listener.wait();
+			}
+		} catch(BluetoothStateException e) {
+			// FIXME: Logging
+		} catch(InterruptedException e) {
+			// FIXME: Logging
+		}
+		return listener.getUrls();
+	}
+
+	private StreamTransportConnection createConnection(ContactId c,
+			String url) {
 		try {
-			String url;
 			synchronized(this) {
 				if(!started) return null;
-				Map<String, String> properties = remoteProperties.get(c);
-				if(properties == null) return null;
-				url = properties.get("url");
-				if(url == null) return null;
+				if(!remoteProperties.containsKey(c)) return null;
 			}
 			StreamConnection s = (StreamConnection) Connector.open(url);
 			return new BluetoothTransportConnection(s);
@@ -202,9 +266,8 @@ class BluetoothPlugin extends AbstractPlugin implements StreamTransportPlugin {
 	}
 
 	public StreamTransportConnection createConnection(ContactId c) {
-		synchronized(this) {
-			if(!started) return null;
-		}
-		return createAndConnectSocket(c);
+		Map<ContactId, String> discovered = discover(Collections.singleton(c));
+		String url = discovered.get(c);
+		return url == null ? null : createConnection(c, url);
 	}
 }