mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-19 22:29:53 +01:00
Symmetric invitation protocol for the Droidtooth plugin.
This commit is contained in:
@@ -10,10 +10,11 @@ import static java.util.logging.Level.INFO;
|
|||||||
import static java.util.logging.Level.WARNING;
|
import static java.util.logging.Level.WARNING;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.SocketTimeoutException;
|
|
||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@@ -21,12 +22,15 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
import net.sf.briar.api.ContactId;
|
import net.sf.briar.api.ContactId;
|
||||||
import net.sf.briar.api.TransportId;
|
import net.sf.briar.api.TransportId;
|
||||||
import net.sf.briar.api.TransportProperties;
|
import net.sf.briar.api.TransportProperties;
|
||||||
import net.sf.briar.api.android.AndroidExecutor;
|
import net.sf.briar.api.android.AndroidExecutor;
|
||||||
|
import net.sf.briar.api.clock.Clock;
|
||||||
import net.sf.briar.api.crypto.PseudoRandom;
|
import net.sf.briar.api.crypto.PseudoRandom;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
@@ -61,6 +65,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
|
private final Clock clock;
|
||||||
private final DuplexPluginCallback callback;
|
private final DuplexPluginCallback callback;
|
||||||
private final int maxFrameLength;
|
private final int maxFrameLength;
|
||||||
private final long maxLatency, pollingInterval;
|
private final long maxLatency, pollingInterval;
|
||||||
@@ -72,13 +77,14 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
private volatile BluetoothAdapter adapter = null;
|
private volatile BluetoothAdapter adapter = null;
|
||||||
|
|
||||||
DroidtoothPlugin(Executor pluginExecutor, AndroidExecutor androidExecutor,
|
DroidtoothPlugin(Executor pluginExecutor, AndroidExecutor androidExecutor,
|
||||||
Context appContext, SecureRandom secureRandom,
|
Context appContext, SecureRandom secureRandom, Clock clock,
|
||||||
DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
|
DuplexPluginCallback callback, int maxFrameLength, long maxLatency,
|
||||||
long pollingInterval) {
|
long pollingInterval) {
|
||||||
this.pluginExecutor = pluginExecutor;
|
this.pluginExecutor = pluginExecutor;
|
||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
|
this.clock = clock;
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
this.maxFrameLength = maxFrameLength;
|
this.maxFrameLength = maxFrameLength;
|
||||||
this.maxLatency = maxLatency;
|
this.maxLatency = maxLatency;
|
||||||
@@ -271,15 +277,18 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
pluginExecutor.execute(new Runnable() {
|
pluginExecutor.execute(new Runnable() {
|
||||||
public void run() {
|
public void run() {
|
||||||
if(!running) return;
|
if(!running) return;
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
BluetoothSocket s = connect(address, uuid);
|
||||||
if(conn != null)
|
if(s != null) {
|
||||||
callback.outgoingConnectionCreated(c, conn);
|
callback.outgoingConnectionCreated(c,
|
||||||
|
new DroidtoothTransportConnection(
|
||||||
|
DroidtoothPlugin.this, s));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private DuplexTransportConnection connect(String address, String uuid) {
|
private BluetoothSocket connect(String address, String uuid) {
|
||||||
// Validate the address
|
// Validate the address
|
||||||
if(!BluetoothAdapter.checkBluetoothAddress(address)) {
|
if(!BluetoothAdapter.checkBluetoothAddress(address)) {
|
||||||
if(LOG.isLoggable(WARNING))
|
if(LOG.isLoggable(WARNING))
|
||||||
@@ -302,7 +311,7 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + address);
|
if(LOG.isLoggable(INFO)) LOG.info("Connecting to " + address);
|
||||||
s.connect();
|
s.connect();
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Connected to " + address);
|
if(LOG.isLoggable(INFO)) LOG.info("Connected to " + address);
|
||||||
return new DroidtoothTransportConnection(this, s);
|
return s;
|
||||||
} catch(IOException e) {
|
} catch(IOException e) {
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -326,7 +335,9 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
if(StringUtils.isNullOrEmpty(address)) return null;
|
if(StringUtils.isNullOrEmpty(address)) return null;
|
||||||
String uuid = p.get("uuid");
|
String uuid = p.get("uuid");
|
||||||
if(StringUtils.isNullOrEmpty(uuid)) return null;
|
if(StringUtils.isNullOrEmpty(uuid)) return null;
|
||||||
return connect(address, uuid);
|
BluetoothSocket s = connect(address, uuid);
|
||||||
|
if(s == null) return null;
|
||||||
|
return new DroidtoothTransportConnection(this, s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean supportsInvitations() {
|
public boolean supportsInvitations() {
|
||||||
@@ -336,36 +347,11 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
public DuplexTransportConnection sendInvitation(PseudoRandom r,
|
||||||
long timeout) {
|
long timeout) {
|
||||||
if(!running) return null;
|
if(!running) return null;
|
||||||
// Use the same pseudo-random UUID as the contact
|
// Use the invitation codes to generate the UUID
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
|
||||||
String uuid = UUID.nameUUIDFromBytes(b).toString();
|
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Sending invitation, UUID " + uuid);
|
|
||||||
// Register to receive Bluetooth discovery intents
|
|
||||||
IntentFilter filter = new IntentFilter();
|
|
||||||
filter.addAction(FOUND);
|
|
||||||
filter.addAction(DISCOVERY_FINISHED);
|
|
||||||
// Discover nearby devices and connect to any with the right UUID
|
|
||||||
DiscoveryReceiver receiver = new DiscoveryReceiver(uuid);
|
|
||||||
appContext.registerReceiver(receiver, filter);
|
|
||||||
adapter.startDiscovery();
|
|
||||||
try {
|
|
||||||
return receiver.waitForConnection(timeout);
|
|
||||||
} catch(InterruptedException e) {
|
|
||||||
if(LOG.isLoggable(INFO))
|
|
||||||
LOG.info("Interrupted while sending invitation");
|
|
||||||
Thread.currentThread().interrupt();
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
|
||||||
long timeout) {
|
|
||||||
if(!running) return null;
|
|
||||||
// Use the same pseudo-random UUID as the contact
|
|
||||||
byte[] b = r.nextBytes(UUID_BYTES);
|
byte[] b = r.nextBytes(UUID_BYTES);
|
||||||
UUID uuid = UUID.nameUUIDFromBytes(b);
|
UUID uuid = UUID.nameUUIDFromBytes(b);
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Accepting invitation, UUID " + uuid);
|
if(LOG.isLoggable(INFO)) LOG.info("Invitation UUID " + uuid);
|
||||||
// Bind a new server socket to accept the invitation connection
|
// Bind a server socket for receiving invitation connections
|
||||||
BluetoothServerSocket ss = null;
|
BluetoothServerSocket ss = null;
|
||||||
try {
|
try {
|
||||||
ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid);
|
ss = InsecureBluetooth.listen(adapter, "RFCOMM", uuid);
|
||||||
@@ -374,23 +360,29 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Return the first connection received by the socket, if any
|
// Start the background threads
|
||||||
|
SocketReceiver receiver = new SocketReceiver();
|
||||||
|
new DiscoveryThread(receiver, uuid.toString(), timeout).start();
|
||||||
|
new BluetoothListenerThread(receiver, ss).start();
|
||||||
|
// Wait for an incoming or outgoing connection
|
||||||
try {
|
try {
|
||||||
BluetoothSocket s = ss.accept((int) timeout);
|
BluetoothSocket s = receiver.waitForSocket(timeout);
|
||||||
if(LOG.isLoggable(INFO)) {
|
if(s != null) return new DroidtoothTransportConnection(this, s);
|
||||||
String address = s.getRemoteDevice().getAddress();
|
} catch(InterruptedException e) {
|
||||||
LOG.info("Incoming connection from " + address);
|
if(LOG.isLoggable(INFO))
|
||||||
}
|
LOG.info("Interrupted while exchanging invitations");
|
||||||
return new DroidtoothTransportConnection(this, s);
|
Thread.currentThread().interrupt();
|
||||||
} catch(SocketTimeoutException e) {
|
|
||||||
if(LOG.isLoggable(INFO)) LOG.info("Invitation timed out");
|
|
||||||
return null;
|
|
||||||
} catch(IOException e) {
|
|
||||||
if(LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
|
||||||
return null;
|
|
||||||
} finally {
|
} finally {
|
||||||
|
// Closing the socket will terminate the listener thread
|
||||||
tryToClose(ss);
|
tryToClose(ss);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public DuplexTransportConnection acceptInvitation(PseudoRandom r,
|
||||||
|
long timeout) {
|
||||||
|
// FIXME
|
||||||
|
return sendInvitation(r, timeout);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class BluetoothStateReceiver extends BroadcastReceiver {
|
private static class BluetoothStateReceiver extends BroadcastReceiver {
|
||||||
@@ -403,67 +395,153 @@ class DroidtoothPlugin implements DuplexPlugin {
|
|||||||
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
int state = intent.getIntExtra(EXTRA_STATE, 0);
|
||||||
if(state == STATE_ON) {
|
if(state == STATE_ON) {
|
||||||
enabled = true;
|
enabled = true;
|
||||||
finish(ctx);
|
ctx.unregisterReceiver(this);
|
||||||
|
finished.countDown();
|
||||||
} else if(state == STATE_OFF) {
|
} else if(state == STATE_OFF) {
|
||||||
finish(ctx);
|
ctx.unregisterReceiver(this);
|
||||||
|
finished.countDown();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void finish(Context ctx) {
|
|
||||||
ctx.unregisterReceiver(this);
|
|
||||||
finished.countDown();
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean waitForStateChange() throws InterruptedException {
|
boolean waitForStateChange() throws InterruptedException {
|
||||||
finished.await();
|
finished.await();
|
||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class SocketReceiver {
|
||||||
|
|
||||||
|
private final CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
private final AtomicReference<BluetoothSocket> socket =
|
||||||
|
new AtomicReference<BluetoothSocket>();
|
||||||
|
|
||||||
|
private boolean hasSocket() {
|
||||||
|
return socket.get() != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean setSocket(BluetoothSocket s) {
|
||||||
|
if(socket.compareAndSet(null, s)) {
|
||||||
|
latch.countDown();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private BluetoothSocket waitForSocket(long timeout)
|
||||||
|
throws InterruptedException {
|
||||||
|
latch.await(timeout, TimeUnit.MILLISECONDS);
|
||||||
|
return socket.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class DiscoveryThread extends Thread {
|
||||||
|
|
||||||
|
private final SocketReceiver receiver;
|
||||||
|
private final String uuid;
|
||||||
|
private final long timeout;
|
||||||
|
|
||||||
|
private DiscoveryThread(SocketReceiver receiver, String uuid,
|
||||||
|
long timeout) {
|
||||||
|
this.receiver = receiver;
|
||||||
|
this.uuid = uuid;
|
||||||
|
this.timeout = timeout;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
long now = clock.currentTimeMillis();
|
||||||
|
long end = now + timeout;
|
||||||
|
while(now < end && running && !receiver.hasSocket()) {
|
||||||
|
// Discover nearby devices
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.info("Discovering nearby devices");
|
||||||
|
List<String> addresses;
|
||||||
|
try {
|
||||||
|
addresses = discoverDevices(end - now);
|
||||||
|
} catch(InterruptedException e) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Interrupted while discovering devices");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Connect to any device with the right UUID
|
||||||
|
for(String address : addresses) {
|
||||||
|
now = clock.currentTimeMillis();
|
||||||
|
if(now < end && running && !receiver.hasSocket()) {
|
||||||
|
BluetoothSocket s = connect(address, uuid);
|
||||||
|
if(s == null) continue;
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Outgoing connection");
|
||||||
|
if(!receiver.setSocket(s)) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing redundant connection");
|
||||||
|
tryToClose(s);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<String> discoverDevices(long timeout)
|
||||||
|
throws InterruptedException {
|
||||||
|
IntentFilter filter = new IntentFilter();
|
||||||
|
filter.addAction(FOUND);
|
||||||
|
filter.addAction(DISCOVERY_FINISHED);
|
||||||
|
DiscoveryReceiver disco = new DiscoveryReceiver();
|
||||||
|
appContext.registerReceiver(disco, filter);
|
||||||
|
adapter.startDiscovery();
|
||||||
|
return disco.waitForAddresses(timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class DiscoveryReceiver extends BroadcastReceiver {
|
private class DiscoveryReceiver extends BroadcastReceiver {
|
||||||
|
|
||||||
private final CountDownLatch finished = new CountDownLatch(1);
|
private final CountDownLatch finished = new CountDownLatch(1);
|
||||||
private final Collection<String> addresses = new ArrayList<String>();
|
private final List<String> addresses = new ArrayList<String>();
|
||||||
private final String uuid;
|
|
||||||
|
|
||||||
private volatile DuplexTransportConnection connection = null;
|
|
||||||
|
|
||||||
private DiscoveryReceiver(String uuid) {
|
|
||||||
this.uuid = uuid;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onReceive(Context ctx, Intent intent) {
|
public void onReceive(Context ctx, Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
if(action.equals(DISCOVERY_FINISHED)) {
|
if(action.equals(DISCOVERY_FINISHED)) {
|
||||||
ctx.unregisterReceiver(this);
|
ctx.unregisterReceiver(this);
|
||||||
connectToDiscoveredDevices();
|
finished.countDown();
|
||||||
} else if(action.equals(FOUND)) {
|
} else if(action.equals(FOUND)) {
|
||||||
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
BluetoothDevice d = intent.getParcelableExtra(EXTRA_DEVICE);
|
||||||
String address = d.getAddress();
|
addresses.add(d.getAddress());
|
||||||
addresses.add(address);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void connectToDiscoveredDevices() {
|
private List<String> waitForAddresses(long timeout)
|
||||||
for(final String address : addresses) {
|
|
||||||
pluginExecutor.execute(new Runnable() {
|
|
||||||
public void run() {
|
|
||||||
if(!running) return;
|
|
||||||
DuplexTransportConnection conn = connect(address, uuid);
|
|
||||||
if(conn != null) {
|
|
||||||
connection = conn;
|
|
||||||
finished.countDown();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private DuplexTransportConnection waitForConnection(long timeout)
|
|
||||||
throws InterruptedException {
|
throws InterruptedException {
|
||||||
finished.await(timeout, MILLISECONDS);
|
finished.await(timeout, MILLISECONDS);
|
||||||
return connection;
|
return Collections.unmodifiableList(addresses);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BluetoothListenerThread extends Thread {
|
||||||
|
|
||||||
|
private final SocketReceiver receiver;
|
||||||
|
private final BluetoothServerSocket serverSocket;
|
||||||
|
|
||||||
|
private BluetoothListenerThread(SocketReceiver receiver,
|
||||||
|
BluetoothServerSocket serverSocket) {
|
||||||
|
this.receiver = receiver;
|
||||||
|
this.serverSocket = serverSocket;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
BluetoothSocket s = serverSocket.accept();
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.info("Incoming connection");
|
||||||
|
if(!receiver.setSocket(s)) {
|
||||||
|
if(LOG.isLoggable(INFO))
|
||||||
|
LOG.info("Closing redundant connection");
|
||||||
|
tryToClose(s);
|
||||||
|
}
|
||||||
|
} catch(IOException e) {
|
||||||
|
// This is expected when the socket is closed
|
||||||
|
if(LOG.isLoggable(INFO)) LOG.log(INFO, e.toString(), e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import java.util.concurrent.Executor;
|
|||||||
|
|
||||||
import net.sf.briar.api.TransportId;
|
import net.sf.briar.api.TransportId;
|
||||||
import net.sf.briar.api.android.AndroidExecutor;
|
import net.sf.briar.api.android.AndroidExecutor;
|
||||||
|
import net.sf.briar.api.clock.Clock;
|
||||||
|
import net.sf.briar.api.clock.SystemClock;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
import net.sf.briar.api.plugins.duplex.DuplexPlugin;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginCallback;
|
||||||
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
import net.sf.briar.api.plugins.duplex.DuplexPluginFactory;
|
||||||
@@ -20,6 +22,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
|||||||
private final AndroidExecutor androidExecutor;
|
private final AndroidExecutor androidExecutor;
|
||||||
private final Context appContext;
|
private final Context appContext;
|
||||||
private final SecureRandom secureRandom;
|
private final SecureRandom secureRandom;
|
||||||
|
private final Clock clock;
|
||||||
|
|
||||||
public DroidtoothPluginFactory(Executor pluginExecutor,
|
public DroidtoothPluginFactory(Executor pluginExecutor,
|
||||||
AndroidExecutor androidExecutor, Context appContext,
|
AndroidExecutor androidExecutor, Context appContext,
|
||||||
@@ -28,6 +31,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
|||||||
this.androidExecutor = androidExecutor;
|
this.androidExecutor = androidExecutor;
|
||||||
this.appContext = appContext;
|
this.appContext = appContext;
|
||||||
this.secureRandom = secureRandom;
|
this.secureRandom = secureRandom;
|
||||||
|
clock = new SystemClock();
|
||||||
}
|
}
|
||||||
|
|
||||||
public TransportId getId() {
|
public TransportId getId() {
|
||||||
@@ -36,7 +40,7 @@ public class DroidtoothPluginFactory implements DuplexPluginFactory {
|
|||||||
|
|
||||||
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
public DuplexPlugin createPlugin(DuplexPluginCallback callback) {
|
||||||
return new DroidtoothPlugin(pluginExecutor, androidExecutor, appContext,
|
return new DroidtoothPlugin(pluginExecutor, androidExecutor, appContext,
|
||||||
secureRandom, callback, MAX_FRAME_LENGTH, MAX_LATENCY,
|
secureRandom, clock, callback, MAX_FRAME_LENGTH, MAX_LATENCY,
|
||||||
POLLING_INTERVAL);
|
POLLING_INTERVAL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user