Compare commits

..

1 Commits

Author SHA1 Message Date
akwizgran
d1cda4fb1e Add recently connected state to core and UI. 2020-05-05 15:39:09 +01:00
63 changed files with 520 additions and 1166 deletions

View File

@@ -9,7 +9,6 @@ import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -77,12 +76,11 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
private volatile BluetoothAdapter adapter = null;
AndroidBluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, AndroidExecutor androidExecutor,
Context appContext, Clock clock, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
super(connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
Executor ioExecutor, AndroidExecutor androidExecutor,
Context appContext, SecureRandom secureRandom, Clock clock,
Backoff backoff, PluginCallback callback, int maxLatency) {
super(connectionLimiter, ioExecutor, secureRandom, backoff, callback,
maxLatency);
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.clock = clock;
@@ -174,10 +172,9 @@ class AndroidBluetoothPlugin extends BluetoothPlugin<BluetoothServerSocket> {
return wrapSocket(ss.accept());
}
private DuplexTransportConnection wrapSocket(BluetoothSocket s)
throws IOException {
return new AndroidBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s);
private DuplexTransportConnection wrapSocket(BluetoothSocket s) {
return new AndroidBluetoothTransportConnection(this,
connectionLimiter, s);
}
@Override

View File

@@ -3,7 +3,6 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.content.Context;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -26,7 +25,6 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
@@ -37,20 +35,18 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
private final SecureRandom secureRandom;
private final EventBus eventBus;
private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
public AndroidBluetoothPluginFactory(Executor ioExecutor,
AndroidExecutor androidExecutor, Context appContext,
SecureRandom secureRandom, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.androidExecutor = androidExecutor;
this.appContext = appContext;
this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
}
@@ -67,13 +63,12 @@ public class AndroidBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(eventBus, clock);
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
AndroidBluetoothPlugin plugin = new AndroidBluetoothPlugin(
connectionLimiter, timeoutMonitor, ioExecutor, secureRandom,
androidExecutor, appContext, clock, backoff,
callback, MAX_LATENCY, MAX_IDLE_TIME);
connectionLimiter, ioExecutor, androidExecutor, appContext,
secureRandom, clock, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.plugin.bluetooth;
import android.bluetooth.BluetoothSocket;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
@@ -18,26 +17,22 @@ import static org.briarproject.bramble.util.AndroidUtils.isValidBluetoothAddress
class AndroidBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionLimiter;
private final BluetoothConnectionLimiter connectionManager;
private final BluetoothSocket socket;
private final InputStream in;
AndroidBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, BluetoothSocket socket)
throws IOException {
BluetoothConnectionLimiter connectionManager,
BluetoothSocket socket) {
super(plugin);
this.connectionLimiter = connectionLimiter;
this.connectionManager = connectionManager;
this.socket = socket;
in = timeoutMonitor.createTimeoutInputStream(
socket.getInputStream(), plugin.getMaxIdleTime() * 2);
String address = socket.getRemoteDevice().getAddress();
if (isValidBluetoothAddress(address)) remote.put(PROP_ADDRESS, address);
}
@Override
protected InputStream getInputStream() {
return in;
protected InputStream getInputStream() throws IOException {
return socket.getInputStream();
}
@Override
@@ -50,7 +45,7 @@ class AndroidBluetoothTransportConnection
try {
socket.close();
} finally {
connectionLimiter.connectionClosed(this, exception);
connectionManager.connectionClosed(this);
}
}
}

View File

@@ -1,15 +0,0 @@
package org.briarproject.bramble.api.io;
import java.io.InputStream;
public interface TimeoutMonitor {
/**
* Returns an {@link InputStream} that wraps the given stream and allows
* read timeouts to be detected.
*
* @param timeoutMs The read timeout in milliseconds. Timeouts will be
* detected eventually but are not guaranteed to be detected immediately.
*/
InputStream createTimeoutInputStream(InputStream in, long timeoutMs);
}

View File

