diff --git a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java b/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java index 5bb975e1a..0533d6e93 100644 --- a/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java +++ b/bramble-android/src/main/java/org/briarproject/bramble/plugin/droidtooth/DroidtoothPlugin.java @@ -19,8 +19,7 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.PluginException; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; +import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; @@ -75,7 +74,8 @@ import static org.briarproject.bramble.util.PrivacyUtils.scrubMacAddress; @MethodsNotNullByDefault @ParametersNotNullByDefault -class DroidtoothPlugin implements DuplexPlugin { +class DroidtoothPlugin + extends AbstractBluetoothPlugin{ private static final Logger LOG = Logger.getLogger(DroidtoothPlugin.class.getName()); @@ -84,16 +84,10 @@ class DroidtoothPlugin implements DuplexPlugin { private static final String DISCOVERY_FINISHED = "android.bluetooth.adapter.action.DISCOVERY_FINISHED"; - private final Executor ioExecutor; private final AndroidExecutor androidExecutor; private final Context appContext; - private final SecureRandom secureRandom; - private final Backoff backoff; - private final DuplexPluginCallback callback; - private final int maxLatency; private final AtomicBoolean used = new AtomicBoolean(false); - private volatile boolean running = false; private volatile boolean wasEnabledByUs = false; private volatile BluetoothStateReceiver receiver = null; private volatile BluetoothServerSocket socket = null; @@ -104,29 +98,15 @@ class DroidtoothPlugin implements DuplexPlugin { DroidtoothPlugin(Executor ioExecutor, AndroidExecutor androidExecutor, Context appContext, SecureRandom secureRandom, Backoff backoff, DuplexPluginCallback callback, int maxLatency) { - this.ioExecutor = ioExecutor; + + super(ioExecutor, secureRandom, backoff, maxLatency, callback); this.androidExecutor = androidExecutor; this.appContext = appContext; - this.secureRandom = secureRandom; - this.backoff = backoff; - this.callback = callback; - this.maxLatency = maxLatency; } @Override - public TransportId getId() { - return ID; - } - - @Override - public int getMaxLatency() { - return maxLatency; - } - - @Override - public int getMaxIdleTime() { - // Bluetooth detects dead connections so we don't need keepalives - return Integer.MAX_VALUE; + protected void close(S ss) throws IOException { + ((BluetoothServerSocket)ss).close(); } @Override @@ -194,14 +174,14 @@ class DroidtoothPlugin implements DuplexPlugin { BluetoothServerSocket ss; try { ss = adapter.listenUsingInsecureRfcommWithServiceRecord( - "RFCOMM", getUuid()); + "RFCOMM", UUID.fromString(getUuid())); } catch (IOException e) { if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); return; } if (!isRunning()) { - tryToClose(ss); + tryToClose((S)ss); return; } LOG.info("Socket bound"); @@ -213,29 +193,6 @@ class DroidtoothPlugin implements DuplexPlugin { }); } - private UUID getUuid() { - String uuid = callback.getLocalProperties().get(PROP_UUID); - if (uuid == null) { - byte[] random = new byte[UUID_BYTES]; - secureRandom.nextBytes(random); - uuid = UUID.nameUUIDFromBytes(random).toString(); - TransportProperties p = new TransportProperties(); - p.put(PROP_UUID, uuid); - callback.mergeLocalProperties(p); - } - return UUID.fromString(uuid); - } - - private void tryToClose(@Nullable BluetoothServerSocket ss) { - try { - if (ss != null) ss.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - callback.transportDisabled(); - } - } - private void acceptContactConnections() { while (isRunning()) { BluetoothSocket s; @@ -261,9 +218,9 @@ class DroidtoothPlugin implements DuplexPlugin { @Override public void stop() { - running = false; + this.running = false; if (receiver != null) appContext.unregisterReceiver(receiver); - tryToClose(socket); + tryToClose((S)socket); // Disable Bluetooth if we enabled it and it's still enabled if (wasEnabledByUs && adapter.isEnabled()) { if (adapter.disable()) LOG.info("Disabling Bluetooth"); @@ -276,42 +233,20 @@ class DroidtoothPlugin implements DuplexPlugin { return running && adapter != null && adapter.isEnabled(); } - @Override - public boolean shouldPoll() { - return true; - } - - @Override - public int getPollingInterval() { - return backoff.getPollingInterval(); - } - - @Override - public void poll(Collection connected) { - if (!isRunning()) return; - backoff.increment(); - // Try to connect to known devices in parallel - Map remote = - callback.getRemoteProperties(); - for (Entry e : remote.entrySet()) { - final ContactId c = e.getKey(); - if (connected.contains(c)) continue; - final String address = e.getValue().get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) continue; - final String uuid = e.getValue().get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) continue; - ioExecutor.execute(new Runnable() { - @Override - public void run() { - if (!running) return; - BluetoothSocket s = connect(address, uuid); - if (s != null) { - backoff.reset(); - callback.outgoingConnectionCreated(c, wrapSocket(s)); - } + protected Runnable returnPollRunnable(final String address, final String uuid, + final ContactId c) { + return new Runnable() { + @Override + public void run() { + if (!running) return; + BluetoothSocket s = connect(address, uuid); + if (s != null) { + backoff.reset(); + callback.outgoingConnectionCreated(c, wrapSocket(s)); } - }); - } + } + + }; } @Nullable @@ -347,37 +282,17 @@ class DroidtoothPlugin implements DuplexPlugin { LOG.info("Failed to connect to " + scrubMacAddress(address) + ": " + e); } - tryToClose(s); + tryToClose((S)s); return null; } } - private void tryToClose(@Nullable Closeable c) { - try { - if (c != null) c.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } - } - @Override - public DuplexTransportConnection createConnection(ContactId c) { - if (!isRunning()) return null; - TransportProperties p = callback.getRemoteProperties().get(c); - if (p == null) return null; - String address = p.get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) return null; - String uuid = p.get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) return null; + public DuplexTransportConnection connectToAddress(String address, String uuid) { BluetoothSocket s = connect(address, uuid); - if (s == null) return null; - return new DroidtoothTransportConnection(this, s); + return s == null ? null : wrapSocket(s); } - @Override - public boolean supportsInvitations() { - return true; - } @Override public DuplexTransportConnection createInvitationConnection(PseudoRandom r, @@ -426,7 +341,7 @@ class DroidtoothPlugin implements DuplexPlugin { return null; } finally { // Closing the socket will terminate the listener task - tryToClose(ss); + tryToClose((S)ss); closeSockets(futures, chosen); } } @@ -458,11 +373,6 @@ class DroidtoothPlugin implements DuplexPlugin { }); } - @Override - public boolean supportsKeyAgreement() { - return true; - } - @Override public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { if (!isRunning()) return null; @@ -487,31 +397,6 @@ class DroidtoothPlugin implements DuplexPlugin { return new BluetoothKeyAgreementListener(descriptor, ss); } - @Override - public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, long timeout) { - if (!isRunning()) return null; - String address; - try { - address = parseAddress(descriptor); - } catch (FormatException e) { - LOG.info("Invalid address in key agreement descriptor"); - return null; - } - // No truncation necessary because COMMIT_LENGTH = 16 - UUID uuid = UUID.nameUUIDFromBytes(commitment); - if (LOG.isLoggable(INFO)) - LOG.info("Connecting to key agreement UUID " + uuid); - BluetoothSocket s = connect(address, uuid.toString()); - if (s == null) return null; - return new DroidtoothTransportConnection(this, s); - } - - private String parseAddress(BdfList descriptor) throws FormatException { - byte[] mac = descriptor.getRaw(1); - if (mac.length != 6) throw new FormatException(); - return StringUtils.macToString(mac); - } private class BluetoothStateReceiver extends BroadcastReceiver { @@ -523,7 +408,7 @@ class DroidtoothPlugin implements DuplexPlugin { bind(); } else if (state == STATE_OFF) { LOG.info("Bluetooth disabled"); - tryToClose(socket); + tryToClose((S)socket); } int scanMode = intent.getIntExtra(EXTRA_SCAN_MODE, 0); if (scanMode == SCAN_MODE_NONE) { diff --git a/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractBluetoothPlugin.java b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractBluetoothPlugin.java new file mode 100644 index 000000000..59e55db35 --- /dev/null +++ b/bramble-api/src/main/java/org/briarproject/bramble/api/plugin/duplex/AbstractBluetoothPlugin.java @@ -0,0 +1,190 @@ +package org.briarproject.bramble.api.plugin.duplex; + +import org.briarproject.bramble.api.FormatException; +import org.briarproject.bramble.api.contact.ContactId; +import org.briarproject.bramble.api.data.BdfList; +import org.briarproject.bramble.api.plugin.Backoff; +import org.briarproject.bramble.api.plugin.TransportId; +import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; +import org.briarproject.bramble.api.properties.TransportProperties; +import org.briarproject.bramble.util.StringUtils; + +import static java.util.logging.Level.INFO; +import static java.util.logging.Level.WARNING; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_ADDRESS; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.PROP_UUID; +import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; + +import java.io.IOException; +import java.security.SecureRandom; +import java.util.Collection; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.logging.Logger; + +import javax.annotation.Nullable; + +/** + * Created by Santiago Torres-Arias on 1/10/17. + */ + +public abstract class AbstractBluetoothPlugin implements DuplexPlugin { + + private static final Logger LOG = + Logger.getLogger("Halp"); + + protected final Executor ioExecutor; + protected final SecureRandom secureRandom; + protected final Backoff backoff; + protected final int maxLatency; + protected final DuplexPluginCallback callback; + protected final S ss = null; + + protected volatile boolean running = false; + + protected Runnable pollRunnable = null; + + public AbstractBluetoothPlugin(Executor ioExecutor, + SecureRandom secureRandom, + Backoff backoff, int maxLatency, + DuplexPluginCallback callback) { + this.ioExecutor = ioExecutor; + this.secureRandom = secureRandom; + this.backoff = backoff; + this.maxLatency = maxLatency; + this.callback = callback; + } + + @Override + public boolean supportsInvitations() { + return true; + } + + @Override + public boolean supportsKeyAgreement() { + return true; + } + + @Override + public boolean isRunning() { + return running; + } + + @Override + public boolean shouldPoll() { + return true; + } + + @Override + public int getPollingInterval() { + return backoff.getPollingInterval(); + } + + protected String getUuid() { + String uuid = callback.getLocalProperties().get(PROP_UUID); + if (uuid == null) { + byte[] random = new byte[UUID_BYTES]; + secureRandom.nextBytes(random); + uuid = UUID.nameUUIDFromBytes(random).toString(); + TransportProperties p = new TransportProperties(); + p.put(PROP_UUID, uuid); + callback.mergeLocalProperties(p); + } + return uuid; + } + + @Override + public TransportId getId() { + return ID; + } + + @Override + public int getMaxLatency() { + return maxLatency; + } + + @Override + public int getMaxIdleTime() { + // Bluetooth detects dead connections so we don't need keepalives + return Integer.MAX_VALUE; + } + + protected String parseAddress(BdfList descriptor) throws FormatException { + byte[] mac = descriptor.getRaw(1); + if (mac.length != 6) throw new FormatException(); + return StringUtils.macToString(mac); + } + + protected void tryToClose(@Nullable S ss) { + try { + if (ss != null) close(ss); + } catch (IOException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + } finally { + callback.transportDisabled(); + } + } + + protected abstract void close(S ss) throws IOException; + + public void stop() { + running = false; + tryToClose(ss); + } + + @Override + public void poll(final Collection connected) { + if (!running) return; + backoff.increment(); + // Try to connect to known devices in parallel + Map remote = + callback.getRemoteProperties(); + for (Map.Entry e : remote.entrySet()) { + final ContactId c = e.getKey(); + if (connected.contains(c)) continue; + final String address = e.getValue().get(PROP_ADDRESS); + if (StringUtils.isNullOrEmpty(address)) continue; + final String uuid = e.getValue().get(PROP_UUID); + if (StringUtils.isNullOrEmpty(uuid)) continue; + ioExecutor.execute(returnPollRunnable(address,uuid, c)); + } + } + + protected abstract Runnable returnPollRunnable(String address, String uuid, + ContactId c); + + @Override + public DuplexTransportConnection createConnection(ContactId c) { + if (!isRunning()) return null; + TransportProperties p = callback.getRemoteProperties().get(c); + if (p == null) return null; + String address = p.get(PROP_ADDRESS); + if (StringUtils.isNullOrEmpty(address)) return null; + String uuid = p.get(PROP_UUID); + if (StringUtils.isNullOrEmpty(uuid)) return null; + return connectToAddress(address, uuid); + } + + protected abstract DuplexTransportConnection connectToAddress(String address, String uuid); + + @Override + public DuplexTransportConnection createKeyAgreementConnection( + byte[] commitment, BdfList descriptor, long timeout) { + if (!isRunning()) return null; + String address; + try { + address = parseAddress(descriptor); + } catch (FormatException e) { + LOG.info("Invalid address in key agreement descriptor"); + return null; + } + // No truncation necessary because COMMIT_LENGTH = 16 + String uuid = UUID.nameUUIDFromBytes(commitment).toString(); + if (LOG.isLoggable(INFO)) + LOG.info("Connecting to key agreement UUID " + uuid); + return connectToAddress(address, uuid); + } + +} diff --git a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java index 924079103..301bc4ebb 100644 --- a/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java +++ b/bramble-j2se/src/main/java/org/briarproject/bramble/plugin/bluetooth/BluetoothPlugin.java @@ -10,14 +10,14 @@ import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault; import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault; import org.briarproject.bramble.api.plugin.Backoff; import org.briarproject.bramble.api.plugin.PluginException; -import org.briarproject.bramble.api.plugin.TransportId; -import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin; +import org.briarproject.bramble.api.plugin.duplex.AbstractBluetoothPlugin; import org.briarproject.bramble.api.plugin.duplex.DuplexPluginCallback; import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection; import org.briarproject.bramble.api.properties.TransportProperties; import org.briarproject.bramble.util.OsUtils; import org.briarproject.bramble.util.StringUtils; +import java.io.IOError; import java.io.IOException; import java.io.InputStream; import java.security.SecureRandom; @@ -57,46 +57,22 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.UUID_BYTES; @MethodsNotNullByDefault @ParametersNotNullByDefault -class BluetoothPlugin implements DuplexPlugin { +class BluetoothPlugin extends AbstractBluetoothPlugin { private static final Logger LOG = Logger.getLogger(BluetoothPlugin.class.getName()); - private final Executor ioExecutor; - private final SecureRandom secureRandom; - private final Backoff backoff; - private final DuplexPluginCallback callback; - private final int maxLatency; private final Semaphore discoverySemaphore = new Semaphore(1); private final AtomicBoolean used = new AtomicBoolean(false); - private volatile boolean running = false; private volatile StreamConnectionNotifier socket = null; private volatile LocalDevice localDevice = null; - BluetoothPlugin(Executor ioExecutor, SecureRandom secureRandom, - Backoff backoff, DuplexPluginCallback callback, int maxLatency) { - this.ioExecutor = ioExecutor; - this.secureRandom = secureRandom; - this.backoff = backoff; - this.callback = callback; - this.maxLatency = maxLatency; - } - - @Override - public TransportId getId() { - return ID; - } - - @Override - public int getMaxLatency() { - return maxLatency; - } - - @Override - public int getMaxIdleTime() { - // Bluetooth detects dead connections so we don't need keepalives - return Integer.MAX_VALUE; + BluetoothPlugin(Executor ioExecutor, + SecureRandom secureRandom, + Backoff backoff, int maxLatency, + DuplexPluginCallback callback) { + super(ioExecutor, secureRandom, backoff, maxLatency, callback); } @Override @@ -139,7 +115,7 @@ class BluetoothPlugin implements DuplexPlugin { return; } if (!running) { - tryToClose(ss); + tryToClose((S)ss); return; } socket = ss; @@ -153,29 +129,16 @@ class BluetoothPlugin implements DuplexPlugin { private String makeUrl(String address, String uuid) { return "btspp://" + address + ":" + uuid + ";name=RFCOMM"; } - - private String getUuid() { - String uuid = callback.getLocalProperties().get(PROP_UUID); - if (uuid == null) { - byte[] random = new byte[UUID_BYTES]; - secureRandom.nextBytes(random); - uuid = UUID.nameUUIDFromBytes(random).toString(); - TransportProperties p = new TransportProperties(); - p.put(PROP_UUID, uuid); - callback.mergeLocalProperties(p); - } - return uuid; - } - - private void tryToClose(@Nullable StreamConnectionNotifier ss) { - try { - if (ss != null) ss.close(); - } catch (IOException e) { - if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); - } finally { - callback.transportDisabled(); - } - } +// +// private void tryToClose(@Nullable StreamConnectionNotifier ss) { +// try { +// if (ss != null) ss.close(); +// } catch (IOException e) { +// if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); +// } finally { +// callback.transportDisabled(); +// } +// } private void acceptContactConnections(StreamConnectionNotifier ss) { while (true) { @@ -196,54 +159,33 @@ class BluetoothPlugin implements DuplexPlugin { private DuplexTransportConnection wrapSocket(StreamConnection s) { return new BluetoothTransportConnection(this, s); } +// +// @Override +// public void stop() { +// running = false; +// tryToClose(socket); +// } @Override - public void stop() { - running = false; - tryToClose(socket); + protected void close(S ss) throws IOException { + ((StreamConnection)ss).close(); } @Override - public boolean isRunning() { - return running; - } + public Runnable returnPollRunnable(final String address, final String uuid, + final ContactId c) { - @Override - public boolean shouldPoll() { - return true; - } - - @Override - public int getPollingInterval() { - return backoff.getPollingInterval(); - } - - @Override - public void poll(final Collection connected) { - if (!running) return; - backoff.increment(); - // Try to connect to known devices in parallel - Map remote = - callback.getRemoteProperties(); - for (Entry e : remote.entrySet()) { - final ContactId c = e.getKey(); - if (connected.contains(c)) continue; - final String address = e.getValue().get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) continue; - final String uuid = e.getValue().get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) continue; - ioExecutor.execute(new Runnable() { - @Override - public void run() { - if (!running) return; - StreamConnection s = connect(makeUrl(address, uuid)); - if (s != null) { - backoff.reset(); - callback.outgoingConnectionCreated(c, wrapSocket(s)); - } + return new Runnable() { + @Override + public void run() { + if (!running) return; + StreamConnection s = connect(makeUrl(address, uuid)); + if (s != null) { + backoff.reset(); + callback.outgoingConnectionCreated(c, wrapSocket(s)); } - }); - } + } + }; } private StreamConnection connect(String url) { @@ -259,25 +201,13 @@ class BluetoothPlugin implements DuplexPlugin { } @Override - public DuplexTransportConnection createConnection(ContactId c) { - if (!running) return null; - TransportProperties p = callback.getRemoteProperties().get(c); - if (p == null) return null; - String address = p.get(PROP_ADDRESS); - if (StringUtils.isNullOrEmpty(address)) return null; - String uuid = p.get(PROP_UUID); - if (StringUtils.isNullOrEmpty(uuid)) return null; - String url = makeUrl(address, uuid); + protected DuplexTransportConnection connectToAddress(String address, String uuid) { + String url = makeUrl(address, uuid); StreamConnection s = connect(url); if (s == null) return null; return new BluetoothTransportConnection(this, s); } - @Override - public boolean supportsInvitations() { - return true; - } - @Override public DuplexTransportConnection createInvitationConnection(PseudoRandom r, long timeout, boolean alice) { @@ -297,7 +227,7 @@ class BluetoothPlugin implements DuplexPlugin { return null; } if (!running) { - tryToClose(ss); + tryToClose((S)ss); return null; } // Create the background tasks @@ -330,7 +260,7 @@ class BluetoothPlugin implements DuplexPlugin { return null; } finally { // Closing the socket will terminate the listener task - tryToClose(ss); + tryToClose((S)ss); closeSockets(futures, chosen); } } @@ -362,11 +292,6 @@ class BluetoothPlugin implements DuplexPlugin { }); } - @Override - public boolean supportsKeyAgreement() { - return true; - } - @Override public KeyAgreementListener createKeyAgreementListener(byte[] commitment) { if (!running) return null; @@ -385,7 +310,7 @@ class BluetoothPlugin implements DuplexPlugin { return null; } if (!running) { - tryToClose(ss); + tryToClose((S)ss); return null; } BdfList descriptor = new BdfList(); @@ -395,33 +320,6 @@ class BluetoothPlugin implements DuplexPlugin { return new BluetoothKeyAgreementListener(descriptor, ss); } - @Override - public DuplexTransportConnection createKeyAgreementConnection( - byte[] commitment, BdfList descriptor, long timeout) { - if (!isRunning()) return null; - String address; - try { - address = parseAddress(descriptor); - } catch (FormatException e) { - LOG.info("Invalid address in key agreement descriptor"); - return null; - } - // No truncation necessary because COMMIT_LENGTH = 16 - String uuid = UUID.nameUUIDFromBytes(commitment).toString(); - if (LOG.isLoggable(INFO)) - LOG.info("Connecting to key agreement UUID " + uuid); - String url = makeUrl(address, uuid); - StreamConnection s = connect(url); - if (s == null) return null; - return new BluetoothTransportConnection(this, s); - } - - private String parseAddress(BdfList descriptor) throws FormatException { - byte[] mac = descriptor.getRaw(1); - if (mac.length != 6) throw new FormatException(); - return StringUtils.macToString(mac); - } - private void makeDeviceDiscoverable() { // Try to make the device discoverable (requires root on Linux) try {