@@ -5,8 +5,7 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
@@ -21,15 +20,15 @@ public interface ConnectionRegistry {
/**
* Registers a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionOpenedEvent}. Also broadcasts
* {@link ContactConnectedEvent} if this is the only connection with the
* contact.
* {@link ConnectionStatusChangedEvent} if this is the only connection with
* the contact.
*/
void registerConnection(ContactId c, TransportId t, boolean incoming);
/**
* Unregisters a connection with the given contact over the given transport.
* Broadcasts {@link ConnectionClosedEvent}. Also broadcasts
* {@link ContactDisconnectedEvent} if this is the only connection with
* {@link ConnectionStatusChangedEvent} if this is the only connection with
* the contact.
*/
void unregisterConnection(ContactId c, TransportId t, boolean incoming);
@@ -45,9 +44,9 @@ public interface ConnectionRegistry {
boolean isConnected(ContactId c, TransportId t);
/**
* Returns true if the given contact is connected via any transport.
* Returns the connection status of the given contact via all transports.
*/
boolean isConnected(ContactId c);
ConnectionStatus getConnectionStatus(ContactId c);
/**
* Registers a connection with the given pending contact. Broadcasts

View File

@@ -0,0 +1,5 @@
package org.briarproject.bramble.api.plugin;
public enum ConnectionStatus {
CONNECTED, RECENTLY_CONNECTED, DISCONNECTED
}

View File

@@ -3,24 +3,31 @@ package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a contact connects that was not previously
* connected via any transport.
* An event that is broadcast when a contact's connection status changes.
*/
@Immutable
@NotNullByDefault
public class ContactConnectedEvent extends Event {
public class ConnectionStatusChangedEvent extends Event {
private final ContactId contactId;
private final ConnectionStatus status;
public ContactConnectedEvent(ContactId contactId) {
public ConnectionStatusChangedEvent(ContactId contactId,
ConnectionStatus status) {
this.contactId = contactId;
this.status = status;
}
public ContactId getContactId() {
return contactId;
}
public ConnectionStatus getConnectionStatus() {
return status;
}
}

View File

@@ -1,26 +0,0 @@
package org.briarproject.bramble.api.plugin.event;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when a contact disconnects and is no longer
* connected via any transport.
*/
@Immutable
@NotNullByDefault
public class ContactDisconnectedEvent extends Event {
private final ContactId contactId;
public ContactDisconnectedEvent(ContactId contactId) {
this.contactId = contactId;
}
public ContactId getContactId() {
return contactId;
}
}

View File

@@ -2,7 +2,6 @@ package org.briarproject.bramble.api.sync;
import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.InputStream;
@@ -12,9 +11,9 @@ public interface SyncSessionFactory {
SyncSession createIncomingSession(ContactId c, InputStream in);
SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, StreamWriter streamWriter);
SyncSession createSimplexOutgoingSession(ContactId c, int maxLatency,
StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, TransportId t,
int maxLatency, int maxIdleTime, StreamWriter streamWriter);
SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter);
}

View File

@@ -1,26 +0,0 @@
package org.briarproject.bramble.api.sync.event;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import javax.annotation.concurrent.Immutable;
/**
* An event that is broadcast when all sync connections using a given
* transport should be closed.
*/
@Immutable
@NotNullByDefault
public class CloseSyncConnectionsEvent extends Event {
private final TransportId transportId;
public CloseSyncConnectionsEvent(TransportId transportId) {
this.transportId = transportId;
}
public TransportId getTransportId() {
return transportId;
}
}

View File

@@ -9,7 +9,6 @@ import org.briarproject.bramble.db.DatabaseExecutorModule;
import org.briarproject.bramble.db.DatabaseModule;
import org.briarproject.bramble.event.EventModule;
import org.briarproject.bramble.identity.IdentityModule;
import org.briarproject.bramble.io.IoModule;
import org.briarproject.bramble.keyagreement.KeyAgreementModule;
import org.briarproject.bramble.lifecycle.LifecycleModule;
import org.briarproject.bramble.plugin.PluginModule;
@@ -36,7 +35,6 @@ import dagger.Module;
DatabaseExecutorModule.class,
EventModule.class,
IdentityModule.class,
IoModule.class,
KeyAgreementModule.class,
LifecycleModule.class,
PluginModule.class,

View File

@@ -1,18 +0,0 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class IoModule {
@Provides
@Singleton
TimeoutMonitor provideTimeoutMonitor(TimeoutMonitorImpl timeoutMonitor) {
return timeoutMonitor;
}
}

View File

@@ -1,104 +0,0 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.system.Clock;
import java.io.IOException;
import java.io.InputStream;
import javax.annotation.concurrent.GuardedBy;
@NotNullByDefault
class TimeoutInputStream extends InputStream {
private final Clock clock;
private final InputStream in;
private final long timeoutMs;
private final CloseListener listener;
private final Object lock = new Object();
@GuardedBy("lock")
private long readStartedMs = -1;
TimeoutInputStream(Clock clock, InputStream in, long timeoutMs,
CloseListener listener) {
this.clock = clock;
this.in = in;
this.timeoutMs = timeoutMs;
this.listener = listener;
}
@Override
public int read() throws IOException {
synchronized (lock) {
readStartedMs = clock.currentTimeMillis();
}
int input = in.read();
synchronized (lock) {
readStartedMs = -1;
}
return input;
}
@Override
public int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
@Override
public int read(byte[] b, int off, int len) throws IOException {
synchronized (lock) {
readStartedMs = clock.currentTimeMillis();
}
int read = in.read(b, off, len);
synchronized (lock) {
readStartedMs = -1;
}
return read;
}
@Override
public void close() throws IOException {
try {
in.close();
} finally {
listener.onClose(this);
}
}
@Override
public int available() throws IOException {
return in.available();
}
@Override
public void mark(int readlimit) {
in.mark(readlimit);
}
@Override
public boolean markSupported() {
return in.markSupported();
}
@Override
public void reset() throws IOException {
in.reset();
}
@Override
public long skip(long n) throws IOException {
return in.skip(n);
}
boolean hasTimedOut() {
synchronized (lock) {
return readStartedMs != -1 &&
clock.currentTimeMillis() - readStartedMs > timeoutMs;
}
}
interface CloseListener {
void onClose(TimeoutInputStream closed);
}
}

View File

@@ -1,96 +0,0 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.inject.Inject;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.util.LogUtils.logException;
class TimeoutMonitorImpl implements TimeoutMonitor {
private static final Logger LOG =
getLogger(TimeoutMonitorImpl.class.getName());
private static final long CHECK_INTERVAL_MS = SECONDS.toMillis(10);
private final ScheduledExecutorService scheduler;
private final Executor ioExecutor;
private final Clock clock;
private final Object lock = new Object();
@GuardedBy("lock")
private final List<TimeoutInputStream> streams = new ArrayList<>();
@GuardedBy("lock")
private Future<?> task = null;
@Inject
TimeoutMonitorImpl(@Scheduler ScheduledExecutorService scheduler,
@IoExecutor Executor ioExecutor, Clock clock) {
this.scheduler = scheduler;
this.ioExecutor = ioExecutor;
this.clock = clock;
}
@Override
public InputStream createTimeoutInputStream(InputStream in,
long timeoutMs) {
TimeoutInputStream stream = new TimeoutInputStream(clock, in,
timeoutMs, this::removeStream);
synchronized (lock) {
if (streams.isEmpty()) {
task = scheduler.scheduleWithFixedDelay(this::checkTimeouts,
CHECK_INTERVAL_MS, CHECK_INTERVAL_MS, MILLISECONDS);
}
streams.add(stream);
}
return stream;
}
private void removeStream(TimeoutInputStream stream) {
Future<?> toCancel = null;
synchronized (lock) {
if (streams.remove(stream) && streams.isEmpty()) {
toCancel = task;
task = null;
}
}
if (toCancel != null) toCancel.cancel(false);
}
@Scheduler
private void checkTimeouts() {
ioExecutor.execute(() -> {
List<TimeoutInputStream> snapshot;
synchronized (lock) {
snapshot = new ArrayList<>(streams);
}
for (TimeoutInputStream stream : snapshot) {
if (stream.hasTimedOut()) {
LOG.info("Input stream has timed out");
try {
stream.close();
} catch (IOException e) {
logException(LOG, INFO, e);
}
}
}
});
}
}

View File

@@ -130,8 +130,8 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
return syncSessionFactory.createSimplexOutgoingSession(
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createSimplexOutgoingSession(c,
w.getMaxLatency(), streamWriter);
}
@@ -139,8 +139,8 @@ class ConnectionManagerImpl implements ConnectionManager {
TransportConnectionWriter w) throws IOException {
StreamWriter streamWriter = streamWriterFactory.createStreamWriter(
w.getOutputStream(), ctx);
return syncSessionFactory.createDuplexOutgoingSession(
requireNonNull(ctx.getContactId()), ctx.getTransportId(),
ContactId c = requireNonNull(ctx.getContactId());
return syncSessionFactory.createDuplexOutgoingSession(c,
w.getMaxLatency(), w.getMaxIdleTime(), streamWriter);
}

View File

@@ -6,30 +6,41 @@ import org.briarproject.bramble.api.contact.PendingContactId;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.api.system.Scheduler;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.ScheduledExecutorService;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.util.Collections.emptyList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.DISCONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
@ThreadSafe
@NotNullByDefault
@@ -38,22 +49,30 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
private static final Logger LOG =
getLogger(ConnectionRegistryImpl.class.getName());
private static final long RECENTLY_CONNECTED_MS = MINUTES.toMillis(1);
private static final long EXPIRY_INTERVAL_MS = SECONDS.toMillis(10);
private final EventBus eventBus;
private final Clock clock;
private final Object lock = new Object();
@GuardedBy("lock")
private final Map<TransportId, Multiset<ContactId>> contactConnections;
@GuardedBy("lock")
private final Multiset<ContactId> contactCounts;
private final Map<ContactId, Counter> contactCounts;
@GuardedBy("lock")
private final Set<PendingContactId> connectedPendingContacts;
@Inject
ConnectionRegistryImpl(EventBus eventBus) {
ConnectionRegistryImpl(EventBus eventBus, Clock clock,
@Scheduler ScheduledExecutorService scheduler) {
this.eventBus = eventBus;
this.clock = clock;
contactConnections = new HashMap<>();
contactCounts = new Multiset<>();
contactCounts = new HashMap<>();
connectedPendingContacts = new HashSet<>();
scheduler.scheduleWithFixedDelay(this::expireRecentConnections,
EXPIRY_INTERVAL_MS, EXPIRY_INTERVAL_MS, MILLISECONDS);
}
@Override
@@ -71,12 +90,22 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
contactConnections.put(t, m);
}
m.add(c);
if (contactCounts.add(c) == 1) firstConnection = true;
Counter counter = contactCounts.get(c);
if (counter == null) {
counter = new Counter();
contactCounts.put(c, counter);
}
if (counter.connections == 0) {
counter.disconnectedTime = 0;
firstConnection = true;
}
counter.connections++;
}
eventBus.broadcast(new ConnectionOpenedEvent(c, t, incoming));
if (firstConnection) {
LOG.info("Contact connected");
eventBus.broadcast(new ContactConnectedEvent(c));
eventBus.broadcast(new ConnectionStatusChangedEvent(c, CONNECTED));
}
}
@@ -93,12 +122,22 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
if (m == null || !m.contains(c))
throw new IllegalArgumentException();
m.remove(c);
if (contactCounts.remove(c) == 0) lastConnection = true;
Counter counter = contactCounts.get(c);
if (counter == null || counter.connections == 0) {
throw new IllegalArgumentException();
}
counter.connections--;
if (counter.connections == 0) {
counter.disconnectedTime = clock.currentTimeMillis();
lastConnection = true;
}
}
eventBus.broadcast(new ConnectionClosedEvent(c, t, incoming));
if (lastConnection) {
LOG.info("Contact disconnected");
eventBus.broadcast(new ContactDisconnectedEvent(c));
eventBus.broadcast(
new ConnectionStatusChangedEvent(c, RECENTLY_CONNECTED));
}
}
@@ -106,7 +145,7 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
public Collection<ContactId> getConnectedContacts(TransportId t) {
synchronized (lock) {
Multiset<ContactId> m = contactConnections.get(t);
if (m == null) return Collections.emptyList();
if (m == null) return emptyList();
List<ContactId> ids = new ArrayList<>(m.keySet());
if (LOG.isLoggable(INFO))
LOG.info(ids.size() + " contacts connected: " + t);
@@ -123,9 +162,11 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
}
@Override
public boolean isConnected(ContactId c) {
public ConnectionStatus getConnectionStatus(ContactId c) {
synchronized (lock) {
return contactCounts.contains(c);
Counter counter = contactCounts.get(c);
if (counter == null) return DISCONNECTED;
return counter.connections > 0 ? CONNECTED : RECENTLY_CONNECTED;
}
}
@@ -147,4 +188,36 @@ class ConnectionRegistryImpl implements ConnectionRegistry {
}
eventBus.broadcast(new RendezvousConnectionClosedEvent(p, success));
}
@Scheduler
private void expireRecentConnections() {
long now = clock.currentTimeMillis();
List<ContactId> disconnected = new ArrayList<>();
synchronized (lock) {
Iterator<Entry<ContactId, Counter>> it =
contactCounts.entrySet().iterator();
while (it.hasNext()) {
Entry<ContactId, Counter> e = it.next();
if (e.getValue().isExpired(now)) {
disconnected.add(e.getKey());
it.remove();
}
}
}
for (ContactId c : disconnected) {
eventBus.broadcast(
new ConnectionStatusChangedEvent(c, DISCONNECTED));
}
}
private static class Counter {
private int connections = 0;
private long disconnectedTime = 0;
private boolean isExpired(long now) {
return connections == 0 &&
now - disconnectedTime > RECENTLY_CONNECTED_MS;
}
}
}

View File

@@ -3,30 +3,9 @@ package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import static java.util.concurrent.TimeUnit.DAYS;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
@NotNullByDefault
interface BluetoothConnectionLimiter {
/**
* How long a connection must remain open before it's considered stable.
*/
long STABILITY_PERIOD_MS = SECONDS.toMillis(90);
/**
* The minimum interval between attempts to raise the connection limit.
* This is longer than {@link #STABILITY_PERIOD_MS} so we don't start
* another attempt before knowing the outcome of the last one.
*/
long MIN_ATTEMPT_INTERVAL_MS = MINUTES.toMillis(2);
/**
* The maximum interval between attempts to raise the connection limit.
*/
long MAX_ATTEMPT_INTERVAL_MS = DAYS.toMillis(2);
/**
* Informs the limiter that key agreement has started.
*/
@@ -44,12 +23,12 @@ interface BluetoothConnectionLimiter {
boolean canOpenContactConnection();
/**
* Informs the limiter that a contact connection has been opened.
* Informs the limiter that a contact connection has been opened. The
* limiter may close the new connection if key agreement is in progress.
* <p/>
* Returns true if the connection is allowed.
* Returns false if the limiter has closed the new connection.
*/
boolean contactConnectionOpened(DuplexTransportConnection conn,
boolean incoming);
boolean contactConnectionOpened(DuplexTransportConnection conn);
/**
* Informs the limiter that a key agreement connection has been opened.
@@ -58,13 +37,11 @@ interface BluetoothConnectionLimiter {
/**
* Informs the limiter that the given connection has been closed.
*
* @param exception True if the connection was closed due to an exception.
*/
void connectionClosed(DuplexTransportConnection conn, boolean exception);
void connectionClosed(DuplexTransportConnection conn);
/**
* Informs the limiter that the Bluetooth adapter has been disabled.
* Informs the limiter that all connections have been closed.
*/
void bluetoothDisabled();
void allConnectionsClosed();
}

View File

@@ -1,59 +1,46 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.system.Clock;
import java.util.Iterator;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.logging.Logger;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.ThreadSafe;
import javax.inject.Inject;
import static java.lang.Math.min;
import static java.util.logging.Level.INFO;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.util.LogUtils.logException;
@NotNullByDefault
@ThreadSafe
class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
private static final Logger LOG =
getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final EventBus eventBus;
private final Clock clock;
Logger.getLogger(BluetoothConnectionLimiterImpl.class.getName());
private final Object lock = new Object();
@GuardedBy("lock")
private final List<ConnectionRecord> connections = new LinkedList<>();
@GuardedBy("lock")
// The following are locking: lock
private final LinkedList<DuplexTransportConnection> connections =
new LinkedList<>();
private boolean keyAgreementInProgress = false;
@GuardedBy("lock")
private int connectionLimit = 1;
@GuardedBy("lock")
private long timeOfLastAttempt = 0,
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
@Inject
BluetoothConnectionLimiterImpl(EventBus eventBus, Clock clock) {
this.eventBus = eventBus;
this.clock = clock;
}
@Override
public void keyAgreementStarted() {
List<DuplexTransportConnection> close;
synchronized (lock) {
keyAgreementInProgress = true;
close = new ArrayList<>(connections);
connections.clear();
}
LOG.info("Key agreement started");
eventBus.broadcast(new CloseSyncConnectionsEvent(ID));
if (LOG.isLoggable(INFO)) {
LOG.info("Key agreement started, closing " + close.size() +
" connections");
}
for (DuplexTransportConnection conn : close) tryToClose(conn);
}
@Override
@@ -68,128 +55,62 @@ class BluetoothConnectionLimiterImpl implements BluetoothConnectionLimiter {
public boolean canOpenContactConnection() {
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement");
LOG.info("Can't open contact connection during key agreement");
return false;
} else {
long now = clock.currentTimeMillis();
return isContactConnectionAllowedByLimit(now);
LOG.info("Can open contact connection");
return true;
}
}
}
@Override
public boolean contactConnectionOpened(DuplexTransportConnection conn,
boolean incoming) {
public boolean contactConnectionOpened(DuplexTransportConnection conn) {
boolean accept = true;
synchronized (lock) {
if (keyAgreementInProgress) {
LOG.info("Refusing contact connection during key agreement");
return false;
accept = false;
} else {
long now = clock.currentTimeMillis();
if (incoming || isContactConnectionAllowedByLimit(now)) {
connections.add(new ConnectionRecord(conn, now));
if (!incoming && connections.size() > connectionLimit) {
LOG.info("Attempting to raise connection limit");
timeOfLastAttempt = now;
}
return true;
} else {
return false;
}
LOG.info("Accepting contact connection");
connections.add(conn);
}
}
if (!accept) tryToClose(conn);
return accept;
}
@Override
public void keyAgreementConnectionOpened(DuplexTransportConnection conn) {
synchronized (lock) {
LOG.info("Accepting key agreement connection");
connections.add(
new ConnectionRecord(conn, clock.currentTimeMillis()));
connections.add(conn);
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void connectionClosed(DuplexTransportConnection conn,
boolean exception) {
public void connectionClosed(DuplexTransportConnection conn) {
synchronized (lock) {
Iterator<ConnectionRecord> it = connections.iterator();
while (it.hasNext()) {
if (it.next().connection == conn) {
long now = clock.currentTimeMillis();
if (exception) connectionFailed(now);
else considerRaisingConnectionLimit(now);
it.remove();
break;
}
}
connections.remove(conn);
if (LOG.isLoggable(INFO))
LOG.info("Connection closed, " + connections.size() + " open");
}
}
@Override
public void bluetoothDisabled() {
public void allConnectionsClosed() {
synchronized (lock) {
LOG.info("Bluetooth disabled");
considerRaisingConnectionLimit(clock.currentTimeMillis());
connections.clear();
}
}
@GuardedBy("lock")
private boolean isContactConnectionAllowedByLimit(long now) {
considerRaisingConnectionLimit(now);
if (connections.size() > connectionLimit) {
LOG.info("Refusing contact connection, above limit");
return false;
} else if (connections.size() < connectionLimit) {
LOG.info("Allowing contact connection, below limit");
return true;
} else if (now - timeOfLastAttempt >= attemptInterval) {
LOG.info("Allowing contact connection, at limit");
return true;
} else {
LOG.info("Refusing contact connection, at limit");
return false;
}
}
@GuardedBy("lock")
private void considerRaisingConnectionLimit(long now) {
int stable = 0;
for (ConnectionRecord rec : connections) {
if (now - rec.timeOpened >= STABILITY_PERIOD_MS) stable++;
}
if (stable > connectionLimit) {
LOG.info("Raising connection limit");
connectionLimit = stable;
attemptInterval = MIN_ATTEMPT_INTERVAL_MS;
}
if (LOG.isLoggable(INFO)) {
LOG.info(stable + " connections are stable, limit is "
+ connectionLimit);
}
}
@GuardedBy("lock")
private void connectionFailed(long now) {
if (connections.size() > connectionLimit &&
now - timeOfLastAttempt < STABILITY_PERIOD_MS) {
LOG.info("Connection failed above limit, increasing interval");
attemptInterval = min(attemptInterval * 2, MAX_ATTEMPT_INTERVAL_MS);
}
}
private static final class ConnectionRecord {
private final DuplexTransportConnection connection;
private final long timeOpened;
private ConnectionRecord(DuplexTransportConnection connection,
long timeOpened) {
this.connection = connection;
this.timeOpened = timeOpened;
LOG.info("All connections closed");
}
}
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.Pair;
import org.briarproject.bramble.api.data.BdfList;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
import org.briarproject.bramble.api.keyagreement.KeyAgreementListener;
import org.briarproject.bramble.api.keyagreement.event.KeyAgreementListeningEvent;
@@ -61,13 +60,12 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
getLogger(BluetoothPlugin.class.getName());
final BluetoothConnectionLimiter connectionLimiter;
final TimeoutMonitor timeoutMonitor;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final Backoff backoff;
private final PluginCallback callback;
private final int maxLatency, maxIdleTime;
private final int maxLatency;
private final AtomicBoolean used = new AtomicBoolean(false);
private volatile boolean running = false, contactConnections = false;
@@ -107,17 +105,14 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
abstract DuplexTransportConnection discoverAndConnect(String uuid);
BluetoothPlugin(BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, PluginCallback callback, int maxLatency) {
this.connectionLimiter = connectionLimiter;
this.timeoutMonitor = timeoutMonitor;
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.backoff = backoff;
this.callback = callback;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
}
void onAdapterEnabled() {
@@ -130,7 +125,7 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
void onAdapterDisabled() {
LOG.info("Bluetooth disabled");
tryToClose(socket);
connectionLimiter.bluetoothDisabled();
connectionLimiter.allConnectionsClosed();
callback.transportDisabled();
}
@@ -146,7 +141,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
@Override
public int getMaxIdleTime() {
return maxIdleTime;
// Bluetooth detects dead connections so we don't need keepalives
return Integer.MAX_VALUE;
}
@Override
@@ -231,26 +227,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (LOG.isLoggable(INFO)) LOG.info(e.toString());
return;
}
LOG.info("Connection received");
if (connectionLimiter.contactConnectionOpened(conn, true)) {
backoff.reset();
backoff.reset();
if (connectionLimiter.contactConnectionOpened(conn))
callback.handleConnection(conn);
} else {
tryToClose(conn);
}
if (!running) return;
}
}
private void tryToClose(DuplexTransportConnection conn) {
try {
conn.getWriter().dispose(false);
conn.getReader().dispose(false, false);
} catch (IOException e) {
logException(LOG, WARNING, e);
}
}
@Override
public void stop() {
running = false;
@@ -290,10 +273,13 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
String uuid = p.get(PROP_UUID);
if (isNullOrEmpty(uuid)) return;
ioExecutor.execute(() -> {
if (!isRunning() || !shouldAllowContactConnections()) return;
if (!connectionLimiter.canOpenContactConnection()) return;
DuplexTransportConnection d = createConnection(p);
if (d != null) {
backoff.reset();
h.handleConnection(d);
if (connectionLimiter.contactConnectionOpened(d))
h.handleConnection(d);
}
});
}
@@ -339,12 +325,8 @@ abstract class BluetoothPlugin<SS> implements DuplexPlugin, EventListener {
if (isNullOrEmpty(uuid)) return null;
DuplexTransportConnection conn = connect(address, uuid);
if (conn == null) return null;
if (connectionLimiter.contactConnectionOpened(conn, false)) {
return conn;
} else {
tryToClose(conn);
return null;
}
// TODO: Why don't we reset the backoff here?
return connectionLimiter.contactConnectionOpened(conn) ? conn : null;
}
@Override

View File

@@ -11,7 +11,6 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.Offer;
@@ -19,7 +18,6 @@ import org.briarproject.bramble.api.sync.Request;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
import org.briarproject.bramble.api.sync.event.MessageRequestedEvent;
import org.briarproject.bramble.api.sync.event.MessageSharedEvent;
@@ -73,7 +71,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private final EventBus eventBus;
private final Clock clock;
private final ContactId contactId;
private final TransportId transportId;
private final int maxLatency, maxIdleTime;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter;
@@ -89,15 +86,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private volatile boolean interrupted = false;
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, Clock clock, ContactId contactId,
TransportId transportId, int maxLatency, int maxIdleTime,
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
int maxIdleTime, StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.clock = clock;
this.contactId = contactId;
this.transportId = transportId;
this.maxLatency = maxLatency;
this.maxIdleTime = maxIdleTime;
this.streamWriter = streamWriter;
@@ -227,9 +223,6 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt();
}
}

View File

@@ -11,13 +11,11 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.event.LifecycleEvent;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.Message;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
import org.briarproject.bramble.api.sync.SyncSession;
import org.briarproject.bramble.api.sync.Versions;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.transport.StreamWriter;
import java.io.IOException;
@@ -58,7 +56,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private final Executor dbExecutor;
private final EventBus eventBus;
private final ContactId contactId;
private final TransportId transportId;
private final int maxLatency;
private final StreamWriter streamWriter;
private final SyncRecordWriter recordWriter;
@@ -68,14 +65,12 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private volatile boolean interrupted = false;
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
EventBus eventBus, ContactId contactId, TransportId transportId,
int maxLatency, StreamWriter streamWriter,
SyncRecordWriter recordWriter) {
EventBus eventBus, ContactId contactId, int maxLatency,
StreamWriter streamWriter, SyncRecordWriter recordWriter) {
this.db = db;
this.dbExecutor = dbExecutor;
this.eventBus = eventBus;
this.contactId = contactId;
this.transportId = transportId;
this.maxLatency = maxLatency;
this.streamWriter = streamWriter;
this.recordWriter = recordWriter;
@@ -128,9 +123,6 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
} else if (e instanceof LifecycleEvent) {
LifecycleEvent l = (LifecycleEvent) e;
if (l.getLifecycleState() == STOPPING) interrupt();
} else if (e instanceof CloseSyncConnectionsEvent) {
CloseSyncConnectionsEvent c = (CloseSyncConnectionsEvent) e;
if (c.getTransportId().equals(transportId)) interrupt();
}
}

View File

@@ -5,7 +5,6 @@ import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.SyncRecordReader;
import org.briarproject.bramble.api.sync.SyncRecordReaderFactory;
import org.briarproject.bramble.api.sync.SyncRecordWriter;
@@ -54,23 +53,22 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
}
@Override
public SyncSession createSimplexOutgoingSession(ContactId c, TransportId t,
public SyncSession createSimplexOutgoingSession(ContactId c,
int maxLatency, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c, t,
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
maxLatency, streamWriter, recordWriter);
}
@Override
public SyncSession createDuplexOutgoingSession(ContactId c,
TransportId t, int maxLatency, int maxIdleTime,
StreamWriter streamWriter) {
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
int maxIdleTime, StreamWriter streamWriter) {
OutputStream out = streamWriter.getOutputStream();
SyncRecordWriter recordWriter =
recordWriterFactory.createRecordWriter(out);
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c, t,
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
maxLatency, maxIdleTime, streamWriter, recordWriter);
}
}

View File

@@ -1,143 +0,0 @@
package org.briarproject.bramble.io;
import org.briarproject.bramble.test.BrambleTestCase;
import org.briarproject.bramble.test.SettableClock;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import static java.util.concurrent.TimeUnit.MINUTES;
import static java.util.concurrent.TimeUnit.SECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
public class TimeoutInputStreamTest extends BrambleTestCase {
private static final long TIMEOUT_MS = MINUTES.toMillis(1);
private final long now = System.currentTimeMillis();
private AtomicLong time;
private UnresponsiveInputStream in;
private AtomicBoolean listenerCalled;
private TimeoutInputStream stream;
private CountDownLatch readReturned;
@Before
public void setUp() {
time = new AtomicLong(now);
in = new UnresponsiveInputStream();
listenerCalled = new AtomicBoolean(false);
stream = new TimeoutInputStream(new SettableClock(time), in,
TIMEOUT_MS, stream -> listenerCalled.set(true));
readReturned = new CountDownLatch(1);
}
@Test
public void testTimeoutIsReportedIfReadDoesNotReturn() throws Exception {
startReading();
try {
// The stream should not report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS);
// The stream still shouldn't report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS + 1);
// The stream should report a timeout
assertTrue(stream.hasTimedOut());
// The listener should not have been called yet
assertFalse(listenerCalled.get());
// Close the stream
stream.close();
// The listener should have been called
assertTrue(listenerCalled.get());
} finally {
// Allow the read to return
in.readFinished.countDown();
}
}
@Test
public void testTimeoutIsNotReportedIfReadReturns() throws Exception {
startReading();
try {
// The stream should not report a timeout
assertFalse(stream.hasTimedOut());
// Time passes
time.set(now + TIMEOUT_MS);
// The stream still shouldn't report a timeout
assertFalse(stream.hasTimedOut());
// Allow the read to finish and wait for it to return
in.readFinished.countDown();
readReturned.await(10, SECONDS);
// Time passes
time.set(now + TIMEOUT_MS + 1);
// The stream should not report a timeout as the read has returned
assertFalse(stream.hasTimedOut());
// The listener should not have been called yet
assertFalse(listenerCalled.get());
// Close the stream
stream.close();
// The listener should have been called
assertTrue(listenerCalled.get());
} finally {
// Allow the read to return in case an assertion was thrown
in.readFinished.countDown();
}
}
private void startReading() throws Exception {
// Start a background thread to read from the unresponsive stream
new Thread(() -> {
try {
assertEquals(123, stream.read());
readReturned.countDown();
} catch (IOException e) {
fail();
}
}).start();
// Wait for the background thread to start reading
assertTrue(in.readStarted.await(10, SECONDS));
}
private class UnresponsiveInputStream extends InputStream {
private final CountDownLatch readStarted = new CountDownLatch(1);
private final CountDownLatch readFinished = new CountDownLatch(1);
@Override
public int read() throws IOException {
readStarted.countDown();
try {
readFinished.await();
return 123;
} catch (InterruptedException e) {
throw new IOException(e);
}
}
}
}

View File

@@ -7,18 +7,20 @@ import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.event.ConnectionClosedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionOpenedEvent;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionClosedEvent;
import org.briarproject.bramble.api.rendezvous.event.RendezvousConnectionOpenedEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.jmock.Expectations;
import org.junit.Test;
import java.util.Collection;
import java.util.concurrent.ScheduledExecutorService;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
@@ -30,6 +32,9 @@ import static org.junit.Assert.fail;
public class ConnectionRegistryImplTest extends BrambleMockTestCase {
private final EventBus eventBus = context.mock(EventBus.class);
private final Clock clock = context.mock(Clock.class);
private final ScheduledExecutorService scheduler =
context.mock(ScheduledExecutorService.class);
private final ContactId contactId = getContactId();
private final ContactId contactId1 = getContactId();
@@ -40,17 +45,25 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
@Test
public void testRegisterAndUnregister() {
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus);
context.checking(new Expectations() {{
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(10_000L), with(10_000L), with(MILLISECONDS));
}});
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus, clock,
scheduler);
context.assertIsSatisfied();
// The registry should be empty
assertEquals(emptyList(), c.getConnectedContacts(transportId));
assertEquals(emptyList(), c.getConnectedContacts(transportId1));
// Check that a registered connection shows up - this should
// broadcast a ConnectionOpenedEvent and a ContactConnectedEvent
// broadcast a ConnectionOpenedEvent and a ConnectionStatusChangedEvent
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(ConnectionOpenedEvent.class)));
oneOf(eventBus).broadcast(with(any(ContactConnectedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ConnectionStatusChangedEvent.class)));
}});
c.registerConnection(contactId, transportId, true);
assertEquals(singletonList(contactId),
@@ -81,11 +94,13 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
context.assertIsSatisfied();
// Unregister the other connection - this should broadcast a
// ConnectionClosedEvent and a ContactDisconnectedEvent
// ConnectionClosedEvent and a ConnectionStatusChangedEvent
context.checking(new Expectations() {{
oneOf(clock).currentTimeMillis();
will(returnValue(System.currentTimeMillis()));
oneOf(eventBus).broadcast(with(any(ConnectionClosedEvent.class)));
oneOf(eventBus).broadcast(with(any(
ContactDisconnectedEvent.class)));
ConnectionStatusChangedEvent.class)));
}});
c.unregisterConnection(contactId, transportId, true);
assertEquals(emptyList(), c.getConnectedContacts(transportId));
@@ -102,12 +117,12 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
// Register both contacts with one transport, one contact with both -
// this should broadcast three ConnectionOpenedEvents and two
// ContactConnectedEvents
// ConnectionStatusChangedEvents
context.checking(new Expectations() {{
exactly(3).of(eventBus).broadcast(with(any(
ConnectionOpenedEvent.class)));
exactly(2).of(eventBus).broadcast(with(any(
ContactConnectedEvent.class)));
ConnectionStatusChangedEvent.class)));
}});
c.registerConnection(contactId, transportId, true);
c.registerConnection(contactId1, transportId, true);
@@ -122,7 +137,14 @@ public class ConnectionRegistryImplTest extends BrambleMockTestCase {
@Test
public void testRegisterAndUnregisterPendingContacts() {
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus);
context.checking(new Expectations() {{
oneOf(scheduler).scheduleWithFixedDelay(with(any(Runnable.class)),
with(10_000L), with(10_000L), with(MILLISECONDS));
}});
ConnectionRegistry c = new ConnectionRegistryImpl(eventBus, clock,
scheduler);
context.assertIsSatisfied();
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(

View File

@@ -1,182 +0,0 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
import org.briarproject.bramble.api.sync.event.CloseSyncConnectionsEvent;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.test.BrambleMockTestCase;
import org.briarproject.bramble.test.SettableClock;
import org.jmock.Expectations;
import org.junit.Before;
import org.junit.Test;
import java.util.concurrent.atomic.AtomicLong;
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.MIN_ATTEMPT_INTERVAL_MS;
import static org.briarproject.bramble.plugin.bluetooth.BluetoothConnectionLimiter.STABILITY_PERIOD_MS;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class BluetoothConnectionLimiterImplTest extends BrambleMockTestCase {
private final EventBus eventBus = context.mock(EventBus.class);
private final DuplexTransportConnection conn1 =
context.mock(DuplexTransportConnection.class, "conn1");
private final DuplexTransportConnection conn2 =
context.mock(DuplexTransportConnection.class, "conn2");
private final DuplexTransportConnection conn3 =
context.mock(DuplexTransportConnection.class, "conn3");
private final long now = System.currentTimeMillis();
private AtomicLong time;
private BluetoothConnectionLimiter limiter;
@Before
public void setUp() {
time = new AtomicLong(now);
Clock clock = new SettableClock(time);
limiter = new BluetoothConnectionLimiterImpl(eventBus, clock);
}
@Test
public void testLimiterDoesNotAllowContactConnectionsDuringKeyAgreement() {
assertTrue(limiter.canOpenContactConnection());
expectCloseSyncConnectionsEvent();
limiter.keyAgreementStarted();
assertFalse(limiter.canOpenContactConnection());
limiter.keyAgreementEnded();
assertTrue(limiter.canOpenContactConnection());
}
@Test
public void testLimiterAllowsAttemptToRaiseLimitAtStartup() {
// First outgoing connection is allowed - we're below the limit of 1
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn1, false));
// Second outgoing connection is allowed - it's time to try raising
// the limit to 2
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn2, false));
// Third outgoing connection is not allowed - we're above the limit of 1
assertFalse(limiter.canOpenContactConnection());
}
@Test
public void testLimiterAllowsThirdConnectionAfterFirstTwoAreClosed() {
// First outgoing connection is allowed - we're below the limit of 1
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn1, false));
// Second outgoing connection is allowed - it's time to try raising
// the limit to 2
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn2, false));
// Third outgoing connection is not allowed - we're above the limit of 1
assertFalse(limiter.canOpenContactConnection());
// Close the first connection
limiter.connectionClosed(conn1, false);
// Third outgoing connection is not allowed - we're at the limit of 1
assertFalse(limiter.canOpenContactConnection());
// Close the second connection
limiter.connectionClosed(conn2, false);
// Third outgoing connection is allowed - we're below the limit of 1
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn3, false));
}
@Test
public void testLimiterRaisesLimitWhenConnectionsAreStable() {
// First outgoing connection is allowed - we're below the limit of 1
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn1, false));
// Second outgoing connection is allowed - it's time to try raising
// the limit to 2
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn2, false));
// Third outgoing connection is not allowed - we're above the limit of 1
assertFalse(limiter.canOpenContactConnection());
// Time passes
time.set(now + STABILITY_PERIOD_MS);
// Third outgoing connection is still not allowed - first two are now
// stable so limit is raised to 2, but we're already at the new limit
assertFalse(limiter.canOpenContactConnection());
// Time passes
time.set(now + MIN_ATTEMPT_INTERVAL_MS);
// Third outgoing connection is allowed - it's time to try raising
// the limit to 3
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn3, false));
// Fourth outgoing connection is not allowed - we're above the limit
// of 2
assertFalse(limiter.canOpenContactConnection());
}
@Test
public void testLimiterIncreasesIntervalWhenConnectionFailsAboveLimit() {
// First outgoing connection is allowed - we're below the limit of 1
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn1, false));
// Time passes
time.set(now + 1);
// Second outgoing connection is allowed - it's time to try raising
// the limit to 2
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn2, false));
// Time passes - the first connection is stable, the second isn't
time.set(now + STABILITY_PERIOD_MS);
// First connection fails. The second connection isn't stable yet, so
// the limiter considers this a failed attempt and doubles the interval
// between attempts
limiter.connectionClosed(conn1, true);
// Third outgoing connection is not allowed - we're still at the limit
// of 1
assertFalse(limiter.canOpenContactConnection());
// Time passes - nearly time for the second attempt
time.set(now + MIN_ATTEMPT_INTERVAL_MS * 2);
// Third outgoing connection is not allowed - we're still at the limit
// of 1
assertFalse(limiter.canOpenContactConnection());
// Time passes - now it's time for the second attempt
time.set(now + 1 + MIN_ATTEMPT_INTERVAL_MS * 2);
// Third outgoing connection is allowed - it's time to try raising the
// limit to 2 again
assertTrue(limiter.canOpenContactConnection());
assertTrue(limiter.contactConnectionOpened(conn3, false));
}
private void expectCloseSyncConnectionsEvent() {
context.checking(new Expectations() {{
oneOf(eventBus).broadcast(with(any(
CloseSyncConnectionsEvent.class)));
}});
}
}

View File

@@ -4,7 +4,6 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.db.DatabaseComponent;
import org.briarproject.bramble.api.db.Transaction;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.sync.Ack;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.Message;
@@ -24,7 +23,6 @@ import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
import static org.briarproject.bramble.test.TestUtils.getContactId;
import static org.briarproject.bramble.test.TestUtils.getMessage;
import static org.briarproject.bramble.test.TestUtils.getRandomId;
import static org.briarproject.bramble.test.TestUtils.getTransportId;
public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
@@ -38,15 +36,14 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
private final Executor dbExecutor = new ImmediateExecutor();
private final ContactId contactId = getContactId();
private final TransportId transportId = getTransportId();
private final Message message = getMessage(new GroupId(getRandomId()));
private final MessageId messageId = message.getId();
@Test
public void testNothingToSend() throws Exception {
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter);
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter,
recordWriter);
Transaction noAckTxn = new Transaction(null, false);
Transaction noMsgTxn = new Transaction(null, false);
@@ -79,8 +76,8 @@ public class SimplexOutgoingSessionTest extends BrambleMockTestCase {
public void testSomethingToSend() throws Exception {
Ack ack = new Ack(singletonList(messageId));
SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, MAX_LATENCY,
streamWriter, recordWriter);
dbExecutor, eventBus, contactId, MAX_LATENCY, streamWriter,
recordWriter);
Transaction ackTxn = new Transaction(null, false);
Transaction noAckTxn = new Transaction(null, false);
Transaction msgTxn = new Transaction(null, false);

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.plugin;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.ShutdownManager;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
@@ -10,7 +9,6 @@ import org.briarproject.bramble.api.plugin.PluginConfig;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.plugin.simplex.SimplexPluginFactory;
import org.briarproject.bramble.api.reliability.ReliabilityLayerFactory;
import org.briarproject.bramble.api.system.Clock;
import org.briarproject.bramble.plugin.bluetooth.JavaBluetoothPluginFactory;
import org.briarproject.bramble.plugin.modem.ModemPluginFactory;
import org.briarproject.bramble.plugin.tcp.LanTcpPluginFactory;
@@ -33,11 +31,10 @@ public class DesktopPluginModule extends PluginModule {
PluginConfig getPluginConfig(@IoExecutor Executor ioExecutor,
SecureRandom random, BackoffFactory backoffFactory,
ReliabilityLayerFactory reliabilityFactory,
ShutdownManager shutdownManager, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor) {
DuplexPluginFactory bluetooth = new JavaBluetoothPluginFactory(
ioExecutor, random, eventBus, clock, timeoutMonitor,
backoffFactory);
ShutdownManager shutdownManager, EventBus eventBus) {
DuplexPluginFactory bluetooth =
new JavaBluetoothPluginFactory(ioExecutor, random, eventBus,
backoffFactory);
DuplexPluginFactory modem = new ModemPluginFactory(ioExecutor,
reliabilityFactory);
DuplexPluginFactory lan = new LanTcpPluginFactory(ioExecutor,

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
@@ -35,11 +34,10 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
private volatile LocalDevice localDevice = null;
JavaBluetoothPlugin(BluetoothConnectionLimiter connectionManager,
TimeoutMonitor timeoutMonitor, Executor ioExecutor,
SecureRandom secureRandom, Backoff backoff,
PluginCallback callback, int maxLatency, int maxIdleTime) {
super(connectionManager, timeoutMonitor, ioExecutor, secureRandom,
backoff, callback, maxLatency, maxIdleTime);
Executor ioExecutor, SecureRandom secureRandom,
Backoff backoff, PluginCallback callback, int maxLatency) {
super(connectionManager, ioExecutor, secureRandom, backoff, callback,
maxLatency);
}
@Override
@@ -121,9 +119,7 @@ class JavaBluetoothPlugin extends BluetoothPlugin<StreamConnectionNotifier> {
return "btspp://" + address + ":" + uuid + ";name=RFCOMM";
}
private DuplexTransportConnection wrapSocket(StreamConnection s)
throws IOException {
return new JavaBluetoothTransportConnection(this, connectionLimiter,
timeoutMonitor, s);
private DuplexTransportConnection wrapSocket(StreamConnection s) {
return new JavaBluetoothTransportConnection(this, connectionLimiter, s);
}
}

View File

@@ -1,7 +1,6 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Backoff;
import org.briarproject.bramble.api.plugin.BackoffFactory;
@@ -9,7 +8,6 @@ import org.briarproject.bramble.api.plugin.PluginCallback;
import org.briarproject.bramble.api.plugin.TransportId;
import org.briarproject.bramble.api.plugin.duplex.DuplexPlugin;
import org.briarproject.bramble.api.plugin.duplex.DuplexPluginFactory;
import org.briarproject.bramble.api.system.Clock;
import java.security.SecureRandom;
import java.util.concurrent.Executor;
@@ -23,27 +21,22 @@ import static org.briarproject.bramble.api.plugin.BluetoothConstants.ID;
public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
private static final int MAX_LATENCY = 30 * 1000; // 30 seconds
private static final int MAX_IDLE_TIME = 30 * 1000; // 30 seconds
private static final int MIN_POLLING_INTERVAL = 60 * 1000; // 1 minute
private static final int MAX_POLLING_INTERVAL = 10 * 60 * 1000; // 10 mins
private static final double BACKOFF_BASE = 1.2;
private final Executor ioExecutor;
private final SecureRandom secureRandom;
private final EventBus eventBus;
private final Clock clock;
private final TimeoutMonitor timeoutMonitor;
private final BackoffFactory backoffFactory;
private final EventBus eventBus;
public JavaBluetoothPluginFactory(Executor ioExecutor,
SecureRandom secureRandom, EventBus eventBus, Clock clock,
TimeoutMonitor timeoutMonitor, BackoffFactory backoffFactory) {
SecureRandom secureRandom, EventBus eventBus,
BackoffFactory backoffFactory) {
this.ioExecutor = ioExecutor;
this.secureRandom = secureRandom;
this.eventBus = eventBus;
this.clock = clock;
this.timeoutMonitor = timeoutMonitor;
this.backoffFactory = backoffFactory;
this.eventBus = eventBus;
}
@Override
@@ -59,12 +52,11 @@ public class JavaBluetoothPluginFactory implements DuplexPluginFactory {
@Override
public DuplexPlugin createPlugin(PluginCallback callback) {
BluetoothConnectionLimiter connectionLimiter =
new BluetoothConnectionLimiterImpl(eventBus, clock);
new BluetoothConnectionLimiterImpl();
Backoff backoff = backoffFactory.createBackoff(MIN_POLLING_INTERVAL,
MAX_POLLING_INTERVAL, BACKOFF_BASE);
JavaBluetoothPlugin plugin = new JavaBluetoothPlugin(connectionLimiter,
timeoutMonitor, ioExecutor, secureRandom, backoff, callback,
MAX_LATENCY, MAX_IDLE_TIME);
ioExecutor, secureRandom, backoff, callback, MAX_LATENCY);
eventBus.addListener(plugin);
return plugin;
}

View File

@@ -1,6 +1,5 @@
package org.briarproject.bramble.plugin.bluetooth;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.Plugin;
import org.briarproject.bramble.api.plugin.duplex.AbstractDuplexTransportConnection;
@@ -15,24 +14,20 @@ import javax.microedition.io.StreamConnection;
class JavaBluetoothTransportConnection
extends AbstractDuplexTransportConnection {
private final BluetoothConnectionLimiter connectionLimiter;
private final BluetoothConnectionLimiter connectionManager;
private final StreamConnection stream;
private final InputStream in;
JavaBluetoothTransportConnection(Plugin plugin,
BluetoothConnectionLimiter connectionLimiter,
TimeoutMonitor timeoutMonitor,
StreamConnection stream) throws IOException {
BluetoothConnectionLimiter connectionManager,
StreamConnection stream) {
super(plugin);
this.connectionLimiter = connectionLimiter;
this.stream = stream;
in = timeoutMonitor.createTimeoutInputStream(
stream.openInputStream(), plugin.getMaxIdleTime() * 2);
this.connectionManager = connectionManager;
}
@Override
protected InputStream getInputStream() {
return in;
protected InputStream getInputStream() throws IOException {
return stream.openInputStream();
}
@Override
@@ -45,7 +40,7 @@ class JavaBluetoothTransportConnection
try {
stream.close();
} finally {
connectionLimiter.connectionClosed(this, exception);
connectionManager.connectionClosed(this);
}
}
}

View File

@@ -14,7 +14,6 @@ import org.briarproject.bramble.api.crypto.KeyStrengthener;
import org.briarproject.bramble.api.crypto.PublicKey;
import org.briarproject.bramble.api.db.DatabaseConfig;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.io.TimeoutMonitor;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.network.NetworkManager;
@@ -123,12 +122,11 @@ public class AppModule {
LocationUtils locationUtils, EventBus eventBus,
ResourceProvider resourceProvider,
CircumventionProvider circumventionProvider,
BatteryManager batteryManager, Clock clock,
TimeoutMonitor timeoutMonitor) {
BatteryManager batteryManager, Clock clock) {
Context appContext = app.getApplicationContext();
DuplexPluginFactory bluetooth = new AndroidBluetoothPluginFactory(
ioExecutor, androidExecutor, appContext, random, eventBus,
clock, timeoutMonitor, backoffFactory);
DuplexPluginFactory bluetooth =
new AndroidBluetoothPluginFactory(ioExecutor, androidExecutor,
appContext, random, eventBus, clock, backoffFactory);
DuplexPluginFactory tor = new AndroidTorPluginFactory(ioExecutor,
scheduler, appContext, networkManager, locationUtils, eventBus,
torSocketFactory, backoffFactory, resourceProvider,

View File

@@ -2,35 +2,38 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import javax.annotation.concurrent.NotThreadSafe;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.DISCONNECTED;
@NotThreadSafe
@NotNullByDefault
public class ContactItem {
private final Contact contact;
private boolean connected;
private ConnectionStatus status;
public ContactItem(Contact contact) {
this(contact, false);
this(contact, DISCONNECTED);
}
public ContactItem(Contact contact, boolean connected) {
public ContactItem(Contact contact, ConnectionStatus status) {
this.contact = contact;
this.connected = connected;
this.status = status;
}
public Contact getContact() {
return contact;
}
boolean isConnected() {
return connected;
ConnectionStatus getConnectionStatus() {
return status;
}
void setConnected(boolean connected) {
this.connected = connected;
void setConnectionStatus(ConnectionStatus status) {
this.status = status;
}
}

View File

@@ -7,6 +7,7 @@ import android.widget.TextView;
import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -16,6 +17,8 @@ import androidx.annotation.UiThread;
import androidx.recyclerview.widget.RecyclerView;
import im.delight.android.identicons.IdenticonDrawable;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread
@@ -27,7 +30,7 @@ public class ContactItemViewHolder<I extends ContactItem>
protected final ImageView avatar;
protected final TextView name;
@Nullable
protected final ImageView bulb;
private final ImageView bulb;
public ContactItemViewHolder(View v) {
super(v);
@@ -47,10 +50,13 @@ public class ContactItemViewHolder<I extends ContactItem>
if (bulb != null) {
// online/offline
if (item.isConnected()) {
bulb.setImageResource(R.drawable.contact_connected);
ConnectionStatus status = item.getConnectionStatus();
if (status == CONNECTED) {
bulb.setImageResource(R.drawable.ic_connected);
} else if (status == RECENTLY_CONNECTED) {
bulb.setImageResource(R.drawable.ic_recently_connected);
} else {
bulb.setImageResource(R.drawable.contact_disconnected);
bulb.setImageResource(R.drawable.ic_disconnected);
}
}

View File

@@ -39,7 +39,7 @@ public class ContactListAdapter extends
if (c1.getTimestamp() != c2.getTimestamp()) {
return false;
}
return c1.isConnected() == c2.isConnected();
return c1.getConnectionStatus() == c2.getConnectionStatus();
}
@Override

View File

@@ -25,8 +25,8 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -53,7 +53,6 @@ import javax.inject.Inject;
import androidx.annotation.UiThread;
import androidx.core.app.ActivityCompat;
import androidx.core.app.ActivityOptionsCompat;
import androidx.core.util.Pair;
import androidx.recyclerview.widget.LinearLayoutManager;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial;
import io.github.kobakei.materialfabspeeddial.FabSpeedDial.OnMenuItemClickListener;
@@ -137,26 +136,15 @@ public class ContactListFragment extends BaseFragment implements EventListener,
ContactId contactId = item.getContact().getId();
i.putExtra(CONTACT_ID, contactId.getInt());
Bundle options = null;
// work-around for android bug #224270
if (SDK_INT >= 23 && !isSamsung7()) {
ContactListItemViewHolder holder =
(ContactListItemViewHolder) list
.getRecyclerView()
.findViewHolderForAdapterPosition(
adapter.findItemPosition(item));
Pair<View, String> avatar =
Pair.create(holder.avatar,
getTransitionName(holder.avatar));
Pair<View, String> bulb =
Pair.create(holder.bulb,
getTransitionName(holder.bulb));
ActivityOptionsCompat options =
makeSceneTransitionAnimation(getActivity(),
avatar, bulb);
ActivityCompat.startActivity(getActivity(), i,
options.toBundle());
} else {
// work-around for android bug #224270
options = makeTransitionOptions(view);
}
if (options == null) {
startActivity(i);
} else {
ActivityCompat.startActivity(getActivity(), i, options);
}
};
adapter = new ContactListAdapter(requireContext(),
@@ -171,6 +159,15 @@ public class ContactListFragment extends BaseFragment implements EventListener,
return contentView;
}
@Nullable
private Bundle makeTransitionOptions(View view) {
View avatar = view.findViewById(R.id.avatarView);
String name = requireNonNull(getTransitionName(avatar));
ActivityOptionsCompat options = makeSceneTransitionAnimation(
requireActivity(), view, name);
return options.toBundle();
}
@Override
public void onMenuItemClick(FloatingActionButton fab, @Nullable TextView v,
int itemId) {
@@ -232,9 +229,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
ConnectionStatus status = connectionRegistry
.getConnectionStatus(c.getId());
contacts.add(new ContactListItem(c, status, count));
} catch (NoSuchContactException e) {
// Continue
}
@@ -265,10 +262,9 @@ public class ContactListFragment extends BaseFragment implements EventListener,
if (e instanceof ContactAddedEvent) {
LOG.info("Contact added, reloading");
loadContacts();
} else if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId(), true);
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId(), false);
} else if (e instanceof ConnectionStatusChangedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e;
setConnectionStatus(c.getContactId(), c.getConnectionStatus());
} else if (e instanceof ContactRemovedEvent) {
LOG.info("Contact removed, removing item");
removeItem(((ContactRemovedEvent) e).getContactId());
@@ -304,12 +300,12 @@ public class ContactListFragment extends BaseFragment implements EventListener,
}
@UiThread
private void setConnected(ContactId c, boolean connected) {
private void setConnectionStatus(ContactId c, ConnectionStatus status) {
adapter.incrementRevision();
int position = adapter.findItemPosition(c);
ContactListItem item = adapter.getItemAt(position);
if (item != null) {
item.setConnected(connected);
item.setConnectionStatus(status);
adapter.updateItemAt(position, item);
}
}

View File

@@ -2,6 +2,7 @@ package org.briarproject.briar.android.contact;
import org.briarproject.bramble.api.contact.Contact;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.api.client.MessageTracker.GroupCount;
import org.briarproject.briar.api.conversation.ConversationMessageHeader;
@@ -15,9 +16,9 @@ public class ContactListItem extends ContactItem {
private long timestamp;
private int unread;
public ContactListItem(Contact contact, boolean connected,
public ContactListItem(Contact contact, ConnectionStatus status,
GroupCount count) {
super(contact, connected);
super(contact, status);
this.empty = count.getMsgCount() == 0;
this.unread = count.getUnreadCount();
this.timestamp = count.getLatestMsgTime();

View File

@@ -7,7 +7,6 @@ import org.briarproject.bramble.api.contact.ContactId;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
import org.briarproject.briar.android.util.UiUtils;
import java.util.Locale;
@@ -15,8 +14,11 @@ import javax.annotation.Nullable;
import androidx.annotation.UiThread;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static androidx.core.view.ViewCompat.setTransitionName;
import static org.briarproject.briar.android.util.UiUtils.formatDate;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
@UiThread
@NotNullByDefault
@@ -39,10 +41,11 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
// unread count
int unreadCount = item.getUnreadCount();
if (unreadCount > 0) {
unread.setText(String.format(Locale.getDefault(), "%d", unreadCount));
unread.setVisibility(View.VISIBLE);
unread.setText(String.format(Locale.getDefault(), "%d",
unreadCount));
unread.setVisibility(VISIBLE);
} else {
unread.setVisibility(View.INVISIBLE);
unread.setVisibility(INVISIBLE);
}
// date of last message
@@ -54,8 +57,7 @@ class ContactListItemViewHolder extends ContactItemViewHolder<ContactListItem> {
}
ContactId c = item.getContact().getId();
setTransitionName(avatar, UiUtils.getAvatarTransitionName(c));
setTransitionName(bulb, UiUtils.getBulbTransitionName(c));
setTransitionName(avatar, getAvatarTransitionName(c));
}
}

View File

@@ -6,8 +6,7 @@ import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import java.util.Collection;
import java.util.HashSet;
@@ -18,6 +17,8 @@ import javax.inject.Inject;
import androidx.annotation.UiThread;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
@NotNullByDefault
public class SharingControllerImpl implements SharingController, EventListener {
@@ -60,15 +61,14 @@ public class SharingControllerImpl implements SharingController, EventListener {
@Override
public void eventOccurred(Event e) {
if (e instanceof ContactConnectedEvent) {
setConnected(((ContactConnectedEvent) e).getContactId());
} else if (e instanceof ContactDisconnectedEvent) {
setConnected(((ContactDisconnectedEvent) e).getContactId());
if (e instanceof ConnectionStatusChangedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e;
setConnectionStatus(c.getContactId());
}
}
@UiThread
private void setConnected(ContactId c) {
private void setConnectionStatus(ContactId c) {
if (listener == null) throw new IllegalStateException();
if (contacts.contains(c)) {
int online = getOnlineCount();
@@ -95,7 +95,9 @@ public class SharingControllerImpl implements SharingController, EventListener {
public int getOnlineCount() {
int online = 0;
for (ContactId c : contacts) {
if (connectionRegistry.isConnected(c)) online++;
if (connectionRegistry.getConnectionStatus(c) == CONNECTED) {
online++;
}
}
return online;
}

View File

@@ -13,7 +13,6 @@ import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
@@ -34,8 +33,8 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.event.ContactConnectedEvent;
import org.briarproject.bramble.api.plugin.event.ContactDisconnectedEvent;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.plugin.event.ConnectionStatusChangedEvent;
import org.briarproject.bramble.api.sync.ClientId;
import org.briarproject.bramble.api.sync.MessageId;
import org.briarproject.bramble.api.sync.event.MessagesAckedEvent;
@@ -125,6 +124,8 @@ import static java.util.Objects.requireNonNull;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static java.util.logging.Logger.getLogger;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
import static org.briarproject.bramble.util.LogUtils.logDuration;
import static org.briarproject.bramble.util.LogUtils.logException;
import static org.briarproject.bramble.util.LogUtils.now;
@@ -138,7 +139,6 @@ import static org.briarproject.briar.android.conversation.ImageActivity.ATTACHME
import static org.briarproject.briar.android.conversation.ImageActivity.DATE;
import static org.briarproject.briar.android.conversation.ImageActivity.NAME;
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
import static org.briarproject.briar.android.util.UiUtils.observeOnce;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_ATTACHMENTS_PER_MESSAGE;
import static org.briarproject.briar.api.messaging.MessagingConstants.MAX_PRIVATE_MESSAGE_TEXT_LENGTH;
@@ -198,8 +198,8 @@ public class ConversationActivity extends BriarActivity
private ConversationAdapter adapter;
private Toolbar toolbar;
private CircleImageView toolbarAvatar;
private ImageView toolbarStatus;
private TextView toolbarTitle;
private TextView toolbarStatus;
private BriarRecyclerView list;
private LinearLayoutManager layoutManager;
private TextInputView textInputView;
@@ -237,8 +237,8 @@ public class ConversationActivity extends BriarActivity
// Custom Toolbar
toolbar = requireNonNull(setUpCustomToolbar(true));
toolbarAvatar = toolbar.findViewById(R.id.contactAvatar);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
toolbarTitle = toolbar.findViewById(R.id.contactName);
toolbarStatus = toolbar.findViewById(R.id.contactStatus);
observeOnce(viewModel.getContactAuthorId(), this, authorId -> {
requireNonNull(authorId);
@@ -257,7 +257,6 @@ public class ConversationActivity extends BriarActivity
this::onAddedPrivateMessage);
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
setTransitionName(toolbarStatus, getBulbTransitionName(contactId));
visitor = new ConversationVisitor(this, this, this,
viewModel.getContactDisplayName());
@@ -499,14 +498,14 @@ public class ConversationActivity extends BriarActivity
@UiThread
private void displayContactOnlineStatus() {
if (connectionRegistry.isConnected(contactId)) {
toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_online));
toolbarStatus.setContentDescription(getString(R.string.online));
ConnectionStatus status =
connectionRegistry.getConnectionStatus(contactId);
if (status == CONNECTED) {
toolbarStatus.setText(R.string.online);
} else if (status == RECENTLY_CONNECTED) {
toolbarStatus.setText(R.string.recently_online);
} else {
toolbarStatus.setImageDrawable(ContextCompat.getDrawable(
ConversationActivity.this, R.drawable.contact_offline));
toolbarStatus.setContentDescription(getString(R.string.offline));
toolbarStatus.setText(R.string.offline);
}
}
@@ -729,16 +728,10 @@ public class ConversationActivity extends BriarActivity
LOG.info("Messages acked");
markMessages(m.getMessageIds(), true, true);
}
} else if (e instanceof ContactConnectedEvent) {
ContactConnectedEvent c = (ContactConnectedEvent) e;
} else if (e instanceof ConnectionStatusChangedEvent) {
ConnectionStatusChangedEvent c = (ConnectionStatusChangedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact connected");
displayContactOnlineStatus();
}
} else if (e instanceof ContactDisconnectedEvent) {
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
if (c.getContactId().equals(contactId)) {
LOG.info("Contact disconnected");
LOG.info("Connection status changed");
displayContactOnlineStatus();
}
} else if (e instanceof ClientVersionUpdatedEvent) {

View File

@@ -12,6 +12,7 @@ import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.contact.BaseContactListAdapter.OnContactClickListener;
@@ -73,7 +74,8 @@ public class ContactChooserFragment extends BaseFragment {
}
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
public View onCreateView(LayoutInflater inflater,
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View contentView = inflater.inflate(R.layout.list, container, false);
@@ -127,9 +129,9 @@ public class ContactChooserFragment extends BaseFragment {
ContactId id = c.getId();
GroupCount count =
conversationManager.getGroupCount(id);
boolean connected =
connectionRegistry.isConnected(c.getId());
contacts.add(new ContactListItem(c, connected, count));
ConnectionStatus status = connectionRegistry
.getConnectionStatus(c.getId());
contacts.add(new ContactListItem(c, status, count));
}
}
displayContacts(contacts);

View File

@@ -99,7 +99,7 @@ public class GroupMemberListActivity extends BriarActivity
supportFinishAfterTransition();
}
}
// TODO ContactConnectedEvent and ContactDisconnectedEvent
// TODO ConnectionStatusChangedEvent
}
@Override

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.db.DatabaseExecutor;
import org.briarproject.bramble.api.db.DbException;
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.controller.handler.ResultExceptionHandler;
@@ -19,6 +20,7 @@ import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.WARNING;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.DISCONNECTED;
import static org.briarproject.bramble.util.LogUtils.logException;
class GroupMemberListControllerImpl extends DbControllerImpl
@@ -50,10 +52,11 @@ class GroupMemberListControllerImpl extends DbControllerImpl
privateGroupManager.getMembers(groupId);
for (GroupMember m : members) {
ContactId c = m.getContactId();
boolean online = false;
if (c != null)
online = connectionRegistry.isConnected(c);
items.add(new MemberListItem(m, online));
ConnectionStatus status = DISCONNECTED;
if (c != null) {
status = connectionRegistry.getConnectionStatus(c);
}
items.add(new MemberListItem(m, status));
}
handler.onResult(items);
} catch (DbException e) {

View File

@@ -45,7 +45,7 @@ class MemberListAdapter extends
@Override
public boolean areContentsTheSame(MemberListItem m1, MemberListItem m2) {
if (m1.isOnline() != m2.isOnline()) return false;
if (m1.getConnectionStatus() != m2.getConnectionStatus()) return false;
if (m1.getContactId() != m2.getContactId()) return false;
if (m1.getStatus() != m2.getStatus()) return false;
return true;

View File

@@ -5,6 +5,7 @@ import org.briarproject.bramble.api.identity.Author;
import org.briarproject.bramble.api.identity.AuthorInfo;
import org.briarproject.bramble.api.identity.AuthorInfo.Status;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.api.privategroup.GroupMember;
import javax.annotation.Nullable;
@@ -15,11 +16,11 @@ import javax.annotation.concurrent.NotThreadSafe;
class MemberListItem {
private final GroupMember groupMember;
private boolean online;
private ConnectionStatus status;
MemberListItem(GroupMember groupMember, boolean online) {
MemberListItem(GroupMember groupMember, ConnectionStatus status) {
this.groupMember = groupMember;
this.online = online;
this.status = status;
}
Author getMember() {
@@ -43,12 +44,12 @@ class MemberListItem {
return groupMember.getContactId();
}
boolean isOnline() {
return online;
ConnectionStatus getConnectionStatus() {
return status;
}
void setOnline(boolean online) {
this.online = online;
void setConnectionStatus(ConnectionStatus status) {
this.status = status;
}
}

View File

@@ -5,6 +5,7 @@ import android.widget.ImageView;
import android.widget.TextView;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.briar.R;
import org.briarproject.briar.android.view.AuthorView;
@@ -14,6 +15,8 @@ import androidx.recyclerview.widget.RecyclerView;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
import static org.briarproject.bramble.api.identity.AuthorInfo.Status.OURSELVES;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.CONNECTED;
import static org.briarproject.bramble.api.plugin.ConnectionStatus.RECENTLY_CONNECTED;
import static org.briarproject.briar.android.util.UiUtils.getContactDisplayName;
@UiThread
@@ -38,10 +41,13 @@ class MemberListItemHolder extends RecyclerView.ViewHolder {
// online status of visible contacts
if (item.getContactId() != null) {
bulb.setVisibility(VISIBLE);
if (item.isOnline()) {
bulb.setImageResource(R.drawable.contact_connected);
ConnectionStatus status = item.getConnectionStatus();
if (status == CONNECTED) {
bulb.setImageResource(R.drawable.ic_connected);
} else if (status == RECENTLY_CONNECTED) {
bulb.setImageResource(R.drawable.ic_recently_connected);
} else {
bulb.setImageResource(R.drawable.contact_disconnected);
bulb.setImageResource(R.drawable.ic_disconnected);
}
} else {
bulb.setVisibility(GONE);

View File

@@ -14,6 +14,7 @@ import org.briarproject.bramble.api.event.EventListener;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.bramble.api.plugin.ConnectionRegistry;
import org.briarproject.bramble.api.plugin.ConnectionStatus;
import org.briarproject.bramble.api.sync.GroupId;
import org.briarproject.bramble.api.sync.event.GroupRemovedEvent;
import org.briarproject.briar.R;
@@ -104,7 +105,7 @@ abstract class SharingStatusActivity extends BriarActivity
supportFinishAfterTransition();
}
}
// TODO ContactConnectedEvent and ContactDisconnectedEvent
// TODO ConnectionStatusChangedEvent
}
@Override
@@ -134,8 +135,9 @@ abstract class SharingStatusActivity extends BriarActivity
try {
List<ContactItem> contactItems = new ArrayList<>();
for (Contact c : getSharedWith()) {
boolean online = connectionRegistry.isConnected(c.getId());
ContactItem item = new ContactItem(c, online);
ConnectionStatus status =
connectionRegistry.getConnectionStatus(c.getId());
ContactItem item = new ContactItem(c, status);
contactItems.add(item);
}
displaySharedWith(contactItems);

View File

@@ -238,10 +238,6 @@ public class UiUtils {
return "avatar" + c.getInt();
}
public static String getBulbTransitionName(ContactId c) {
return "bulb" + c.getInt();
}
public static OnClickListener getGoToSettingsListener(Context context) {
return (dialog, which) -> {
Intent i = new Intent();

View File

@@ -1,24 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#abffffff"
android:pathData="M12,2 C6.48,2,2,6.48,2,12 S6.48,22,12,22 S22,17.52,22,12 S17.52,2,12,2 Z M12,20
C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/>
<path
android:fillColor="#95d220"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295
C20.0698,10.7495,20.1616,12.4612,19.777,13.9758
C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771
C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="0.76779664"/>
</vector>

View File

@@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#abffffff"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#95d220"
android:strokeColor="#abffffff"
android:fillAlpha="1"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#abffffff"
android:fillType="nonZero"
android:fillAlpha="1"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:strokeAlpha="1"
android:strokeWidth="4.5"
android:fillColor="#00000000"
android:strokeColor="#95d220"
android:fillAlpha="1"/>
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#abffffff"
android:fillAlpha="1"/>
</vector>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.56"
android:viewportHeight="24"
android:viewportWidth="24">
<path
android:fillColor="#FF000000"
android:pathData="M12,2 C6.48,2,2,6.48,2,12 S6.48,22,12,22 S22,17.52,22,12 S17.52,2,12,2 Z M12,20
C7.58,20,4,16.42,4,12 S7.58,4,12,4 S20,7.58,20,12 S16.42,20,12,20 Z"/>
<path
android:pathData="M0,0 L24,0 L24,24 L0,24 Z"/>
<path
android:fillColor="#95d220"
android:pathData="M10.8972,19.9503 C6.5514,19.3493,3.43091,15.2154,4.0625,10.896
C4.55452,7.53099,7.09451,4.8236,10.394,4.14714
C14.2569,3.35517,18.1698,5.54347,19.5236,9.25295
C20.0698,10.7495,20.1616,12.4612,19.777,13.9758
C19.5457,14.8864,18.8106,16.3388,18.2072,17.0771
C16.4904,19.1779,13.581,20.3215,10.8973,19.9503 Z"
android:strokeLineCap="round"
android:strokeLineJoin="round"
android:strokeWidth="0.76779664"/>
</vector>

View File

@@ -1,10 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:alpha="0.56"
android:viewportHeight="24.0"
android:viewportWidth="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zm0,18c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8z"/>
</vector>

View File

@@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#95d220"
android:strokeColor="#000000"
android:fillAlpha="0.56"/>
</vector>

View File

@@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:fillType="nonZero"
android:fillAlpha="1"/>
</vector>

View File

@@ -0,0 +1,20 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:pathData="M12,12m-7,0a7,7 0,1 1,14 0a7,7 0,1 1,-14 0"
android:strokeAlpha="0.56"
android:strokeWidth="4.5"
android:fillColor="#ffffff"
android:strokeColor="#95d220"
android:fillAlpha="1"/>
<path
android:pathData="M12,12m-10,0a10,10 0,1 1,20 0a10,10 0,1 1,-20 0"
android:strokeAlpha="1"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:fillAlpha="1"/>
</vector>

View File

@@ -18,27 +18,50 @@
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<include layout="@layout/contact_avatar_status" />
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/contactAvatar"
style="@style/BriarAvatar"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_alignParentStart="true"
android:layout_alignParentLeft="true"
android:layout_margin="4dp"
app:civ_border_color="@color/action_bar_text"
tools:src="@mipmap/ic_launcher_round" />
<com.vanniktech.emoji.EmojiTextView
android:id="@+id/contactName"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Title.Inverse"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:layout_marginStart="@dimen/margin_medium"
android:layout_marginLeft="@dimen/margin_medium"
android:layout_toEndOf="@id/contactAvatar"
android:layout_toRightOf="@id/contactAvatar"
android:ellipsize="end"
android:gravity="center_vertical"
android:maxLines="1"
android:textColor="@color/action_bar_text"
tools:text="Contact Name of someone who chose a long name" />
</LinearLayout>
<TextView
android:id="@+id/contactStatus"
style="@style/TextAppearance.AppCompat.Widget.ActionBar.Subtitle.Inverse"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/contactName"
android:layout_marginStart="8dp"
android:layout_marginLeft="8dp"
android:layout_toEndOf="@id/contactAvatar"
android:layout_toRightOf="@id/contactAvatar"
android:textColor="@color/briar_text_secondary_inverse"
tools:text="Recently online" />
</RelativeLayout>
</androidx.appcompat.widget.Toolbar>

View File

@@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="32dp"
android:layout_height="32dp"
tools:showIn="@layout/activity_conversation">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/contactAvatar"
style="@style/BriarAvatar"
android:layout_width="30dp"
android:layout_height="30dp"
app:civ_border_color="@color/action_bar_text"
tools:src="@mipmap/ic_launcher_round" />
<ImageView
android:id="@+id/contactStatus"
android:layout_width="15dp"
android:layout_height="15dp"
android:layout_gravity="bottom|end|right"
android:scaleType="fitCenter"
tools:ignore="ContentDescription"
tools:src="@drawable/contact_online" />
</FrameLayout>

View File

@@ -90,7 +90,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/contact_connected" />
tools:src="@drawable/ic_recently_connected" />
<View
android:id="@+id/divider"

View File

@@ -40,6 +40,6 @@
android:layout_marginEnd="@dimen/listitem_horizontal_margin"
android:layout_marginRight="@dimen/listitem_horizontal_margin"
tools:ignore="ContentDescription"
tools:src="@drawable/contact_connected" />
tools:src="@drawable/ic_connected" />
</LinearLayout>

View File

@@ -24,7 +24,7 @@
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="ContentDescription"
tools:src="@drawable/contact_connected" />
tools:src="@drawable/ic_connected" />
<TextView
android:id="@+id/creatorView"

View File

@@ -110,6 +110,7 @@
<string name="decline">Decline</string>
<string name="options">Options</string>
<string name="online">Online</string>
<string name="recently_online">Recently online</string>
<string name="offline">Offline</string>
<string name="send">Send</string>
<string name="allow">Allow</string>