mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Compare commits
50 Commits
851-recycl
...
beta-2017-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e51ddb767 | ||
|
|
36f02b36d9 | ||
|
|
2fb11fba2a | ||
|
|
1d11857e75 | ||
|
|
04508a7431 | ||
|
|
5653c6d650 | ||
|
|
ab100ad19b | ||
|
|
c13eafef14 | ||
|
|
d5443e9651 | ||
|
|
d5f9a3280d | ||
|
|
09b2ecaecf | ||
|
|
dc6a6f27ab | ||
|
|
8d9ddeeeee | ||
|
|
baed2b8483 | ||
|
|
b3d3230549 | ||
|
|
deb8787668 | ||
|
|
7034ea28f3 | ||
|
|
51b78cf9b1 | ||
|
|
b4c669243b | ||
|
|
694e662028 | ||
|
|
409e0fb5a5 | ||
|
|
279f4d668a | ||
|
|
d2608e28ac | ||
|
|
8cf02c5f0e | ||
|
|
c5df2100da | ||
|
|
a6999a8197 | ||
|
|
da89f11419 | ||
|
|
a9663875f4 | ||
|
|
804966ede6 | ||
|
|
f0f22b42e5 | ||
|
|
59316ae3c4 | ||
|
|
460b524e4b | ||
|
|
48e949c9f8 | ||
|
|
924398c829 | ||
|
|
8619b044ce | ||
|
|
3c3731a562 | ||
|
|
b54984b542 | ||
|
|
2390f767f5 | ||
|
|
0a9840997f | ||
|
|
6a94785d9a | ||
|
|
79fc41477c | ||
|
|
efb89adf41 | ||
|
|
c04580e321 | ||
|
|
2ef9b8f4b6 | ||
|
|
d63d15329c | ||
|
|
5345db0b6b | ||
|
|
501980d8fe | ||
|
|
cc5c000278 | ||
|
|
7666b210e4 | ||
|
|
db71472501 |
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A packet acknowledging receipt of one or more {@link Message Messages}.
|
||||
* A record acknowledging receipt of one or more {@link Message Messages}.
|
||||
*/
|
||||
public class Ack {
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
|
||||
public class Group {
|
||||
|
||||
public enum Visibility {
|
||||
@@ -13,6 +15,8 @@ public class Group {
|
||||
private final byte[] descriptor;
|
||||
|
||||
public Group(GroupId id, ClientId clientId, byte[] descriptor) {
|
||||
if (descriptor.length > MAX_GROUP_DESCRIPTOR_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
this.id = id;
|
||||
this.clientId = clientId;
|
||||
this.descriptor = descriptor;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A packet offering the recipient one or more {@link Message Messages}.
|
||||
* A record offering the recipient one or more {@link Message Messages}.
|
||||
*/
|
||||
public class Offer {
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PacketReader {
|
||||
public interface RecordReader {
|
||||
|
||||
boolean eof() throws IOException;
|
||||
|
||||
@@ -24,4 +24,5 @@ public interface PacketReader {
|
||||
boolean hasRequest() throws IOException;
|
||||
|
||||
Request readRequest() throws IOException;
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.InputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PacketReaderFactory {
|
||||
public interface RecordReaderFactory {
|
||||
|
||||
PacketReader createPacketReader(InputStream in);
|
||||
RecordReader createRecordReader(InputStream in);
|
||||
}
|
||||
@@ -1,12 +1,13 @@
|
||||
package org.briarproject.bramble.api.sync;
|
||||
|
||||
/**
|
||||
* Packet types for the sync protocol.
|
||||
* Record types for the sync protocol.
|
||||
*/
|
||||
public interface PacketTypes {
|
||||
public interface RecordTypes {
|
||||
|
||||
byte ACK = 0;
|
||||
byte MESSAGE = 1;
|
||||
byte OFFER = 2;
|
||||
byte REQUEST = 3;
|
||||
|
||||
}
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.IOException;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PacketWriter {
|
||||
public interface RecordWriter {
|
||||
|
||||
void writeAck(Ack a) throws IOException;
|
||||
|
||||
@@ -5,7 +5,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
public interface PacketWriterFactory {
|
||||
public interface RecordWriterFactory {
|
||||
|
||||
PacketWriter createPacketWriter(OutputStream out);
|
||||
RecordWriter createRecordWriter(OutputStream out);
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package org.briarproject.bramble.api.sync;
|
||||
import java.util.Collection;
|
||||
|
||||
/**
|
||||
* A packet requesting one or more {@link Message Messages} from the recipient.
|
||||
* A record requesting one or more {@link Message Messages} from the recipient.
|
||||
*/
|
||||
public class Request {
|
||||
|
||||
|
||||
@@ -10,19 +10,17 @@ public interface SyncConstants {
|
||||
byte PROTOCOL_VERSION = 0;
|
||||
|
||||
/**
|
||||
* The length of the packet header in bytes.
|
||||
* The length of the record header in bytes.
|
||||
*/
|
||||
int PACKET_HEADER_LENGTH = 4;
|
||||
int RECORD_HEADER_LENGTH = 4;
|
||||
|
||||
/**
|
||||
* The maximum length of the packet payload in bytes.
|
||||
* The maximum length of the record payload in bytes.
|
||||
*/
|
||||
int MAX_PACKET_PAYLOAD_LENGTH = 32 * 1024; // 32 KiB
|
||||
int MAX_RECORD_PAYLOAD_LENGTH = 48 * 1024; // 48 KiB
|
||||
|
||||
/**
|
||||
* The maximum length of a message in bytes.
|
||||
*/
|
||||
int MAX_MESSAGE_LENGTH = MAX_PACKET_PAYLOAD_LENGTH - PACKET_HEADER_LENGTH;
|
||||
/** The maximum length of a group descriptor in bytes. */
|
||||
int MAX_GROUP_DESCRIPTOR_LENGTH = 16 * 1024; // 16 KiB
|
||||
|
||||
/**
|
||||
* The length of the message header in bytes.
|
||||
@@ -32,10 +30,15 @@ public interface SyncConstants {
|
||||
/**
|
||||
* The maximum length of a message body in bytes.
|
||||
*/
|
||||
int MAX_MESSAGE_BODY_LENGTH = MAX_MESSAGE_LENGTH - MESSAGE_HEADER_LENGTH;
|
||||
int MAX_MESSAGE_BODY_LENGTH = 32 * 1024; // 32 KiB
|
||||
|
||||
/**
|
||||
* The maximum number of message IDs in an ack, offer or request packet.
|
||||
* The maximum length of a message in bytes.
|
||||
*/
|
||||
int MAX_MESSAGE_IDS = MAX_PACKET_PAYLOAD_LENGTH / UniqueId.LENGTH;
|
||||
int MAX_MESSAGE_LENGTH = MESSAGE_HEADER_LENGTH + MAX_MESSAGE_BODY_LENGTH;
|
||||
|
||||
/**
|
||||
* The maximum number of message IDs in an ack, offer or request record.
|
||||
*/
|
||||
int MAX_MESSAGE_IDS = MAX_RECORD_PAYLOAD_LENGTH / UniqueId.LENGTH;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.io.IOException;
|
||||
public interface SyncSession {
|
||||
|
||||
/**
|
||||
* Runs the session. This method returns when there are no more packets to
|
||||
* Runs the session. This method returns when there are no more records to
|
||||
* send or receive, or when the {@link #interrupt()} method has been called.
|
||||
*/
|
||||
void run() throws IOException;
|
||||
|
||||
@@ -52,7 +52,7 @@ class KeyAgreementProtocol {
|
||||
|
||||
void connectionWaiting();
|
||||
|
||||
void initialPacketReceived();
|
||||
void initialRecordReceived();
|
||||
}
|
||||
|
||||
private final Callbacks callbacks;
|
||||
@@ -117,7 +117,7 @@ class KeyAgreementProtocol {
|
||||
|
||||
private byte[] receiveKey() throws AbortException {
|
||||
byte[] publicKey = transport.receiveKey();
|
||||
callbacks.initialPacketReceived();
|
||||
callbacks.initialRecordReceived();
|
||||
byte[] expected = crypto.deriveKeyCommitment(publicKey);
|
||||
if (!Arrays.equals(expected, theirPayload.getCommitment()))
|
||||
throw new AbortException();
|
||||
|
||||
@@ -129,7 +129,7 @@ class KeyAgreementTaskImpl extends Thread implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initialPacketReceived() {
|
||||
public void initialRecordReceived() {
|
||||
// We send this here instead of when we create the protocol, so that
|
||||
// if device A makes a connection after getting device B's payload and
|
||||
// starts its protocol, device A's UI doesn't change to prevent device B
|
||||
|
||||
@@ -95,20 +95,30 @@ class KeyAgreementTransport {
|
||||
out.flush();
|
||||
}
|
||||
|
||||
private byte[] readRecord(byte type) throws AbortException {
|
||||
byte[] header = readHeader();
|
||||
if (header[0] != PROTOCOL_VERSION)
|
||||
throw new AbortException(); // TODO handle?
|
||||
if (header[1] != type) {
|
||||
// Unexpected packet
|
||||
throw new AbortException(header[1] == ABORT);
|
||||
}
|
||||
int len = ByteUtils.readUint16(header,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
try {
|
||||
return readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
private byte[] readRecord(byte expectedType) throws AbortException {
|
||||
while (true) {
|
||||
byte[] header = readHeader();
|
||||
byte version = header[0], type = header[1];
|
||||
int len = ByteUtils.readUint16(header,
|
||||
RECORD_HEADER_PAYLOAD_LENGTH_OFFSET);
|
||||
// Reject unrecognised protocol version
|
||||
if (version != PROTOCOL_VERSION) throw new AbortException(false);
|
||||
if (type == ABORT) throw new AbortException(true);
|
||||
if (type == expectedType) {
|
||||
try {
|
||||
return readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
// Reject recognised but unexpected record type
|
||||
if (type == KEY || type == CONFIRM) throw new AbortException(false);
|
||||
// Skip unrecognised record type
|
||||
try {
|
||||
readData(len);
|
||||
} catch (IOException e) {
|
||||
throw new AbortException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@ import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.event.GroupVisibilityUpdatedEvent;
|
||||
@@ -37,19 +37,19 @@ import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
/**
|
||||
* An outgoing {@link SyncSession} suitable for duplex transports. The session
|
||||
* offers messages before sending them, keeps its output stream open when there
|
||||
* are no packets to send, and reacts to events that make packets available to
|
||||
* are no records to send, and reacts to events that make records available to
|
||||
* send.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
// Check for retransmittable packets once every 60 seconds
|
||||
// Check for retransmittable records once every 60 seconds
|
||||
private static final int RETX_QUERY_INTERVAL = 60 * 1000;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(DuplexOutgoingSession.class.getName());
|
||||
@@ -67,14 +67,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final Clock clock;
|
||||
private final ContactId contactId;
|
||||
private final int maxLatency, maxIdleTime;
|
||||
private final PacketWriter packetWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
DuplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, Clock clock, ContactId contactId, int maxLatency,
|
||||
int maxIdleTime, PacketWriter packetWriter) {
|
||||
int maxIdleTime, RecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
@@ -82,7 +82,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
this.contactId = contactId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.packetWriter = packetWriter;
|
||||
this.recordWriter = recordWriter;
|
||||
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
|
||||
}
|
||||
|
||||
@@ -91,7 +91,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
public void run() throws IOException {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of packet
|
||||
// Start a query for each type of record
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
@@ -100,33 +100,33 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
long nextKeepalive = now + maxIdleTime;
|
||||
long nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
boolean dataToFlush = true;
|
||||
// Write packets until interrupted
|
||||
// Write records until interrupted
|
||||
try {
|
||||
while (!interrupted) {
|
||||
// Work out how long we should wait for a packet
|
||||
// Work out how long we should wait for a record
|
||||
now = clock.currentTimeMillis();
|
||||
long wait = Math.min(nextKeepalive, nextRetxQuery) - now;
|
||||
if (wait < 0) wait = 0;
|
||||
// Flush any unflushed data if we're going to wait
|
||||
if (wait > 0 && dataToFlush && writerTasks.isEmpty()) {
|
||||
packetWriter.flush();
|
||||
recordWriter.flush();
|
||||
dataToFlush = false;
|
||||
nextKeepalive = now + maxIdleTime;
|
||||
}
|
||||
// Wait for a packet
|
||||
// Wait for a record
|
||||
ThrowingRunnable<IOException> task = writerTasks.poll(wait,
|
||||
MILLISECONDS);
|
||||
if (task == null) {
|
||||
now = clock.currentTimeMillis();
|
||||
if (now >= nextRetxQuery) {
|
||||
// Check for retransmittable packets
|
||||
// Check for retransmittable records
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
nextRetxQuery = now + RETX_QUERY_INTERVAL;
|
||||
}
|
||||
if (now >= nextKeepalive) {
|
||||
// Flush the stream to keep it alive
|
||||
packetWriter.flush();
|
||||
recordWriter.flush();
|
||||
dataToFlush = false;
|
||||
nextKeepalive = now + maxIdleTime;
|
||||
}
|
||||
@@ -137,9 +137,9 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
dataToFlush = true;
|
||||
}
|
||||
}
|
||||
if (dataToFlush) packetWriter.flush();
|
||||
if (dataToFlush) recordWriter.flush();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for a packet to write");
|
||||
LOG.info("Interrupted while waiting for a record to write");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} finally {
|
||||
@@ -215,7 +215,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeAck(ack);
|
||||
recordWriter.writeAck(ack);
|
||||
LOG.info("Sent ack");
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
@@ -232,7 +232,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateRequestedBatch(txn, contactId,
|
||||
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -259,7 +259,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
for (byte[] raw : batch) packetWriter.writeMessage(raw);
|
||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||
LOG.info("Sent batch");
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
@@ -303,7 +303,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeOffer(offer);
|
||||
recordWriter.writeOffer(offer);
|
||||
LOG.info("Sent offer");
|
||||
dbExecutor.execute(new GenerateOffer());
|
||||
}
|
||||
@@ -346,7 +346,7 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeRequest(request);
|
||||
recordWriter.writeRequest(request);
|
||||
LOG.info("Sent request");
|
||||
dbExecutor.execute(new GenerateRequest());
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.PacketReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
|
||||
@@ -42,18 +42,18 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
private final Executor dbExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final ContactId contactId;
|
||||
private final PacketReader packetReader;
|
||||
private final RecordReader recordReader;
|
||||
|
||||
private volatile boolean interrupted = false;
|
||||
|
||||
IncomingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId,
|
||||
PacketReader packetReader) {
|
||||
RecordReader recordReader) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.contactId = contactId;
|
||||
this.packetReader = packetReader;
|
||||
this.recordReader = recordReader;
|
||||
}
|
||||
|
||||
@IoExecutor
|
||||
@@ -61,21 +61,22 @@ class IncomingSession implements SyncSession, EventListener {
|
||||
public void run() throws IOException {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Read packets until interrupted or EOF
|
||||
while (!interrupted && !packetReader.eof()) {
|
||||
if (packetReader.hasAck()) {
|
||||
Ack a = packetReader.readAck();
|
||||
// Read records until interrupted or EOF
|
||||
while (!interrupted && !recordReader.eof()) {
|
||||
if (recordReader.hasAck()) {
|
||||
Ack a = recordReader.readAck();
|
||||
dbExecutor.execute(new ReceiveAck(a));
|
||||
} else if (packetReader.hasMessage()) {
|
||||
Message m = packetReader.readMessage();
|
||||
} else if (recordReader.hasMessage()) {
|
||||
Message m = recordReader.readMessage();
|
||||
dbExecutor.execute(new ReceiveMessage(m));
|
||||
} else if (packetReader.hasOffer()) {
|
||||
Offer o = packetReader.readOffer();
|
||||
} else if (recordReader.hasOffer()) {
|
||||
Offer o = recordReader.readOffer();
|
||||
dbExecutor.execute(new ReceiveOffer(o));
|
||||
} else if (packetReader.hasRequest()) {
|
||||
Request r = packetReader.readRequest();
|
||||
} else if (recordReader.hasRequest()) {
|
||||
Request r = recordReader.readRequest();
|
||||
dbExecutor.execute(new ReceiveRequest(r));
|
||||
} else {
|
||||
// unknown records are ignored in RecordReader#eof()
|
||||
throw new FormatException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,11 +30,15 @@ class MessageFactoryImpl implements MessageFactory {
|
||||
public Message createMessage(GroupId g, long timestamp, byte[] body) {
|
||||
if (body.length > MAX_MESSAGE_BODY_LENGTH)
|
||||
throw new IllegalArgumentException();
|
||||
byte[] timeBytes = new byte[ByteUtils.INT_64_BYTES];
|
||||
ByteUtils.writeUint64(timestamp, timeBytes, 0);
|
||||
byte[] idHash =
|
||||
crypto.hash(MessageId.LABEL, g.getBytes(), timeBytes, body);
|
||||
MessageId id = new MessageId(idHash);
|
||||
byte[] raw = new byte[MESSAGE_HEADER_LENGTH + body.length];
|
||||
System.arraycopy(g.getBytes(), 0, raw, 0, UniqueId.LENGTH);
|
||||
ByteUtils.writeUint64(timestamp, raw, UniqueId.LENGTH);
|
||||
System.arraycopy(body, 0, raw, MESSAGE_HEADER_LENGTH, body.length);
|
||||
MessageId id = new MessageId(crypto.hash(MessageId.LABEL, raw));
|
||||
return new Message(id, g, timestamp, raw);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.PacketReader;
|
||||
import org.briarproject.bramble.api.sync.PacketReaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class PacketReaderFactoryImpl implements PacketReaderFactory {
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
|
||||
@Inject
|
||||
PacketReaderFactoryImpl(CryptoComponent crypto) {
|
||||
this.crypto = crypto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PacketReader createPacketReader(InputStream in) {
|
||||
return new PacketReaderImpl(crypto, in);
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.PacketWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class PacketWriterFactoryImpl implements PacketWriterFactory {
|
||||
|
||||
@Override
|
||||
public PacketWriter createPacketWriter(OutputStream out) {
|
||||
return new PacketWriterImpl(out);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
|
||||
import java.io.InputStream;
|
||||
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
class RecordReaderFactoryImpl implements RecordReaderFactory {
|
||||
|
||||
private final MessageFactory messageFactory;
|
||||
|
||||
@Inject
|
||||
RecordReaderFactoryImpl(MessageFactory messageFactory) {
|
||||
this.messageFactory = messageFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RecordReader createRecordReader(InputStream in) {
|
||||
return new RecordReaderImpl(messageFactory, in);
|
||||
}
|
||||
}
|
||||
@@ -2,14 +2,14 @@ package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.crypto.CryptoComponent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.PacketReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
@@ -20,66 +20,84 @@ import java.util.List;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.MESSAGE;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.MESSAGE;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PacketReaderImpl implements PacketReader {
|
||||
class RecordReaderImpl implements RecordReader {
|
||||
|
||||
private enum State { BUFFER_EMPTY, BUFFER_FULL, EOF }
|
||||
private enum State {BUFFER_EMPTY, BUFFER_FULL, EOF}
|
||||
|
||||
private final CryptoComponent crypto;
|
||||
private final MessageFactory messageFactory;
|
||||
private final InputStream in;
|
||||
private final byte[] header, payload;
|
||||
|
||||
private State state = State.BUFFER_EMPTY;
|
||||
private int payloadLength = 0;
|
||||
|
||||
PacketReaderImpl(CryptoComponent crypto, InputStream in) {
|
||||
this.crypto = crypto;
|
||||
RecordReaderImpl(MessageFactory messageFactory, InputStream in) {
|
||||
this.messageFactory = messageFactory;
|
||||
this.in = in;
|
||||
header = new byte[PACKET_HEADER_LENGTH];
|
||||
payload = new byte[MAX_PACKET_PAYLOAD_LENGTH];
|
||||
header = new byte[RECORD_HEADER_LENGTH];
|
||||
payload = new byte[MAX_RECORD_PAYLOAD_LENGTH];
|
||||
}
|
||||
|
||||
private void readPacket() throws IOException {
|
||||
private void readRecord() throws IOException {
|
||||
if (state != State.BUFFER_EMPTY) throw new IllegalStateException();
|
||||
// Read the header
|
||||
int offset = 0;
|
||||
while (offset < PACKET_HEADER_LENGTH) {
|
||||
int read = in.read(header, offset, PACKET_HEADER_LENGTH - offset);
|
||||
if (read == -1) {
|
||||
if (offset > 0) throw new FormatException();
|
||||
state = State.EOF;
|
||||
while (true) {
|
||||
// Read the header
|
||||
int offset = 0;
|
||||
while (offset < RECORD_HEADER_LENGTH) {
|
||||
int read =
|
||||
in.read(header, offset, RECORD_HEADER_LENGTH - offset);
|
||||
if (read == -1) {
|
||||
if (offset > 0) throw new FormatException();
|
||||
state = State.EOF;
|
||||
return;
|
||||
}
|
||||
offset += read;
|
||||
}
|
||||
byte version = header[0], type = header[1];
|
||||
payloadLength = ByteUtils.readUint16(header, 2);
|
||||
// Check the protocol version
|
||||
if (version != PROTOCOL_VERSION) throw new FormatException();
|
||||
// Check the payload length
|
||||
if (payloadLength > MAX_RECORD_PAYLOAD_LENGTH)
|
||||
throw new FormatException();
|
||||
// Read the payload
|
||||
offset = 0;
|
||||
while (offset < payloadLength) {
|
||||
int read = in.read(payload, offset, payloadLength - offset);
|
||||
if (read == -1) throw new FormatException();
|
||||
offset += read;
|
||||
}
|
||||
state = State.BUFFER_FULL;
|
||||
// Return if this is a known record type, otherwise continue
|
||||
if (type == ACK || type == MESSAGE || type == OFFER ||
|
||||
type == REQUEST) {
|
||||
return;
|
||||
}
|
||||
offset += read;
|
||||
}
|
||||
// Check the protocol version
|
||||
if (header[0] != PROTOCOL_VERSION) throw new FormatException();
|
||||
// Read the payload length
|
||||
payloadLength = ByteUtils.readUint16(header, 2);
|
||||
if (payloadLength > MAX_PACKET_PAYLOAD_LENGTH) throw new FormatException();
|
||||
// Read the payload
|
||||
offset = 0;
|
||||
while (offset < payloadLength) {
|
||||
int read = in.read(payload, offset, payloadLength - offset);
|
||||
if (read == -1) throw new FormatException();
|
||||
offset += read;
|
||||
}
|
||||
state = State.BUFFER_FULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if there's another record available or false if we've
|
||||
* reached the end of the input stream.
|
||||
* <p>
|
||||
* If a record is available, it's been read into the buffer by the time
|
||||
* eof() returns, so the method that called eof() can access the record
|
||||
* from the buffer, for example to check its type or extract its payload.
|
||||
*/
|
||||
@Override
|
||||
public boolean eof() throws IOException {
|
||||
if (state == State.BUFFER_EMPTY) readPacket();
|
||||
if (state == State.BUFFER_EMPTY) readRecord();
|
||||
if (state == State.BUFFER_EMPTY) throw new IllegalStateException();
|
||||
return state == State.EOF;
|
||||
}
|
||||
@@ -124,13 +142,12 @@ class PacketReaderImpl implements PacketReader {
|
||||
// Timestamp
|
||||
long timestamp = ByteUtils.readUint64(payload, UniqueId.LENGTH);
|
||||
if (timestamp < 0) throw new FormatException();
|
||||
// Raw message
|
||||
byte[] raw = new byte[payloadLength];
|
||||
System.arraycopy(payload, 0, raw, 0, payloadLength);
|
||||
// Body
|
||||
byte[] body = new byte[payloadLength - MESSAGE_HEADER_LENGTH];
|
||||
System.arraycopy(payload, MESSAGE_HEADER_LENGTH, body, 0,
|
||||
payloadLength - MESSAGE_HEADER_LENGTH);
|
||||
state = State.BUFFER_EMPTY;
|
||||
// Message ID
|
||||
MessageId messageId = new MessageId(crypto.hash(MessageId.LABEL, raw));
|
||||
return new Message(messageId, groupId, timestamp, raw);
|
||||
return messageFactory.createMessage(groupId, timestamp, body);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -154,4 +171,5 @@ class PacketReaderImpl implements PacketReader {
|
||||
if (!hasRequest()) throw new FormatException();
|
||||
return new Request(readMessageIds());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
|
||||
import java.io.OutputStream;
|
||||
|
||||
@NotNullByDefault
|
||||
class RecordWriterFactoryImpl implements RecordWriterFactory {
|
||||
|
||||
@Override
|
||||
public RecordWriter createRecordWriter(OutputStream out) {
|
||||
return new RecordWriterImpl(out);
|
||||
}
|
||||
}
|
||||
@@ -4,8 +4,8 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.PacketTypes;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordTypes;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
|
||||
@@ -15,30 +15,30 @@ import java.io.OutputStream;
|
||||
|
||||
import javax.annotation.concurrent.NotThreadSafe;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
|
||||
@NotThreadSafe
|
||||
@NotNullByDefault
|
||||
class PacketWriterImpl implements PacketWriter {
|
||||
class RecordWriterImpl implements RecordWriter {
|
||||
|
||||
private final OutputStream out;
|
||||
private final byte[] header;
|
||||
private final ByteArrayOutputStream payload;
|
||||
|
||||
PacketWriterImpl(OutputStream out) {
|
||||
RecordWriterImpl(OutputStream out) {
|
||||
this.out = out;
|
||||
header = new byte[PACKET_HEADER_LENGTH];
|
||||
header = new byte[RECORD_HEADER_LENGTH];
|
||||
header[0] = PROTOCOL_VERSION;
|
||||
payload = new ByteArrayOutputStream(MAX_PACKET_PAYLOAD_LENGTH);
|
||||
payload = new ByteArrayOutputStream(MAX_RECORD_PAYLOAD_LENGTH);
|
||||
}
|
||||
|
||||
private void writePacket(byte packetType) throws IOException {
|
||||
header[1] = packetType;
|
||||
private void writeRecord(byte recordType) throws IOException {
|
||||
header[1] = recordType;
|
||||
ByteUtils.writeUint16(payload.size(), header, 2);
|
||||
out.write(header);
|
||||
payload.writeTo(out);
|
||||
@@ -49,12 +49,12 @@ class PacketWriterImpl implements PacketWriter {
|
||||
public void writeAck(Ack a) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : a.getMessageIds()) payload.write(m.getBytes());
|
||||
writePacket(ACK);
|
||||
writeRecord(ACK);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeMessage(byte[] raw) throws IOException {
|
||||
header[1] = PacketTypes.MESSAGE;
|
||||
header[1] = RecordTypes.MESSAGE;
|
||||
ByteUtils.writeUint16(raw.length, header, 2);
|
||||
out.write(header);
|
||||
out.write(raw);
|
||||
@@ -64,14 +64,14 @@ class PacketWriterImpl implements PacketWriter {
|
||||
public void writeOffer(Offer o) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : o.getMessageIds()) payload.write(m.getBytes());
|
||||
writePacket(OFFER);
|
||||
writeRecord(OFFER);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeRequest(Request r) throws IOException {
|
||||
if (payload.size() != 0) throw new IllegalStateException();
|
||||
for (MessageId m : r.getMessageIds()) payload.write(m.getBytes());
|
||||
writePacket(REQUEST);
|
||||
writeRecord(REQUEST);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -13,7 +13,7 @@ import org.briarproject.bramble.api.lifecycle.IoExecutor;
|
||||
import org.briarproject.bramble.api.lifecycle.event.ShutdownEvent;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -29,12 +29,12 @@ import javax.annotation.concurrent.ThreadSafe;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
|
||||
/**
|
||||
* An outgoing {@link SyncSession} suitable for simplex transports. The session
|
||||
* sends messages without offering them first, and closes its output stream
|
||||
* when there are no more packets to send.
|
||||
* when there are no more records to send.
|
||||
*/
|
||||
@ThreadSafe
|
||||
@NotNullByDefault
|
||||
@@ -55,7 +55,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
private final EventBus eventBus;
|
||||
private final ContactId contactId;
|
||||
private final int maxLatency;
|
||||
private final PacketWriter packetWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
private final AtomicInteger outstandingQueries;
|
||||
private final BlockingQueue<ThrowingRunnable<IOException>> writerTasks;
|
||||
|
||||
@@ -63,14 +63,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
|
||||
SimplexOutgoingSession(DatabaseComponent db, Executor dbExecutor,
|
||||
EventBus eventBus, ContactId contactId,
|
||||
int maxLatency, PacketWriter packetWriter) {
|
||||
int maxLatency, RecordWriter recordWriter) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.contactId = contactId;
|
||||
this.maxLatency = maxLatency;
|
||||
this.packetWriter = packetWriter;
|
||||
outstandingQueries = new AtomicInteger(2); // One per type of packet
|
||||
this.recordWriter = recordWriter;
|
||||
outstandingQueries = new AtomicInteger(2); // One per type of record
|
||||
writerTasks = new LinkedBlockingQueue<ThrowingRunnable<IOException>>();
|
||||
}
|
||||
|
||||
@@ -79,19 +79,19 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
public void run() throws IOException {
|
||||
eventBus.addListener(this);
|
||||
try {
|
||||
// Start a query for each type of packet
|
||||
// Start a query for each type of record
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
// Write packets until interrupted or no more packets to write
|
||||
// Write records until interrupted or no more records to write
|
||||
try {
|
||||
while (!interrupted) {
|
||||
ThrowingRunnable<IOException> task = writerTasks.take();
|
||||
if (task == CLOSE) break;
|
||||
task.run();
|
||||
}
|
||||
packetWriter.flush();
|
||||
recordWriter.flush();
|
||||
} catch (InterruptedException e) {
|
||||
LOG.info("Interrupted while waiting for a packet to write");
|
||||
LOG.info("Interrupted while waiting for a record to write");
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
} finally {
|
||||
@@ -157,7 +157,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
packetWriter.writeAck(ack);
|
||||
recordWriter.writeAck(ack);
|
||||
LOG.info("Sent ack");
|
||||
dbExecutor.execute(new GenerateAck());
|
||||
}
|
||||
@@ -174,7 +174,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
Transaction txn = db.startTransaction(false);
|
||||
try {
|
||||
b = db.generateBatch(txn, contactId,
|
||||
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
|
||||
MAX_RECORD_PAYLOAD_LENGTH, maxLatency);
|
||||
db.commitTransaction(txn);
|
||||
} finally {
|
||||
db.endTransaction(txn);
|
||||
@@ -202,7 +202,7 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
|
||||
@Override
|
||||
public void run() throws IOException {
|
||||
if (interrupted) return;
|
||||
for (byte[] raw : batch) packetWriter.writeMessage(raw);
|
||||
for (byte[] raw : batch) recordWriter.writeMessage(raw);
|
||||
LOG.info("Sent batch");
|
||||
dbExecutor.execute(new GenerateBatch());
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.sync.GroupFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -40,23 +40,24 @@ public class SyncModule {
|
||||
}
|
||||
|
||||
@Provides
|
||||
PacketReaderFactory providePacketReaderFactory(CryptoComponent crypto) {
|
||||
return new PacketReaderFactoryImpl(crypto);
|
||||
RecordReaderFactory provideRecordReaderFactory(
|
||||
RecordReaderFactoryImpl recordReaderFactory) {
|
||||
return recordReaderFactory;
|
||||
}
|
||||
|
||||
@Provides
|
||||
PacketWriterFactory providePacketWriterFactory() {
|
||||
return new PacketWriterFactoryImpl();
|
||||
RecordWriterFactory provideRecordWriterFactory() {
|
||||
return new RecordWriterFactoryImpl();
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
SyncSessionFactory provideSyncSessionFactory(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||
Clock clock, PacketReaderFactory packetReaderFactory,
|
||||
PacketWriterFactory packetWriterFactory) {
|
||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
return new SyncSessionFactoryImpl(db, dbExecutor, eventBus, clock,
|
||||
packetReaderFactory, packetWriterFactory);
|
||||
recordReaderFactory, recordWriterFactory);
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
||||
@@ -5,10 +5,10 @@ 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.sync.PacketReader;
|
||||
import org.briarproject.bramble.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.SyncSession;
|
||||
import org.briarproject.bramble.api.sync.SyncSessionFactory;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
@@ -28,41 +28,41 @@ class SyncSessionFactoryImpl implements SyncSessionFactory {
|
||||
private final Executor dbExecutor;
|
||||
private final EventBus eventBus;
|
||||
private final Clock clock;
|
||||
private final PacketReaderFactory packetReaderFactory;
|
||||
private final PacketWriterFactory packetWriterFactory;
|
||||
private final RecordReaderFactory recordReaderFactory;
|
||||
private final RecordWriterFactory recordWriterFactory;
|
||||
|
||||
@Inject
|
||||
SyncSessionFactoryImpl(DatabaseComponent db,
|
||||
@DatabaseExecutor Executor dbExecutor, EventBus eventBus,
|
||||
Clock clock, PacketReaderFactory packetReaderFactory,
|
||||
PacketWriterFactory packetWriterFactory) {
|
||||
Clock clock, RecordReaderFactory recordReaderFactory,
|
||||
RecordWriterFactory recordWriterFactory) {
|
||||
this.db = db;
|
||||
this.dbExecutor = dbExecutor;
|
||||
this.eventBus = eventBus;
|
||||
this.clock = clock;
|
||||
this.packetReaderFactory = packetReaderFactory;
|
||||
this.packetWriterFactory = packetWriterFactory;
|
||||
this.recordReaderFactory = recordReaderFactory;
|
||||
this.recordWriterFactory = recordWriterFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createIncomingSession(ContactId c, InputStream in) {
|
||||
PacketReader packetReader = packetReaderFactory.createPacketReader(in);
|
||||
return new IncomingSession(db, dbExecutor, eventBus, c, packetReader);
|
||||
RecordReader recordReader = recordReaderFactory.createRecordReader(in);
|
||||
return new IncomingSession(db, dbExecutor, eventBus, c, recordReader);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createSimplexOutgoingSession(ContactId c,
|
||||
int maxLatency, OutputStream out) {
|
||||
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
||||
return new SimplexOutgoingSession(db, dbExecutor, eventBus, c,
|
||||
maxLatency, packetWriter);
|
||||
maxLatency, recordWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SyncSession createDuplexOutgoingSession(ContactId c, int maxLatency,
|
||||
int maxIdleTime, OutputStream out) {
|
||||
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(out);
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(out);
|
||||
return new DuplexOutgoingSession(db, dbExecutor, eventBus, clock, c,
|
||||
maxLatency, maxIdleTime, packetWriter);
|
||||
maxLatency, maxIdleTime, recordWriter);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +63,7 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
|
||||
@@ -95,7 +96,7 @@ public class DatabaseComponentImplTest extends BrambleTestCase {
|
||||
public DatabaseComponentImplTest() {
|
||||
clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
byte[] descriptor = new byte[0];
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
|
||||
@@ -47,6 +47,7 @@ import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_K
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.VISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.INVALID;
|
||||
@@ -84,7 +85,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
public H2DatabaseTest() throws Exception {
|
||||
groupId = new GroupId(TestUtils.getRandomId());
|
||||
clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
byte[] descriptor = new byte[0];
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
group = new Group(groupId, clientId, descriptor);
|
||||
AuthorId authorId = new AuthorId(TestUtils.getRandomId());
|
||||
author = new Author(authorId, "Alice", new byte[MAX_PUBLIC_KEY_LENGTH]);
|
||||
@@ -1316,7 +1317,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Add a second group
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
Group group1 = new Group(groupId1, clientId,
|
||||
TestUtils.getRandomBytes(42));
|
||||
TestUtils.getRandomBytes(MAX_GROUP_DESCRIPTOR_LENGTH));
|
||||
db.addGroup(txn, group1);
|
||||
|
||||
// Add a message to the second group
|
||||
|
||||
@@ -92,7 +92,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
oneOf(callbacks).connectionWaiting();
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(BOB_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Alice verifies Bob's public key
|
||||
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
|
||||
@@ -152,7 +152,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
// Bob receives Alice's public key
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(ALICE_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Bob verifies Alice's public key
|
||||
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
|
||||
@@ -213,7 +213,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
oneOf(callbacks).connectionWaiting();
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(BAD_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Alice verifies Bob's public key
|
||||
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
|
||||
@@ -250,7 +250,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
// Bob receives a bad public key
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(BAD_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Bob verifies Alice's public key
|
||||
oneOf(crypto).deriveKeyCommitment(BAD_PUBKEY);
|
||||
@@ -296,7 +296,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
oneOf(callbacks).connectionWaiting();
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(BOB_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Alice verifies Bob's public key
|
||||
oneOf(crypto).deriveKeyCommitment(BOB_PUBKEY);
|
||||
@@ -357,7 +357,7 @@ public class KeyAgreementProtocolTest extends BrambleTestCase {
|
||||
// Bob receives Alice's public key
|
||||
oneOf(transport).receiveKey();
|
||||
will(returnValue(ALICE_PUBKEY));
|
||||
oneOf(callbacks).initialPacketReceived();
|
||||
oneOf(callbacks).initialRecordReceived();
|
||||
|
||||
// Bob verifies Alice's public key
|
||||
oneOf(crypto).deriveKeyCommitment(ALICE_PUBKEY);
|
||||
|
||||
@@ -0,0 +1,251 @@
|
||||
package org.briarproject.bramble.keyagreement;
|
||||
|
||||
import org.briarproject.bramble.api.keyagreement.KeyAgreementConnection;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionReader;
|
||||
import org.briarproject.bramble.api.plugin.TransportConnectionWriter;
|
||||
import org.briarproject.bramble.api.plugin.TransportId;
|
||||
import org.briarproject.bramble.api.plugin.duplex.DuplexTransportConnection;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.jmock.Expectations;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.keyagreement.KeyAgreementConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.ABORT;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.CONFIRM;
|
||||
import static org.briarproject.bramble.api.keyagreement.RecordTypes.KEY;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class KeyAgreementTransportTest extends BrambleMockTestCase {
|
||||
|
||||
private final DuplexTransportConnection duplexTransportConnection =
|
||||
context.mock(DuplexTransportConnection.class);
|
||||
private final TransportConnectionReader transportConnectionReader =
|
||||
context.mock(TransportConnectionReader.class);
|
||||
private final TransportConnectionWriter transportConnectionWriter =
|
||||
context.mock(TransportConnectionWriter.class);
|
||||
|
||||
private final TransportId transportId = new TransportId("test");
|
||||
private final KeyAgreementConnection keyAgreementConnection =
|
||||
new KeyAgreementConnection(duplexTransportConnection, transportId);
|
||||
|
||||
private ByteArrayInputStream inputStream;
|
||||
private ByteArrayOutputStream outputStream;
|
||||
private KeyAgreementTransport kat;
|
||||
|
||||
@Test
|
||||
public void testSendKey() throws Exception {
|
||||
setup(new byte[0]);
|
||||
byte[] key = TestUtils.getRandomBytes(123);
|
||||
kat.sendKey(key);
|
||||
assertRecordSent(KEY, key);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendConfirm() throws Exception {
|
||||
setup(new byte[0]);
|
||||
byte[] confirm = TestUtils.getRandomBytes(123);
|
||||
kat.sendConfirm(confirm);
|
||||
assertRecordSent(CONFIRM, confirm);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbortWithException() throws Exception {
|
||||
setup(new byte[0]);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(true, true);
|
||||
oneOf(transportConnectionWriter).dispose(true);
|
||||
}});
|
||||
kat.sendAbort(true);
|
||||
assertRecordSent(ABORT, new byte[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSendAbortWithoutException() throws Exception {
|
||||
setup(new byte[0]);
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(transportConnectionReader).dispose(false, true);
|
||||
oneOf(transportConnectionWriter).dispose(false);
|
||||
}});
|
||||
kat.sendAbort(false);
|
||||
assertRecordSent(ABORT, new byte[0]);
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup(new byte[0]);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfHeaderIsTooShort()
|
||||
throws Exception {
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = KEY;
|
||||
setup(input);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfPayloadIsTooShort()
|
||||
throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = KEY;
|
||||
ByteUtils.writeUint16(payloadLength, input, 2);
|
||||
setup(input);
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
setup(createRecord((byte) (PROTOCOL_VERSION + 1), KEY, new byte[123]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfAbortIsReceived()
|
||||
throws Exception {
|
||||
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfConfirmIsReceived()
|
||||
throws Exception {
|
||||
setup(createRecord(PROTOCOL_VERSION, CONFIRM, new byte[123]));
|
||||
kat.receiveKey();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveKeySkipsUnrecognisedRecordTypes() throws Exception {
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
|
||||
new byte[0]);
|
||||
byte[] payload = TestUtils.getRandomBytes(123);
|
||||
byte[] key = createRecord(PROTOCOL_VERSION, KEY, payload);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(key);
|
||||
setup(input.toByteArray());
|
||||
assertArrayEquals(payload, kat.receiveKey());
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfAtEndOfStream()
|
||||
throws Exception {
|
||||
setup(new byte[0]);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfHeaderIsTooShort()
|
||||
throws Exception {
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = CONFIRM;
|
||||
setup(input);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfPayloadIsTooShort()
|
||||
throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] input = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
input[0] = PROTOCOL_VERSION;
|
||||
input[1] = CONFIRM;
|
||||
ByteUtils.writeUint16(payloadLength, input, 2);
|
||||
setup(input);
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
setup(createRecord((byte) (PROTOCOL_VERSION + 1), CONFIRM,
|
||||
new byte[123]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveConfirmThrowsExceptionIfAbortIsReceived()
|
||||
throws Exception {
|
||||
setup(createRecord(PROTOCOL_VERSION, ABORT, new byte[0]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test(expected = AbortException.class)
|
||||
public void testReceiveKeyThrowsExceptionIfKeyIsReceived()
|
||||
throws Exception {
|
||||
setup(createRecord(PROTOCOL_VERSION, KEY, new byte[123]));
|
||||
kat.receiveConfirm();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReceiveConfirmSkipsUnrecognisedRecordTypes()
|
||||
throws Exception {
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (ABORT + 2),
|
||||
new byte[0]);
|
||||
byte[] payload = TestUtils.getRandomBytes(123);
|
||||
byte[] confirm = createRecord(PROTOCOL_VERSION, CONFIRM, payload);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(confirm);
|
||||
setup(input.toByteArray());
|
||||
assertArrayEquals(payload, kat.receiveConfirm());
|
||||
}
|
||||
|
||||
private void setup(byte[] input) throws Exception {
|
||||
inputStream = new ByteArrayInputStream(input);
|
||||
outputStream = new ByteArrayOutputStream();
|
||||
context.checking(new Expectations() {{
|
||||
allowing(duplexTransportConnection).getReader();
|
||||
will(returnValue(transportConnectionReader));
|
||||
allowing(transportConnectionReader).getInputStream();
|
||||
will(returnValue(inputStream));
|
||||
allowing(duplexTransportConnection).getWriter();
|
||||
will(returnValue(transportConnectionWriter));
|
||||
allowing(transportConnectionWriter).getOutputStream();
|
||||
will(returnValue(outputStream));
|
||||
}});
|
||||
kat = new KeyAgreementTransport(keyAgreementConnection);
|
||||
}
|
||||
|
||||
private void assertRecordSent(byte expectedType, byte[] expectedPayload) {
|
||||
byte[] output = outputStream.toByteArray();
|
||||
assertEquals(RECORD_HEADER_LENGTH + expectedPayload.length,
|
||||
output.length);
|
||||
assertEquals(PROTOCOL_VERSION, output[0]);
|
||||
assertEquals(expectedType, output[1]);
|
||||
assertEquals(expectedPayload.length, ByteUtils.readUint16(output, 2));
|
||||
byte[] payload = new byte[output.length - RECORD_HEADER_LENGTH];
|
||||
System.arraycopy(output, RECORD_HEADER_LENGTH, payload, 0,
|
||||
payload.length);
|
||||
assertArrayEquals(expectedPayload, payload);
|
||||
}
|
||||
|
||||
private byte[] createRecord(byte version, byte type, byte[] payload) {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
|
||||
b[0] = version;
|
||||
b[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, b, 2);
|
||||
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
|
||||
return b;
|
||||
}
|
||||
}
|
||||
@@ -1,162 +0,0 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.PacketTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_PACKET_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PACKET_HEADER_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class PacketReaderImplTest extends BrambleTestCase {
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
|
||||
byte[] b = createAck(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testEmptyAck() throws Exception {
|
||||
byte[] b = createEmptyAck();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
|
||||
byte[] b = createOffer(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
byte[] b = createOffer(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testEmptyOffer() throws Exception {
|
||||
byte[] b = createEmptyOffer();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
|
||||
byte[] b = createRequest(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
byte[] b = createRequest(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testEmptyRequest() throws Exception {
|
||||
byte[] b = createEmptyRequest();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
PacketReaderImpl reader = new PacketReaderImpl(null, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
private byte[] createAck(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(new byte[PACKET_HEADER_LENGTH]);
|
||||
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
|
||||
+ MAX_PACKET_PAYLOAD_LENGTH) {
|
||||
out.write(TestUtils.getRandomId());
|
||||
}
|
||||
if (tooBig) out.write(TestUtils.getRandomId());
|
||||
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
|
||||
MAX_PACKET_PAYLOAD_LENGTH);
|
||||
byte[] packet = out.toByteArray();
|
||||
packet[1] = ACK;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private byte[] createEmptyAck() throws Exception {
|
||||
byte[] packet = new byte[PACKET_HEADER_LENGTH];
|
||||
packet[1] = ACK;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private byte[] createOffer(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(new byte[PACKET_HEADER_LENGTH]);
|
||||
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
|
||||
+ MAX_PACKET_PAYLOAD_LENGTH) {
|
||||
out.write(TestUtils.getRandomId());
|
||||
}
|
||||
if (tooBig) out.write(TestUtils.getRandomId());
|
||||
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
|
||||
MAX_PACKET_PAYLOAD_LENGTH);
|
||||
byte[] packet = out.toByteArray();
|
||||
packet[1] = OFFER;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private byte[] createEmptyOffer() throws Exception {
|
||||
byte[] packet = new byte[PACKET_HEADER_LENGTH];
|
||||
packet[1] = OFFER;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private byte[] createRequest(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||
out.write(new byte[PACKET_HEADER_LENGTH]);
|
||||
while (out.size() + UniqueId.LENGTH <= PACKET_HEADER_LENGTH
|
||||
+ MAX_PACKET_PAYLOAD_LENGTH) {
|
||||
out.write(TestUtils.getRandomId());
|
||||
}
|
||||
if (tooBig) out.write(TestUtils.getRandomId());
|
||||
assertEquals(tooBig, out.size() > PACKET_HEADER_LENGTH +
|
||||
MAX_PACKET_PAYLOAD_LENGTH);
|
||||
byte[] packet = out.toByteArray();
|
||||
packet[1] = REQUEST;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
|
||||
private byte[] createEmptyRequest() throws Exception {
|
||||
byte[] packet = new byte[PACKET_HEADER_LENGTH];
|
||||
packet[1] = REQUEST;
|
||||
ByteUtils.writeUint16(packet.length - PACKET_HEADER_LENGTH, packet, 2);
|
||||
return packet;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
package org.briarproject.bramble.sync;
|
||||
|
||||
import org.briarproject.bramble.api.FormatException;
|
||||
import org.briarproject.bramble.api.UniqueId;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.ByteUtils;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.ACK;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.OFFER;
|
||||
import static org.briarproject.bramble.api.sync.RecordTypes.REQUEST;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_MESSAGE_IDS;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_RECORD_PAYLOAD_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.PROTOCOL_VERSION;
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.RECORD_HEADER_LENGTH;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RecordReaderImplTest extends BrambleMockTestCase {
|
||||
|
||||
private final MessageFactory messageFactory =
|
||||
context.mock(MessageFactory.class);
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsTooLarge() throws Exception {
|
||||
byte[] b = createAck(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfAckIsMaximumSize() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfAckIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyAck();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readAck();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsTooLarge() throws Exception {
|
||||
byte[] b = createOffer(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfOfferIsMaximumSize() throws Exception {
|
||||
byte[] b = createOffer(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfOfferIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyOffer();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readOffer();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsTooLarge() throws Exception {
|
||||
byte[] b = createRequest(true);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testNoFormatExceptionIfRequestIsMaximumSize() throws Exception {
|
||||
byte[] b = createRequest(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testFormatExceptionIfRequestIsEmpty() throws Exception {
|
||||
byte[] b = createEmptyRequest();
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.readRequest();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsTrueWhenAtEndOfStream() throws Exception {
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(new byte[0]);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertTrue(reader.eof());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testEofReturnsFalseWhenNotAtEndOfStream() throws Exception {
|
||||
byte[] b = createAck(false);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertFalse(reader.eof());
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfHeaderIsTooShort() throws Exception {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH - 1];
|
||||
b[0] = PROTOCOL_VERSION;
|
||||
b[1] = ACK;
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfPayloadIsTooShort() throws Exception {
|
||||
int payloadLength = 123;
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payloadLength - 1];
|
||||
b[0] = PROTOCOL_VERSION;
|
||||
b[1] = ACK;
|
||||
ByteUtils.writeUint16(payloadLength, b, 2);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfProtocolVersionIsUnrecognised()
|
||||
throws Exception {
|
||||
byte version = (byte) (PROTOCOL_VERSION + 1);
|
||||
byte[] b = createRecord(version, ACK, new byte[0]);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test(expected = FormatException.class)
|
||||
public void testThrowsExceptionIfPayloadIsTooLong() throws Exception {
|
||||
byte[] payload = new byte[MAX_RECORD_PAYLOAD_LENGTH + 1];
|
||||
byte[] b = createRecord(PROTOCOL_VERSION, ACK, payload);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(b);
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
reader.eof();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSkipsUnrecognisedRecordTypes() throws Exception {
|
||||
byte[] skip1 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 1),
|
||||
new byte[123]);
|
||||
byte[] skip2 = createRecord(PROTOCOL_VERSION, (byte) (REQUEST + 2),
|
||||
new byte[0]);
|
||||
byte[] ack = createAck(false);
|
||||
ByteArrayOutputStream input = new ByteArrayOutputStream();
|
||||
input.write(skip1);
|
||||
input.write(skip2);
|
||||
input.write(ack);
|
||||
ByteArrayInputStream in = new ByteArrayInputStream(input.toByteArray());
|
||||
RecordReaderImpl reader = new RecordReaderImpl(messageFactory, in);
|
||||
assertTrue(reader.hasAck());
|
||||
Ack a = reader.readAck();
|
||||
assertEquals(MAX_MESSAGE_IDS, a.getMessageIds().size());
|
||||
}
|
||||
|
||||
private byte[] createAck(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, ACK, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyAck() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, ACK, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createOffer(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, OFFER, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyOffer() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, OFFER, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createRequest(boolean tooBig) throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, REQUEST, createPayload(tooBig));
|
||||
}
|
||||
|
||||
private byte[] createEmptyRequest() throws Exception {
|
||||
return createRecord(PROTOCOL_VERSION, REQUEST, new byte[0]);
|
||||
}
|
||||
|
||||
private byte[] createRecord(byte version, byte type, byte[] payload) {
|
||||
byte[] b = new byte[RECORD_HEADER_LENGTH + payload.length];
|
||||
b[0] = version;
|
||||
b[1] = type;
|
||||
ByteUtils.writeUint16(payload.length, b, 2);
|
||||
System.arraycopy(payload, 0, b, RECORD_HEADER_LENGTH, payload.length);
|
||||
return b;
|
||||
}
|
||||
|
||||
private byte[] createPayload(boolean tooBig) throws Exception {
|
||||
ByteArrayOutputStream payload = new ByteArrayOutputStream();
|
||||
while (payload.size() + UniqueId.LENGTH <= MAX_RECORD_PAYLOAD_LENGTH) {
|
||||
payload.write(TestUtils.getRandomId());
|
||||
}
|
||||
if (tooBig) payload.write(TestUtils.getRandomId());
|
||||
assertEquals(tooBig, payload.size() > MAX_RECORD_PAYLOAD_LENGTH);
|
||||
return payload.toByteArray();
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,10 @@ import org.briarproject.bramble.api.db.Transaction;
|
||||
import org.briarproject.bramble.api.event.EventBus;
|
||||
import org.briarproject.bramble.api.sync.Ack;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.ImmediateExecutor;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.junit.Test;
|
||||
@@ -29,14 +29,14 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
private final ContactId contactId;
|
||||
private final MessageId messageId;
|
||||
private final int maxLatency;
|
||||
private final PacketWriter packetWriter;
|
||||
private final RecordWriter recordWriter;
|
||||
|
||||
public SimplexOutgoingSessionTest() {
|
||||
context = new Mockery();
|
||||
db = context.mock(DatabaseComponent.class);
|
||||
dbExecutor = new ImmediateExecutor();
|
||||
eventBus = context.mock(EventBus.class);
|
||||
packetWriter = context.mock(PacketWriter.class);
|
||||
recordWriter = context.mock(RecordWriter.class);
|
||||
contactId = new ContactId(234);
|
||||
messageId = new MessageId(TestUtils.getRandomId());
|
||||
maxLatency = Integer.MAX_VALUE;
|
||||
@@ -45,7 +45,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
@Test
|
||||
public void testNothingToSend() throws Exception {
|
||||
final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
dbExecutor, eventBus, contactId, maxLatency, packetWriter);
|
||||
dbExecutor, eventBus, contactId, maxLatency, recordWriter);
|
||||
final Transaction noAckTxn = new Transaction(null, false);
|
||||
final Transaction noMsgTxn = new Transaction(null, false);
|
||||
|
||||
@@ -68,7 +68,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
oneOf(db).commitTransaction(noMsgTxn);
|
||||
oneOf(db).endTransaction(noMsgTxn);
|
||||
// Flush the output stream
|
||||
oneOf(packetWriter).flush();
|
||||
oneOf(recordWriter).flush();
|
||||
// Remove listener
|
||||
oneOf(eventBus).removeListener(session);
|
||||
}});
|
||||
@@ -83,7 +83,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
final Ack ack = new Ack(Collections.singletonList(messageId));
|
||||
final byte[] raw = new byte[1234];
|
||||
final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
|
||||
dbExecutor, eventBus, contactId, maxLatency, packetWriter);
|
||||
dbExecutor, eventBus, contactId, maxLatency, recordWriter);
|
||||
final Transaction ackTxn = new Transaction(null, false);
|
||||
final Transaction noAckTxn = new Transaction(null, false);
|
||||
final Transaction msgTxn = new Transaction(null, false);
|
||||
@@ -99,7 +99,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
will(returnValue(ack));
|
||||
oneOf(db).commitTransaction(ackTxn);
|
||||
oneOf(db).endTransaction(ackTxn);
|
||||
oneOf(packetWriter).writeAck(ack);
|
||||
oneOf(recordWriter).writeAck(ack);
|
||||
// One message to send
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(msgTxn));
|
||||
@@ -108,7 +108,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
will(returnValue(Arrays.asList(raw)));
|
||||
oneOf(db).commitTransaction(msgTxn);
|
||||
oneOf(db).endTransaction(msgTxn);
|
||||
oneOf(packetWriter).writeMessage(raw);
|
||||
oneOf(recordWriter).writeMessage(raw);
|
||||
// No more acks
|
||||
oneOf(db).startTransaction(false);
|
||||
will(returnValue(noAckTxn));
|
||||
@@ -125,7 +125,7 @@ public class SimplexOutgoingSessionTest extends BrambleTestCase {
|
||||
oneOf(db).commitTransaction(noMsgTxn);
|
||||
oneOf(db).endTransaction(noMsgTxn);
|
||||
// Flush the output stream
|
||||
oneOf(packetWriter).flush();
|
||||
oneOf(recordWriter).flush();
|
||||
// Remove listener
|
||||
oneOf(eventBus).removeListener(session);
|
||||
}});
|
||||
|
||||
@@ -12,10 +12,10 @@ import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageFactory;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.Offer;
|
||||
import org.briarproject.bramble.api.sync.PacketReader;
|
||||
import org.briarproject.bramble.api.sync.PacketReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.PacketWriter;
|
||||
import org.briarproject.bramble.api.sync.PacketWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordReader;
|
||||
import org.briarproject.bramble.api.sync.RecordReaderFactory;
|
||||
import org.briarproject.bramble.api.sync.RecordWriter;
|
||||
import org.briarproject.bramble.api.sync.RecordWriterFactory;
|
||||
import org.briarproject.bramble.api.sync.Request;
|
||||
import org.briarproject.bramble.api.transport.StreamContext;
|
||||
import org.briarproject.bramble.api.transport.StreamReaderFactory;
|
||||
@@ -33,6 +33,7 @@ import java.util.Collection;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static org.briarproject.bramble.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
|
||||
import static org.briarproject.bramble.api.transport.TransportConstants.TAG_LENGTH;
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
@@ -50,9 +51,9 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
@Inject
|
||||
StreamWriterFactory streamWriterFactory;
|
||||
@Inject
|
||||
PacketReaderFactory packetReaderFactory;
|
||||
RecordReaderFactory recordReaderFactory;
|
||||
@Inject
|
||||
PacketWriterFactory packetWriterFactory;
|
||||
RecordWriterFactory recordWriterFactory;
|
||||
@Inject
|
||||
CryptoComponent crypto;
|
||||
|
||||
@@ -77,7 +78,7 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
streamNumber = 123;
|
||||
// Create a group
|
||||
ClientId clientId = new ClientId(TestUtils.getRandomString(5));
|
||||
byte[] descriptor = new byte[0];
|
||||
byte[] descriptor = new byte[MAX_GROUP_DESCRIPTOR_LENGTH];
|
||||
Group group = groupFactory.createGroup(clientId, descriptor);
|
||||
// Add two messages to the group
|
||||
long timestamp = System.currentTimeMillis();
|
||||
@@ -98,14 +99,14 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
headerKey, streamNumber);
|
||||
OutputStream streamWriter = streamWriterFactory.createStreamWriter(out,
|
||||
ctx);
|
||||
PacketWriter packetWriter = packetWriterFactory.createPacketWriter(
|
||||
RecordWriter recordWriter = recordWriterFactory.createRecordWriter(
|
||||
streamWriter);
|
||||
|
||||
packetWriter.writeAck(new Ack(messageIds));
|
||||
packetWriter.writeMessage(message.getRaw());
|
||||
packetWriter.writeMessage(message1.getRaw());
|
||||
packetWriter.writeOffer(new Offer(messageIds));
|
||||
packetWriter.writeRequest(new Request(messageIds));
|
||||
recordWriter.writeAck(new Ack(messageIds));
|
||||
recordWriter.writeMessage(message.getRaw());
|
||||
recordWriter.writeMessage(message1.getRaw());
|
||||
recordWriter.writeOffer(new Offer(messageIds));
|
||||
recordWriter.writeRequest(new Request(messageIds));
|
||||
|
||||
streamWriter.flush();
|
||||
return out.toByteArray();
|
||||
@@ -127,31 +128,31 @@ public class SyncIntegrationTest extends BrambleTestCase {
|
||||
headerKey, streamNumber);
|
||||
InputStream streamReader = streamReaderFactory.createStreamReader(in,
|
||||
ctx);
|
||||
PacketReader packetReader = packetReaderFactory.createPacketReader(
|
||||
RecordReader recordReader = recordReaderFactory.createRecordReader(
|
||||
streamReader);
|
||||
|
||||
// Read the ack
|
||||
assertTrue(packetReader.hasAck());
|
||||
Ack a = packetReader.readAck();
|
||||
assertTrue(recordReader.hasAck());
|
||||
Ack a = recordReader.readAck();
|
||||
assertEquals(messageIds, a.getMessageIds());
|
||||
|
||||
// Read the messages
|
||||
assertTrue(packetReader.hasMessage());
|
||||
Message m = packetReader.readMessage();
|
||||
assertTrue(recordReader.hasMessage());
|
||||
Message m = recordReader.readMessage();
|
||||
checkMessageEquality(message, m);
|
||||
assertTrue(packetReader.hasMessage());
|
||||
m = packetReader.readMessage();
|
||||
assertTrue(recordReader.hasMessage());
|
||||
m = recordReader.readMessage();
|
||||
checkMessageEquality(message1, m);
|
||||
assertFalse(packetReader.hasMessage());
|
||||
assertFalse(recordReader.hasMessage());
|
||||
|
||||
// Read the offer
|
||||
assertTrue(packetReader.hasOffer());
|
||||
Offer o = packetReader.readOffer();
|
||||
assertTrue(recordReader.hasOffer());
|
||||
Offer o = recordReader.readOffer();
|
||||
assertEquals(messageIds, o.getMessageIds());
|
||||
|
||||
// Read the request
|
||||
assertTrue(packetReader.hasRequest());
|
||||
Request req = packetReader.readRequest();
|
||||
assertTrue(recordReader.hasRequest());
|
||||
Request req = recordReader.readRequest();
|
||||
assertEquals(messageIds, req.getMessageIds());
|
||||
|
||||
in.close();
|
||||
|
||||
@@ -2,12 +2,9 @@
|
||||
<manifest
|
||||
package="org.briarproject.briar"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:versionCode="13"
|
||||
android:versionName="0.13">
|
||||
|
||||
<uses-sdk tools:overrideLibrary="android.support.v14.preference"/>
|
||||
|
||||
<uses-feature android:name="android.hardware.bluetooth"/>
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
|
||||
@@ -47,7 +44,7 @@
|
||||
android:label="@string/crash_report_title"
|
||||
android:launchMode="singleInstance"
|
||||
android:process=":briar_error_handler"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="stateHidden">
|
||||
</activity>
|
||||
|
||||
@@ -70,7 +67,7 @@
|
||||
|
||||
<activity
|
||||
android:name=".android.splash.SplashScreenActivity"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:label="@string/app_name">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
@@ -80,14 +77,14 @@
|
||||
|
||||
<activity
|
||||
android:name=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:launchMode="singleTop">
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".android.contact.ConversationActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:windowSoftInputMode="stateHidden|adjustResize">
|
||||
<meta-data
|
||||
@@ -111,6 +108,7 @@
|
||||
android:name=".android.privategroup.conversation.GroupActivity"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@@ -194,6 +192,7 @@
|
||||
android:name=".android.forum.ForumActivity"
|
||||
android:label="@string/app_name"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:windowSoftInputMode="adjustResize|stateHidden">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
@@ -245,11 +244,11 @@
|
||||
|
||||
<activity
|
||||
android:name=".android.blog.BlogActivity"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity"
|
||||
android:theme="@style/BriarTheme.NoActionBar">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
android:value=".android.navdrawer.NavDrawerActivity"
|
||||
/>
|
||||
android:value=".android.navdrawer.NavDrawerActivity"/>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
@@ -308,7 +307,7 @@
|
||||
<activity
|
||||
android:name=".android.keyagreement.KeyAgreementActivity"
|
||||
android:label="@string/add_contact_title"
|
||||
android:theme="@style/BriarThemeNoActionBar.Default"
|
||||
android:theme="@style/BriarTheme.NoActionBar"
|
||||
android:parentActivityName=".android.navdrawer.NavDrawerActivity">
|
||||
<meta-data
|
||||
android:name="android.support.PARENT_ACTIVITY"
|
||||
|
||||
@@ -2,10 +2,12 @@ package org.briarproject.briar.android.activity;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
import android.view.View;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.briar.android.AndroidComponent;
|
||||
import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
@@ -116,4 +118,10 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
Object o = getSystemService(INPUT_METHOD_SERVICE);
|
||||
((InputMethodManager) o).hideSoftInputFromWindow(token, 0);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
public void handleDbException(DbException e) {
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,6 +3,9 @@ package org.briarproject.briar.android.activity;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.transition.Slide;
|
||||
import android.transition.Transition;
|
||||
import android.view.Gravity;
|
||||
@@ -25,6 +28,7 @@ import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_PASSWORD;
|
||||
|
||||
@SuppressLint("Registered")
|
||||
public abstract class BriarActivity extends BaseActivity {
|
||||
@@ -33,8 +37,6 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
public static final String GROUP_ID = "briar.GROUP_ID";
|
||||
public static final String GROUP_NAME = "briar.GROUP_NAME";
|
||||
|
||||
public static final int REQUEST_PASSWORD = 1;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BriarActivity.class.getName());
|
||||
|
||||
@@ -94,6 +96,27 @@ public abstract class BriarActivity extends BaseActivity {
|
||||
window.setBackgroundDrawableResource(android.R.color.transparent);
|
||||
}
|
||||
|
||||
/**
|
||||
* This should be called after the content view has been added in onCreate()
|
||||
*
|
||||
* @param ownLayout true if the custom toolbar brings its own layout
|
||||
* @return the Toolbar object or null if content view did not contain one
|
||||
*/
|
||||
@Nullable
|
||||
protected Toolbar setUpCustomToolbar(boolean ownLayout) {
|
||||
// Custom Toolbar
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
setSupportActionBar(toolbar);
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayShowHomeEnabled(true);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
ab.setDisplayShowCustomEnabled(ownLayout);
|
||||
ab.setDisplayShowTitleEnabled(!ownLayout);
|
||||
}
|
||||
return toolbar;
|
||||
}
|
||||
|
||||
protected void signOut(final boolean removeFromRecentApps) {
|
||||
briarController.signOut(new UiResultHandler<Void>(this) {
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package org.briarproject.briar.android.activity;
|
||||
|
||||
public interface RequestCodes {
|
||||
|
||||
int REQUEST_PASSWORD = 1;
|
||||
int REQUEST_BLUETOOTH = 2;
|
||||
int REQUEST_INTRODUCTION = 3;
|
||||
int REQUEST_GROUP_INVITE = 4;
|
||||
int REQUEST_SHARE_FORUM = 5;
|
||||
int REQUEST_WRITE_BLOG_POST = 6;
|
||||
int REQUEST_SHARE_BLOG = 7;
|
||||
int REQUEST_RINGTONE = 8;
|
||||
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package org.briarproject.briar.android.blog;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.view.LayoutInflater;
|
||||
@@ -20,7 +22,7 @@ import javax.annotation.Nullable;
|
||||
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_RESOLUTION;
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@@ -32,8 +34,9 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BasePostFragment.class.getName());
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
protected MessageId postId;
|
||||
private View view;
|
||||
private ProgressBar progressBar;
|
||||
private BlogPostViewHolder ui;
|
||||
private BlogPostItem post;
|
||||
@@ -50,7 +53,7 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
if (p == null) throw new IllegalStateException("No post ID in args");
|
||||
postId = new MessageId(p);
|
||||
|
||||
view = inflater.inflate(R.layout.fragment_blog_post, container,
|
||||
View view = inflater.inflate(R.layout.fragment_blog_post, container,
|
||||
false);
|
||||
progressBar = (ProgressBar) view.findViewById(R.id.progressBar);
|
||||
progressBar.setVisibility(VISIBLE);
|
||||
@@ -83,21 +86,19 @@ abstract class BasePostFragment extends BaseFragment {
|
||||
refresher = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (ui == null) return;
|
||||
LOG.info("Updating Content...");
|
||||
|
||||
ui.updateDate(post.getTimestamp());
|
||||
view.postDelayed(refresher, MIN_RESOLUTION);
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
}
|
||||
};
|
||||
LOG.info("Adding Handler Callback");
|
||||
view.postDelayed(refresher, MIN_RESOLUTION);
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
}
|
||||
|
||||
private void stopPeriodicUpdate() {
|
||||
if (refresher != null && ui != null) {
|
||||
if (refresher != null) {
|
||||
LOG.info("Removing Handler Callback");
|
||||
view.removeCallbacks(refresher);
|
||||
handler.removeCallbacks(refresher);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.briarproject.briar.android.blog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.View;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
@@ -21,9 +22,6 @@ import javax.inject.Inject;
|
||||
public class BlogActivity extends BriarActivity
|
||||
implements BaseFragmentListener {
|
||||
|
||||
static final int REQUEST_WRITE_POST = 2;
|
||||
static final int REQUEST_SHARE = 3;
|
||||
|
||||
@Inject
|
||||
BlogController blogController;
|
||||
|
||||
@@ -38,12 +36,12 @@ public class BlogActivity extends BriarActivity
|
||||
final GroupId groupId = new GroupId(b);
|
||||
blogController.setGroupId(groupId);
|
||||
|
||||
setContentView(R.layout.activity_fragment_container);
|
||||
setContentView(R.layout.activity_fragment_container_toolbar);
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
|
||||
// Open Sharing Status on ActionBar click
|
||||
View actionBar = findViewById(R.id.action_bar);
|
||||
if (actionBar != null) {
|
||||
actionBar.setOnClickListener(
|
||||
// Open Sharing Status on Toolbar click
|
||||
if (toolbar != null) {
|
||||
toolbar.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
|
||||
@@ -46,8 +46,8 @@ import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.blog.BlogActivity.REQUEST_SHARE;
|
||||
import static org.briarproject.briar.android.blog.BlogActivity.REQUEST_WRITE_POST;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_BLOG;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||
import static org.briarproject.briar.android.controller.SharingController.SharingListener;
|
||||
|
||||
@UiThread
|
||||
@@ -145,13 +145,13 @@ public class BlogFragment extends BaseFragment
|
||||
Intent i = new Intent(getActivity(),
|
||||
WriteBlogPostActivity.class);
|
||||
i.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i, REQUEST_WRITE_POST);
|
||||
startActivityForResult(i, REQUEST_WRITE_BLOG_POST);
|
||||
return true;
|
||||
case R.id.action_blog_share:
|
||||
Intent i2 = new Intent(getActivity(), ShareBlogActivity.class);
|
||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i2, REQUEST_SHARE);
|
||||
startActivityForResult(i2, REQUEST_SHARE_BLOG);
|
||||
return true;
|
||||
case R.id.action_blog_sharing_status:
|
||||
Intent i3 = new Intent(getActivity(),
|
||||
@@ -172,10 +172,10 @@ public class BlogFragment extends BaseFragment
|
||||
public void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_WRITE_POST && result == RESULT_OK) {
|
||||
if (request == REQUEST_WRITE_BLOG_POST && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.blogs_blog_post_created, true);
|
||||
loadBlogPosts(true);
|
||||
} else if (request == REQUEST_SHARE && result == RESULT_OK) {
|
||||
} else if (request == REQUEST_SHARE_BLOG && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.blogs_sharing_snackbar, false);
|
||||
}
|
||||
}
|
||||
@@ -205,8 +205,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
@@ -234,8 +233,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -254,8 +252,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -277,8 +274,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -373,8 +369,7 @@ public class BlogFragment extends BaseFragment
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -55,8 +55,7 @@ public class BlogPostFragment extends BasePostFragment {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import javax.inject.Inject;
|
||||
import static android.app.Activity.RESULT_OK;
|
||||
import static android.support.design.widget.Snackbar.LENGTH_LONG;
|
||||
import static org.briarproject.briar.android.activity.BriarActivity.GROUP_ID;
|
||||
import static org.briarproject.briar.android.blog.BlogActivity.REQUEST_WRITE_POST;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_WRITE_BLOG_POST;
|
||||
|
||||
@UiThread
|
||||
@MethodsNotNullByDefault
|
||||
@@ -96,7 +96,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
super.onActivityResult(requestCode, resultCode, data);
|
||||
|
||||
// The BlogPostAddedEvent arrives when the controller is not listening
|
||||
if (requestCode == REQUEST_WRITE_POST && resultCode == RESULT_OK) {
|
||||
if (requestCode == REQUEST_WRITE_BLOG_POST && resultCode == RESULT_OK) {
|
||||
showSnackBar(R.string.blogs_blog_post_created);
|
||||
}
|
||||
}
|
||||
@@ -105,6 +105,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
public void onStart() {
|
||||
super.onStart();
|
||||
feedController.onStart();
|
||||
list.startPeriodicUpdate();
|
||||
loadPersonalBlog();
|
||||
loadBlogPosts(false);
|
||||
}
|
||||
@@ -129,7 +130,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -153,11 +154,10 @@ public class FeedFragment extends BaseFragment implements
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException e) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -174,7 +174,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
Intent i1 =
|
||||
new Intent(getActivity(), WriteBlogPostActivity.class);
|
||||
i1.putExtra(GROUP_ID, personalBlog.getId().getBytes());
|
||||
startActivityForResult(i1, REQUEST_WRITE_POST);
|
||||
startActivityForResult(i1, REQUEST_WRITE_BLOG_POST);
|
||||
return true;
|
||||
case R.id.action_rss_feeds_import:
|
||||
Intent i2 =
|
||||
@@ -211,7 +211,7 @@ public class FeedFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
handleDbException(exception);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
@@ -79,7 +79,7 @@ public class FeedPostFragment extends BasePostFragment {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO: Decide how to handle errors in the UI
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -99,8 +99,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -130,8 +129,7 @@ public class ReblogFragment extends BaseFragment implements TextInputListener {
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
// do nothing, this fragment is gone already
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
finish();
|
||||
|
||||
@@ -6,7 +6,6 @@ import android.os.Bundle;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.ActionMenuView;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
@@ -50,8 +49,11 @@ import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
import org.briarproject.briar.android.activity.BriarActivity;
|
||||
import org.briarproject.briar.android.blog.BlogActivity;
|
||||
import org.briarproject.briar.android.contact.ConversationAdapter.ConversationListener;
|
||||
import org.briarproject.briar.android.forum.ForumActivity;
|
||||
import org.briarproject.briar.android.introduction.IntroductionActivity;
|
||||
import org.briarproject.briar.android.privategroup.conversation.GroupActivity;
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
@@ -77,6 +79,8 @@ import org.briarproject.briar.api.sharing.InvitationRequest;
|
||||
import org.briarproject.briar.api.sharing.InvitationResponse;
|
||||
import org.briarproject.briar.api.sharing.event.InvitationRequestReceivedEvent;
|
||||
import org.briarproject.briar.api.sharing.event.InvitationResponseReceivedEvent;
|
||||
import org.thoughtcrime.securesms.components.util.FutureTaskListener;
|
||||
import org.thoughtcrime.securesms.components.util.ListenableFutureTask;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
@@ -84,8 +88,10 @@ import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
@@ -101,6 +107,7 @@ import static android.support.v7.util.SortedList.INVALID_POSITION;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_INTRODUCTION;
|
||||
import static org.briarproject.briar.android.settings.SettingsFragment.SETTINGS_NAMESPACE;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getAvatarTransitionName;
|
||||
import static org.briarproject.briar.android.util.UiUtils.getBulbTransitionName;
|
||||
@@ -115,7 +122,6 @@ public class ConversationActivity extends BriarActivity
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ConversationActivity.class.getName());
|
||||
private static final int REQUEST_CODE_INTRODUCTION = 2;
|
||||
private static final String SHOW_ONBOARDING_INTRODUCTION =
|
||||
"showOnboardingIntroduction";
|
||||
|
||||
@@ -137,6 +143,18 @@ public class ConversationActivity extends BriarActivity
|
||||
private BriarRecyclerView list;
|
||||
private TextInputView textInputView;
|
||||
|
||||
private final ListenableFutureTask<String> contactNameTask =
|
||||
new ListenableFutureTask<>(new Callable<String>() {
|
||||
@Override
|
||||
public String call() throws Exception {
|
||||
Contact c = contactManager.getContact(contactId);
|
||||
contactName = c.getAuthor().getName();
|
||||
return c.getAuthor().getName();
|
||||
}
|
||||
});
|
||||
private final AtomicBoolean contactNameTaskStarted =
|
||||
new AtomicBoolean(false);
|
||||
|
||||
// Fields that are accessed from background threads must be volatile
|
||||
@Inject
|
||||
volatile ContactManager contactManager;
|
||||
@@ -158,8 +176,11 @@ public class ConversationActivity extends BriarActivity
|
||||
volatile GroupInvitationManager groupInvitationManager;
|
||||
|
||||
private volatile ContactId contactId;
|
||||
@Nullable
|
||||
private volatile String contactName;
|
||||
@Nullable
|
||||
private volatile AuthorId contactAuthorId;
|
||||
@Nullable
|
||||
private volatile GroupId messagingGroupId;
|
||||
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
@@ -176,21 +197,13 @@ public class ConversationActivity extends BriarActivity
|
||||
setContentView(R.layout.activity_conversation);
|
||||
|
||||
// Custom Toolbar
|
||||
toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
toolbar = setUpCustomToolbar(true);
|
||||
if (toolbar != null) {
|
||||
toolbarAvatar =
|
||||
(CircleImageView) toolbar.findViewById(R.id.contactAvatar);
|
||||
toolbarStatus =
|
||||
(ImageView) toolbar.findViewById(R.id.contactStatus);
|
||||
toolbarTitle = (TextView) toolbar.findViewById(R.id.contactName);
|
||||
setSupportActionBar(toolbar);
|
||||
}
|
||||
ActionBar ab = getSupportActionBar();
|
||||
if (ab != null) {
|
||||
ab.setDisplayShowHomeEnabled(true);
|
||||
ab.setDisplayHomeAsUpEnabled(true);
|
||||
ab.setDisplayShowCustomEnabled(true);
|
||||
ab.setDisplayShowTitleEnabled(false);
|
||||
}
|
||||
|
||||
setTransitionName(toolbarAvatar, getAvatarTransitionName(contactId));
|
||||
@@ -215,7 +228,7 @@ public class ConversationActivity extends BriarActivity
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_CODE_INTRODUCTION && result == RESULT_OK) {
|
||||
if (request == REQUEST_INTRODUCTION && result == RESULT_OK) {
|
||||
Snackbar snackbar = Snackbar.make(list, R.string.introduction_sent,
|
||||
Snackbar.LENGTH_SHORT);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
@@ -229,8 +242,8 @@ public class ConversationActivity extends BriarActivity
|
||||
eventBus.addListener(this);
|
||||
notificationManager.blockContactNotification(contactId);
|
||||
notificationManager.clearContactNotification(contactId);
|
||||
loadContactDetails();
|
||||
loadMessages();
|
||||
displayContactOnlineStatus();
|
||||
loadContactDetailsAndMessages();
|
||||
list.startPeriodicUpdate();
|
||||
}
|
||||
|
||||
@@ -265,7 +278,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (contactId == null) return false;
|
||||
Intent intent = new Intent(this, IntroductionActivity.class);
|
||||
intent.putExtra(CONTACT_ID, contactId.getInt());
|
||||
startActivityForResult(intent, REQUEST_CODE_INTRODUCTION);
|
||||
startActivityForResult(intent, REQUEST_INTRODUCTION);
|
||||
return true;
|
||||
case R.id.action_social_remove_person:
|
||||
askToRemoveContact();
|
||||
@@ -275,7 +288,7 @@ public class ConversationActivity extends BriarActivity
|
||||
}
|
||||
}
|
||||
|
||||
private void loadContactDetails() {
|
||||
private void loadContactDetailsAndMessages() {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -289,6 +302,7 @@ public class ConversationActivity extends BriarActivity
|
||||
long duration = System.currentTimeMillis() - now;
|
||||
if (LOG.isLoggable(INFO))
|
||||
LOG.info("Loading contact took " + duration + " ms");
|
||||
loadMessages();
|
||||
displayContactDetails();
|
||||
} catch (NoSuchContactException e) {
|
||||
finishOnUiThread();
|
||||
@@ -304,10 +318,18 @@ public class ConversationActivity extends BriarActivity
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
//noinspection ConstantConditions
|
||||
toolbarAvatar.setImageDrawable(
|
||||
new IdenticonDrawable(contactAuthorId.getBytes()));
|
||||
toolbarTitle.setText(contactName);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void displayContactOnlineStatus() {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (connectionRegistry.isConnected(contactId)) {
|
||||
toolbarStatus.setImageDrawable(ContextCompat
|
||||
.getDrawable(ConversationActivity.this,
|
||||
@@ -347,7 +369,8 @@ public class ConversationActivity extends BriarActivity
|
||||
groupInvitationManager
|
||||
.getInvitationMessages(contactId);
|
||||
List<InvitationMessage> invitations = new ArrayList<>(
|
||||
forumInvitations.size() + blogInvitations.size());
|
||||
forumInvitations.size() + blogInvitations.size() +
|
||||
groupInvitations.size());
|
||||
invitations.addAll(forumInvitations);
|
||||
invitations.addAll(blogInvitations);
|
||||
invitations.addAll(groupInvitations);
|
||||
@@ -390,6 +413,12 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates ConversationItems from headers loaded from the database.
|
||||
*
|
||||
* Attention: Call this only after contactName has been initialized.
|
||||
*/
|
||||
@SuppressWarnings("ConstantConditions")
|
||||
private List<ConversationItem> createItems(
|
||||
Collection<PrivateMessageHeader> headers,
|
||||
Collection<IntroductionMessage> introductions,
|
||||
@@ -468,18 +497,6 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private void addConversationItem(final ConversationItem item) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.incrementRevision();
|
||||
adapter.add(item);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof ContactRemovedEvent) {
|
||||
@@ -512,13 +529,13 @@ public class ConversationActivity extends BriarActivity
|
||||
ContactConnectedEvent c = (ContactConnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact connected");
|
||||
displayContactDetails();
|
||||
displayContactOnlineStatus();
|
||||
}
|
||||
} else if (e instanceof ContactDisconnectedEvent) {
|
||||
ContactDisconnectedEvent c = (ContactDisconnectedEvent) e;
|
||||
if (c.getContactId().equals(contactId)) {
|
||||
LOG.info("Contact disconnected");
|
||||
displayContactDetails();
|
||||
displayContactOnlineStatus();
|
||||
}
|
||||
} else if (e instanceof IntroductionRequestReceivedEvent) {
|
||||
IntroductionRequestReceivedEvent event =
|
||||
@@ -526,9 +543,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
LOG.info("Introduction request received, adding...");
|
||||
IntroductionRequest ir = event.getIntroductionRequest();
|
||||
ConversationItem item =
|
||||
ConversationItem.from(this, contactName, ir);
|
||||
addConversationItem(item);
|
||||
handleIntroductionRequest(ir);
|
||||
}
|
||||
} else if (e instanceof IntroductionResponseReceivedEvent) {
|
||||
IntroductionResponseReceivedEvent event =
|
||||
@@ -536,9 +551,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
LOG.info("Introduction response received, adding...");
|
||||
IntroductionResponse ir = event.getIntroductionResponse();
|
||||
ConversationItem item =
|
||||
ConversationItem.from(this, contactName, ir);
|
||||
addConversationItem(item);
|
||||
handleIntroductionResponse(ir);
|
||||
}
|
||||
} else if (e instanceof InvitationRequestReceivedEvent) {
|
||||
InvitationRequestReceivedEvent event =
|
||||
@@ -546,9 +559,7 @@ public class ConversationActivity extends BriarActivity
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
LOG.info("Invitation received, adding...");
|
||||
InvitationRequest ir = event.getRequest();
|
||||
ConversationItem item =
|
||||
ConversationItem.from(this, contactName, ir);
|
||||
addConversationItem(item);
|
||||
handleInvitationRequest(ir);
|
||||
}
|
||||
} else if (e instanceof InvitationResponseReceivedEvent) {
|
||||
InvitationResponseReceivedEvent event =
|
||||
@@ -556,13 +567,127 @@ public class ConversationActivity extends BriarActivity
|
||||
if (event.getContactId().equals(contactId)) {
|
||||
LOG.info("Invitation response received, adding...");
|
||||
InvitationResponse ir = event.getResponse();
|
||||
ConversationItem item =
|
||||
ConversationItem.from(this, contactName, ir);
|
||||
addConversationItem(item);
|
||||
handleInvitationResponse(ir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void addConversationItem(final ConversationItem item) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
adapter.incrementRevision();
|
||||
adapter.add(item);
|
||||
// Scroll to the bottom
|
||||
list.scrollToPosition(adapter.getItemCount() - 1);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleIntroductionRequest(final IntroductionRequest m) {
|
||||
getContactNameTask().addListener(new FutureTaskListener<String>() {
|
||||
@Override
|
||||
public void onSuccess(final String contactName) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConversationItem item = ConversationItem
|
||||
.from(ConversationActivity.this, contactName,
|
||||
m);
|
||||
addConversationItem(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onFailure(final Throwable exception) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleDbException((DbException) exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleIntroductionResponse(final IntroductionResponse m) {
|
||||
getContactNameTask().addListener(new FutureTaskListener<String>() {
|
||||
@Override
|
||||
public void onSuccess(final String contactName) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConversationItem item = ConversationItem
|
||||
.from(ConversationActivity.this, contactName,
|
||||
m);
|
||||
addConversationItem(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onFailure(final Throwable exception) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleDbException((DbException) exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleInvitationRequest(final InvitationRequest m) {
|
||||
getContactNameTask().addListener(new FutureTaskListener<String>() {
|
||||
@Override
|
||||
public void onSuccess(final String contactName) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConversationItem item = ConversationItem
|
||||
.from(ConversationActivity.this, contactName,
|
||||
m);
|
||||
addConversationItem(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onFailure(final Throwable exception) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleDbException((DbException) exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void handleInvitationResponse(final InvitationResponse m) {
|
||||
getContactNameTask().addListener(new FutureTaskListener<String>() {
|
||||
@Override
|
||||
public void onSuccess(final String contactName) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ConversationItem item = ConversationItem
|
||||
.from(ConversationActivity.this, contactName,
|
||||
m);
|
||||
addConversationItem(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@Override
|
||||
public void onFailure(final Throwable exception) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
handleDbException((DbException) exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void markMessages(final Collection<MessageId> messageIds,
|
||||
final boolean sent, final boolean seen) {
|
||||
runOnUiThreadUnlessDestroyed(new Runnable() {
|
||||
@@ -623,6 +748,7 @@ public class ConversationActivity extends BriarActivity
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
//noinspection ConstantConditions init in loadGroupId()
|
||||
storeMessage(privateMessageFactory.createPrivateMessage(
|
||||
messagingGroupId, timestamp, body), body);
|
||||
} catch (FormatException e) {
|
||||
@@ -854,11 +980,33 @@ public class ConversationActivity extends BriarActivity
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void openRequestedShareable(ConversationRequestItem item) {
|
||||
if (item.getRequestedGroupId() == null)
|
||||
throw new IllegalArgumentException();
|
||||
Intent i;
|
||||
switch (item.getRequestType()) {
|
||||
case FORUM:
|
||||
i = new Intent(this, ForumActivity.class);
|
||||
break;
|
||||
case BLOG:
|
||||
i = new Intent(this, BlogActivity.class);
|
||||
break;
|
||||
case GROUP:
|
||||
i = new Intent(this, GroupActivity.class);
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unknown Request Type");
|
||||
}
|
||||
i.putExtra(GROUP_ID, item.getRequestedGroupId().getBytes());
|
||||
startActivity(i);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void respondToIntroductionRequest(SessionId sessionId,
|
||||
boolean accept, long time) throws DbException, FormatException {
|
||||
@@ -872,13 +1020,13 @@ public class ConversationActivity extends BriarActivity
|
||||
@DatabaseExecutor
|
||||
private void respondToForumRequest(SessionId id, boolean accept)
|
||||
throws DbException {
|
||||
forumSharingManager.respondToInvitation(id, accept);
|
||||
forumSharingManager.respondToInvitation(contactId, id, accept);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
private void respondToBlogRequest(SessionId id, boolean accept)
|
||||
throws DbException {
|
||||
blogSharingManager.respondToInvitation(id, accept);
|
||||
blogSharingManager.respondToInvitation(contactId, id, accept);
|
||||
}
|
||||
|
||||
@DatabaseExecutor
|
||||
@@ -902,4 +1050,10 @@ public class ConversationActivity extends BriarActivity
|
||||
});
|
||||
}
|
||||
|
||||
private ListenableFutureTask<String> getContactNameTask() {
|
||||
if (!contactNameTaskStarted.getAndSet(true))
|
||||
runOnDbThread(contactNameTask);
|
||||
return contactNameTask;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -139,6 +139,9 @@ class ConversationAdapter
|
||||
void onItemVisible(ConversationItem item);
|
||||
|
||||
void respondToRequest(ConversationRequestItem item, boolean accept);
|
||||
|
||||
void openRequestedShareable(ConversationRequestItem item);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -101,9 +101,6 @@ abstract class ConversationItem {
|
||||
text = ctx.getString(
|
||||
R.string.introduction_request_answered_received,
|
||||
contactName, ir.getName());
|
||||
return new ConversationNoticeInItem(ir.getMessageId(),
|
||||
ir.getGroupId(), text, ir.getMessage(), ir.getTimestamp(),
|
||||
ir.isRead());
|
||||
} else if (ir.contactExists()){
|
||||
text = ctx.getString(
|
||||
R.string.introduction_request_exists_received,
|
||||
@@ -114,8 +111,8 @@ abstract class ConversationItem {
|
||||
}
|
||||
return new ConversationRequestItem(ir.getMessageId(),
|
||||
ir.getGroupId(), INTRODUCTION, ir.getSessionId(), text,
|
||||
ir.getMessage(), ir.getTimestamp(), ir.isRead(),
|
||||
ir.wasAnswered());
|
||||
ir.getMessage(), ir.getTimestamp(), ir.isRead(), null,
|
||||
ir.wasAnswered(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -173,8 +170,7 @@ abstract class ConversationItem {
|
||||
} else if (ir instanceof GroupInvitationRequest) {
|
||||
text = ctx.getString(
|
||||
R.string.groups_invitations_invitation_sent,
|
||||
contactName,
|
||||
((GroupInvitationRequest) ir).getGroupName());
|
||||
contactName, ir.getShareable().getName());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown InvitationRequest");
|
||||
}
|
||||
@@ -197,20 +193,16 @@ abstract class ConversationItem {
|
||||
} else if (ir instanceof GroupInvitationRequest) {
|
||||
text = ctx.getString(
|
||||
R.string.groups_invitations_invitation_received,
|
||||
contactName,
|
||||
((GroupInvitationRequest) ir).getGroupName());
|
||||
contactName, ir.getShareable().getName());
|
||||
type = GROUP;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown InvitationRequest");
|
||||
}
|
||||
if (!ir.isAvailable()) {
|
||||
return new ConversationNoticeInItem(ir.getId(), ir.getGroupId(),
|
||||
text, ir.getMessage(), ir.getTimestamp(), ir.isRead());
|
||||
}
|
||||
return new ConversationRequestItem(ir.getId(),
|
||||
ir.getGroupId(), type, ir.getSessionId(), text,
|
||||
ir.getMessage(), ir.getTimestamp(), ir.isRead(),
|
||||
!ir.isAvailable());
|
||||
ir.getShareable().getId(), !ir.isAvailable(),
|
||||
ir.canBeOpened());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -17,18 +17,23 @@ class ConversationRequestItem extends ConversationNoticeInItem {
|
||||
|
||||
enum RequestType { INTRODUCTION, FORUM, BLOG, GROUP }
|
||||
|
||||
@Nullable
|
||||
private final GroupId requestedGroupId;
|
||||
private final RequestType requestType;
|
||||
private final SessionId sessionId;
|
||||
private boolean answered;
|
||||
private final boolean answered, canBeOpened;
|
||||
|
||||
ConversationRequestItem(MessageId id, GroupId groupId,
|
||||
RequestType requestType, SessionId sessionId, String text,
|
||||
@Nullable String msgText, long time, boolean read,
|
||||
boolean answered) {
|
||||
@Nullable GroupId requestedGroupId, boolean answered,
|
||||
boolean canBeOpened) {
|
||||
super(id, groupId, text, msgText, time, read);
|
||||
this.requestType = requestType;
|
||||
this.sessionId = sessionId;
|
||||
this.requestedGroupId = requestedGroupId;
|
||||
this.answered = answered;
|
||||
this.canBeOpened = canBeOpened;
|
||||
}
|
||||
|
||||
RequestType getRequestType() {
|
||||
@@ -39,12 +44,17 @@ class ConversationRequestItem extends ConversationNoticeInItem {
|
||||
return sessionId;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public GroupId getRequestedGroupId() {
|
||||
return requestedGroupId;
|
||||
}
|
||||
|
||||
boolean wasAnswered() {
|
||||
return answered;
|
||||
}
|
||||
|
||||
void setAnswered(boolean answered) {
|
||||
this.answered = answered;
|
||||
public boolean canBeOpened() {
|
||||
return canBeOpened;
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
|
||||
@@ -32,15 +32,25 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
|
||||
final ConversationRequestItem item =
|
||||
(ConversationRequestItem) conversationItem;
|
||||
|
||||
if (item.wasAnswered()) {
|
||||
if (item.wasAnswered() && item.canBeOpened()) {
|
||||
acceptButton.setVisibility(VISIBLE);
|
||||
acceptButton.setText(R.string.open);
|
||||
acceptButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.openRequestedShareable(item);
|
||||
}
|
||||
});
|
||||
declineButton.setVisibility(GONE);
|
||||
} else if (item.wasAnswered()) {
|
||||
acceptButton.setVisibility(GONE);
|
||||
declineButton.setVisibility(GONE);
|
||||
} else {
|
||||
acceptButton.setVisibility(VISIBLE);
|
||||
acceptButton.setText(R.string.accept);
|
||||
acceptButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
item.setAnswered(true);
|
||||
listener.respondToRequest(item, true);
|
||||
}
|
||||
});
|
||||
@@ -48,7 +58,6 @@ class ConversationRequestViewHolder extends ConversationNoticeInViewHolder {
|
||||
declineButton.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
item.setAnswered(true);
|
||||
listener.respondToRequest(item, false);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -130,8 +130,7 @@ public abstract class BaseContactSelectorFragment<I extends SelectableContactIte
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -4,10 +4,10 @@ import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -36,6 +36,7 @@ import javax.inject.Inject;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
|
||||
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
|
||||
import static android.widget.Toast.LENGTH_SHORT;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_SHARE_FORUM;
|
||||
import static org.briarproject.briar.api.forum.ForumConstants.MAX_FORUM_POST_BODY_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -44,8 +45,6 @@ public class ForumActivity extends
|
||||
ThreadListActivity<Forum, ThreadItemAdapter<ForumItem>, ForumItem, ForumPostHeader>
|
||||
implements ForumListener {
|
||||
|
||||
private static final int REQUEST_FORUM_SHARED = 3;
|
||||
|
||||
@Inject
|
||||
ForumController forumController;
|
||||
|
||||
@@ -63,15 +62,16 @@ public class ForumActivity extends
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
|
||||
Intent i = getIntent();
|
||||
String groupName = i.getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
else loadNamedGroup();
|
||||
|
||||
// Open Sharing Status on ActionBar click
|
||||
View actionBar = findViewById(R.id.action_bar);
|
||||
if (actionBar != null) {
|
||||
actionBar.setOnClickListener(
|
||||
// Open member list on Toolbar click
|
||||
if (toolbar != null) {
|
||||
toolbar.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -89,12 +89,6 @@ public class ForumActivity extends
|
||||
setTitle(forum.getName());
|
||||
}
|
||||
|
||||
@Override
|
||||
@LayoutRes
|
||||
protected int getLayout() {
|
||||
return R.layout.activity_forum;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ThreadItemAdapter<ForumItem> createAdapter(
|
||||
LinearLayoutManager layoutManager) {
|
||||
@@ -105,8 +99,8 @@ public class ForumActivity extends
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
super.onActivityResult(request, result, data);
|
||||
|
||||
if (request == REQUEST_FORUM_SHARED && result == RESULT_OK) {
|
||||
displaySnackbarShort(R.string.forum_shared_snackbar);
|
||||
if (request == REQUEST_SHARE_FORUM && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.forum_shared_snackbar);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -130,7 +124,7 @@ public class ForumActivity extends
|
||||
Intent i2 = new Intent(this, ShareForumActivity.class);
|
||||
i2.setFlags(FLAG_ACTIVITY_CLEAR_TOP | FLAG_ACTIVITY_SINGLE_TOP);
|
||||
i2.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i2, REQUEST_FORUM_SHARED);
|
||||
startActivityForResult(i2, REQUEST_SHARE_FORUM);
|
||||
return true;
|
||||
case R.id.action_forum_sharing_status:
|
||||
Intent i3 = new Intent(this, ForumSharingStatusActivity.class);
|
||||
@@ -157,12 +151,6 @@ public class ForumActivity extends
|
||||
return R.string.forum_new_entry_posted;
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
protected int getItemReceivedString() {
|
||||
return R.string.forum_new_entry_received;
|
||||
}
|
||||
|
||||
private void showUnsubscribeDialog() {
|
||||
OnClickListener okListener = new OnClickListener() {
|
||||
@Override
|
||||
@@ -190,8 +178,7 @@ public class ForumActivity extends
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import android.support.annotation.UiThread;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.view.MenuItem;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.activity.ActivityComponent;
|
||||
|
||||
@@ -71,6 +72,9 @@ public abstract class BaseFragment extends Fragment
|
||||
|
||||
@UiThread
|
||||
void showNextFragment(BaseFragment f);
|
||||
|
||||
@UiThread
|
||||
void handleDbException(DbException e);
|
||||
}
|
||||
|
||||
@CallSuper
|
||||
@@ -95,4 +99,9 @@ public abstract class BaseFragment extends Fragment
|
||||
listener.showNextFragment(f);
|
||||
}
|
||||
|
||||
@UiThread
|
||||
protected void handleDbException(DbException e) {
|
||||
listener.handleDbException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import javax.inject.Inject;
|
||||
import static android.widget.Toast.LENGTH_LONG;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.CONNECTED;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.DETAILS;
|
||||
import static org.briarproject.briar.android.invitation.ConfirmationCodeView.ConfirmationState.WAIT_FOR_CONTACT;
|
||||
@@ -32,8 +33,6 @@ import static org.briarproject.briar.android.invitation.ConfirmationCodeView.Con
|
||||
public class AddContactActivity extends BriarActivity
|
||||
implements InvitationListener {
|
||||
|
||||
static final int REQUEST_BLUETOOTH = 1;
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(AddContactActivity.class.getName());
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.briarproject.briar.R;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||
import static org.briarproject.briar.android.invitation.AddContactActivity.REQUEST_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
|
||||
class ChooseIdentityView extends AddContactView implements OnClickListener {
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import org.briarproject.briar.R;
|
||||
|
||||
import static android.bluetooth.BluetoothAdapter.ACTION_REQUEST_DISCOVERABLE;
|
||||
import static android.bluetooth.BluetoothAdapter.EXTRA_DISCOVERABLE_DURATION;
|
||||
import static org.briarproject.briar.android.invitation.AddContactActivity.REQUEST_BLUETOOTH;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_BLUETOOTH;
|
||||
|
||||
class ErrorView extends AddContactView implements OnClickListener {
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public class KeyAgreementActivity extends BriarActivity implements
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
setContentView(R.layout.activity_plain);
|
||||
setContentView(R.layout.activity_fragment_container_toolbar);
|
||||
|
||||
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
|
||||
|
||||
|
||||
@@ -86,6 +86,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
|
||||
private BluetoothStateReceiver receiver;
|
||||
private boolean gotRemotePayload, waitingForBluetooth;
|
||||
private volatile boolean gotLocalPayload;
|
||||
private KeyAgreementTask task;
|
||||
|
||||
public static ShowQrCodeFragment newInstance() {
|
||||
@@ -202,6 +203,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
statusView.setVisibility(INVISIBLE);
|
||||
cameraView.setVisibility(VISIBLE);
|
||||
gotRemotePayload = false;
|
||||
gotLocalPayload = false;
|
||||
startListening();
|
||||
}
|
||||
|
||||
@@ -227,6 +229,7 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
public void eventOccurred(Event e) {
|
||||
if (e instanceof KeyAgreementListeningEvent) {
|
||||
KeyAgreementListeningEvent event = (KeyAgreementListeningEvent) e;
|
||||
gotLocalPayload = true;
|
||||
setQrCode(event.getLocalPayload());
|
||||
} else if (e instanceof KeyAgreementFailedEvent) {
|
||||
keyAgreementFailed();
|
||||
@@ -341,6 +344,10 @@ public class ShowQrCodeFragment extends BaseEventFragment
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Got result from decoder");
|
||||
// Ignore results until the KeyAgreementTask is ready
|
||||
if (!gotLocalPayload) {
|
||||
return;
|
||||
}
|
||||
if (!gotRemotePayload) {
|
||||
gotRemotePayload = true;
|
||||
qrCodeScanned(result.getText());
|
||||
|
||||
@@ -19,6 +19,7 @@ import android.widget.GridView;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
import org.briarproject.bramble.api.plugin.BluetoothConstants;
|
||||
import org.briarproject.bramble.api.plugin.LanTcpConstants;
|
||||
import org.briarproject.bramble.api.plugin.TorConstants;
|
||||
@@ -74,13 +75,13 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
exitIfStartupFailed(intent);
|
||||
// TODO don't create new instances if they are on the stack (#606)
|
||||
if (intent.getBooleanExtra(INTENT_GROUPS, false)) {
|
||||
startFragment(GroupListFragment.newInstance());
|
||||
startFragment(GroupListFragment.newInstance(), R.id.nav_btn_groups);
|
||||
} else if (intent.getBooleanExtra(INTENT_FORUMS, false)) {
|
||||
startFragment(ForumListFragment.newInstance());
|
||||
startFragment(ForumListFragment.newInstance(), R.id.nav_btn_forums);
|
||||
} else if (intent.getBooleanExtra(INTENT_CONTACTS, false)) {
|
||||
startFragment(ContactListFragment.newInstance());
|
||||
startFragment(ContactListFragment.newInstance(), R.id.nav_btn_contacts);
|
||||
} else if (intent.getBooleanExtra(INTENT_BLOGS, false)) {
|
||||
startFragment(FeedFragment.newInstance());
|
||||
startFragment(FeedFragment.newInstance(), R.id.nav_btn_blogs);
|
||||
}
|
||||
setIntent(null);
|
||||
}
|
||||
@@ -116,8 +117,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
transportsView.setAdapter(transportsAdapter);
|
||||
|
||||
if (state == null) {
|
||||
navigation.setCheckedItem(R.id.nav_btn_contacts);
|
||||
startFragment(ContactListFragment.newInstance());
|
||||
startFragment(ContactListFragment.newInstance(), R.id.nav_btn_contacts);
|
||||
}
|
||||
if (getIntent() != null) {
|
||||
onNewIntent(getIntent());
|
||||
@@ -194,8 +194,7 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
* exiting. This models the typical Google navigation behaviour such
|
||||
* as in Gmail/Inbox.
|
||||
*/
|
||||
navigation.setCheckedItem(R.id.nav_btn_contacts);
|
||||
startFragment(ContactListFragment.newInstance());
|
||||
startFragment(ContactListFragment.newInstance(), R.id.nav_btn_contacts);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
@@ -220,6 +219,11 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
super.signOut();
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment, int itemId){
|
||||
navigation.setCheckedItem(itemId);
|
||||
startFragment(fragment);
|
||||
}
|
||||
|
||||
private void startFragment(BaseFragment fragment) {
|
||||
if (getSupportFragmentManager().getBackStackEntryCount() == 0)
|
||||
startFragment(fragment, false);
|
||||
@@ -246,6 +250,11 @@ public class NavDrawerActivity extends BriarActivity implements
|
||||
POP_BACK_STACK_INCLUSIVE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleDbException(DbException e) {
|
||||
// Do nothing for now
|
||||
}
|
||||
|
||||
private void initializeTransports(final LayoutInflater inflater) {
|
||||
transports = new ArrayList<>(3);
|
||||
|
||||
|
||||
@@ -4,10 +4,10 @@ import android.content.DialogInterface;
|
||||
import android.content.DialogInterface.OnClickListener;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
@@ -37,6 +37,7 @@ import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_GROUP_INVITE;
|
||||
import static org.briarproject.briar.api.privategroup.PrivateGroupConstants.MAX_GROUP_POST_BODY_LENGTH;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@@ -45,8 +46,6 @@ public class GroupActivity extends
|
||||
ThreadListActivity<PrivateGroup, GroupMessageAdapter, GroupMessageItem, GroupMessageHeader>
|
||||
implements GroupListener, OnClickListener {
|
||||
|
||||
private final static int REQUEST_INVITE = 2;
|
||||
|
||||
@Inject
|
||||
GroupController controller;
|
||||
|
||||
@@ -68,15 +67,16 @@ public class GroupActivity extends
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
Toolbar toolbar = setUpCustomToolbar(false);
|
||||
|
||||
Intent i = getIntent();
|
||||
String groupName = i.getStringExtra(GROUP_NAME);
|
||||
if (groupName != null) setTitle(groupName);
|
||||
loadNamedGroup();
|
||||
|
||||
// Open member list on ActionBar click
|
||||
View actionBar = findViewById(R.id.action_bar);
|
||||
if (actionBar != null) {
|
||||
actionBar.setOnClickListener(
|
||||
// Open member list on Toolbar click
|
||||
if (toolbar != null) {
|
||||
toolbar.setOnClickListener(
|
||||
new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
@@ -91,12 +91,6 @@ public class GroupActivity extends
|
||||
setGroupEnabled(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@LayoutRes
|
||||
protected int getLayout() {
|
||||
return R.layout.activity_forum;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GroupMessageAdapter createAdapter(
|
||||
LinearLayoutManager layoutManager) {
|
||||
@@ -115,8 +109,7 @@ public class GroupActivity extends
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -135,8 +128,7 @@ public class GroupActivity extends
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -176,7 +168,7 @@ public class GroupActivity extends
|
||||
case R.id.action_group_invite:
|
||||
Intent i3 = new Intent(this, GroupInviteActivity.class);
|
||||
i3.putExtra(GROUP_ID, groupId.getBytes());
|
||||
startActivityForResult(i3, REQUEST_INVITE);
|
||||
startActivityForResult(i3, REQUEST_GROUP_INVITE);
|
||||
return true;
|
||||
case R.id.action_group_leave:
|
||||
showLeaveGroupDialog();
|
||||
@@ -190,8 +182,8 @@ public class GroupActivity extends
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int request, int result, Intent data) {
|
||||
if (request == REQUEST_INVITE && result == RESULT_OK) {
|
||||
displaySnackbarShort(R.string.groups_invitation_sent);
|
||||
if (request == REQUEST_GROUP_INVITE && result == RESULT_OK) {
|
||||
displaySnackbar(R.string.groups_invitation_sent);
|
||||
} else super.onActivityResult(request, result, data);
|
||||
}
|
||||
|
||||
@@ -206,12 +198,6 @@ public class GroupActivity extends
|
||||
return R.string.groups_message_sent;
|
||||
}
|
||||
|
||||
@Override
|
||||
@StringRes
|
||||
protected int getItemReceivedString() {
|
||||
return R.string.groups_message_received;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReplyClick(GroupMessageItem item) {
|
||||
if (!isDissolved) super.onReplyClick(item);
|
||||
@@ -274,8 +260,7 @@ public class GroupActivity extends
|
||||
// GroupRemovedEvent being fired
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,9 +31,8 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
@LayoutRes
|
||||
@Override
|
||||
public int getItemViewType(int position) {
|
||||
GroupMessageItem item = getVisibleItem(position);
|
||||
if (item != null) return item.getLayout();
|
||||
return R.layout.list_item_thread;
|
||||
GroupMessageItem item = items.get(position);
|
||||
return item.getLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -58,7 +57,7 @@ class GroupMessageAdapter extends ThreadItemAdapter<GroupMessageItem> {
|
||||
GroupMessageItem item = items.get(position);
|
||||
if (item instanceof JoinMessageItem) {
|
||||
((JoinMessageItem) item).setVisibility(v);
|
||||
notifyItemChanged(getVisiblePos(item), item);
|
||||
notifyItemChanged(findItemPosition(item), item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package org.briarproject.briar.android.privategroup.conversation;
|
||||
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import org.briarproject.bramble.api.identity.Author;
|
||||
@@ -20,9 +21,8 @@ class GroupMessageItem extends ThreadItem {
|
||||
private final GroupId groupId;
|
||||
|
||||
private GroupMessageItem(MessageId messageId, GroupId groupId,
|
||||
MessageId parentId,
|
||||
String text, long timestamp, Author author, Status status,
|
||||
boolean isRead) {
|
||||
@Nullable MessageId parentId, String text, long timestamp,
|
||||
Author author, Status status, boolean isRead) {
|
||||
super(messageId, parentId, text, timestamp, author, status, isRead);
|
||||
this.groupId = groupId;
|
||||
}
|
||||
|
||||
@@ -27,11 +27,6 @@ class JoinMessageItem extends GroupMessageItem {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasDescendants() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@LayoutRes
|
||||
public int getLayout() {
|
||||
|
||||
@@ -12,7 +12,6 @@ import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.privategroup.reveal.RevealContactsActivity;
|
||||
import org.briarproject.briar.android.threaded.BaseThreadItemViewHolder;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
|
||||
import static org.briarproject.bramble.api.identity.Author.Status.OURSELVES;
|
||||
@@ -41,10 +40,9 @@ class JoinMessageItemViewHolder
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bind(ThreadItemAdapter<GroupMessageItem> adapter,
|
||||
ThreadItemListener<GroupMessageItem> listener,
|
||||
GroupMessageItem item, int pos) {
|
||||
super.bind(adapter, listener, item, pos);
|
||||
public void bind(GroupMessageItem item,
|
||||
ThreadItemListener<GroupMessageItem> listener) {
|
||||
super.bind(item, listener);
|
||||
|
||||
if (isCreator) bindForCreator((JoinMessageItem) item);
|
||||
else bind((JoinMessageItem) item);
|
||||
|
||||
@@ -43,9 +43,8 @@ public abstract class BaseGroupInviteActivity
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
setResult(RESULT_CANCELED);
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
return true;
|
||||
|
||||
@@ -58,8 +58,7 @@ public class CreateGroupActivity extends BaseGroupInviteActivity implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -132,7 +132,7 @@ public class GroupListFragment extends BaseFragment implements
|
||||
// result handled by GroupRemovedEvent and onGroupRemoved()
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO handle error
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -202,7 +202,7 @@ public class GroupListFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO handle this error
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -224,8 +224,7 @@ public class GroupListFragment extends BaseFragment implements
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO handle this error
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -67,8 +67,7 @@ public class GroupMemberListActivity extends BriarActivity {
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
list.startPeriodicUpdate();
|
||||
|
||||
@@ -80,8 +80,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -132,7 +131,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -149,8 +148,7 @@ public class RevealContactsActivity extends ContactSelectorActivity
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
supportFinishAfterTransition();
|
||||
|
||||
@@ -45,13 +45,13 @@ import static android.media.RingtoneManager.TYPE_NOTIFICATION;
|
||||
import static android.provider.Settings.System.DEFAULT_NOTIFICATION_URI;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.briar.android.activity.RequestCodes.REQUEST_RINGTONE;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements EventListener, Preference.OnPreferenceChangeListener {
|
||||
|
||||
private static final int REQUEST_RINGTONE = 2;
|
||||
public static final String SETTINGS_NAMESPACE = "android-ui";
|
||||
public static final String PREF_NOTIFY_GROUP = "notifyGroupMessages";
|
||||
public static final String PREF_NOTIFY_BLOG = "notifyBlogPosts";
|
||||
|
||||
@@ -97,8 +97,7 @@ public abstract class InvitationActivity<I extends InvitationItem>
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -111,8 +110,7 @@ public abstract class InvitationActivity<I extends InvitationItem>
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -56,10 +56,10 @@ public class ShareBlogActivity extends ShareActivity {
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
Toast.makeText(ShareBlogActivity.this,
|
||||
R.string.blogs_sharing_error, LENGTH_SHORT)
|
||||
.show();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
import org.briarproject.briar.api.messaging.ConversationManager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -22,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -31,14 +34,19 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
|
||||
private final static Logger LOG =
|
||||
Logger.getLogger(ShareBlogControllerImpl.class.getName());
|
||||
|
||||
private final ConversationManager conversationManager;
|
||||
private final BlogSharingManager blogSharingManager;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
ShareBlogControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
BlogSharingManager blogSharingManager) {
|
||||
ConversationManager conversationManager,
|
||||
BlogSharingManager blogSharingManager, Clock clock) {
|
||||
super(dbExecutor, lifecycleManager, contactManager);
|
||||
this.conversationManager = conversationManager;
|
||||
this.blogSharingManager = blogSharingManager;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,15 +56,19 @@ class ShareBlogControllerImpl extends ContactSelectorControllerImpl
|
||||
|
||||
@Override
|
||||
public void share(final GroupId g, final Collection<ContactId> contacts,
|
||||
final String msg,
|
||||
final String message,
|
||||
final ExceptionHandler<DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String msg = isNullOrEmpty(message) ? null : message;
|
||||
for (ContactId c : contacts) {
|
||||
try {
|
||||
blogSharingManager.sendInvitation(g, c, msg);
|
||||
long time = Math.max(clock.currentTimeMillis(),
|
||||
conversationManager.getGroupCount(c)
|
||||
.getLatestMsgTime() + 1);
|
||||
blogSharingManager.sendInvitation(g, c, msg, time);
|
||||
} catch (NoSuchContactException | NoSuchGroupException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -56,10 +56,10 @@ public class ShareForumActivity extends ShareActivity {
|
||||
new UiExceptionHandler<DbException>(this) {
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO proper error handling
|
||||
Toast.makeText(ShareForumActivity.this,
|
||||
R.string.forum_share_error, LENGTH_SHORT)
|
||||
.show();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,9 +10,11 @@ import org.briarproject.bramble.api.db.NoSuchGroupException;
|
||||
import org.briarproject.bramble.api.lifecycle.LifecycleManager;
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.system.Clock;
|
||||
import org.briarproject.briar.android.contactselection.ContactSelectorControllerImpl;
|
||||
import org.briarproject.briar.android.controller.handler.ExceptionHandler;
|
||||
import org.briarproject.briar.api.forum.ForumSharingManager;
|
||||
import org.briarproject.briar.api.messaging.ConversationManager;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.concurrent.Executor;
|
||||
@@ -22,6 +24,7 @@ import javax.annotation.concurrent.Immutable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
import static org.briarproject.bramble.util.StringUtils.isNullOrEmpty;
|
||||
|
||||
@Immutable
|
||||
@NotNullByDefault
|
||||
@@ -31,14 +34,19 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
|
||||
private final static Logger LOG =
|
||||
Logger.getLogger(ShareForumControllerImpl.class.getName());
|
||||
|
||||
private final ConversationManager conversationManager;
|
||||
private final ForumSharingManager forumSharingManager;
|
||||
private final Clock clock;
|
||||
|
||||
@Inject
|
||||
ShareForumControllerImpl(@DatabaseExecutor Executor dbExecutor,
|
||||
LifecycleManager lifecycleManager, ContactManager contactManager,
|
||||
ForumSharingManager forumSharingManager) {
|
||||
ConversationManager conversationManager,
|
||||
ForumSharingManager forumSharingManager, Clock clock) {
|
||||
super(dbExecutor, lifecycleManager, contactManager);
|
||||
this.conversationManager = conversationManager;
|
||||
this.forumSharingManager = forumSharingManager;
|
||||
this.clock = clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -48,15 +56,19 @@ class ShareForumControllerImpl extends ContactSelectorControllerImpl
|
||||
|
||||
@Override
|
||||
public void share(final GroupId g, final Collection<ContactId> contacts,
|
||||
final String msg,
|
||||
final String message,
|
||||
final ExceptionHandler<DbException> handler) {
|
||||
runOnDbThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
String msg = isNullOrEmpty(message) ? null : message;
|
||||
for (ContactId c : contacts) {
|
||||
try {
|
||||
forumSharingManager.sendInvitation(g, c, msg);
|
||||
long time = Math.max(clock.currentTimeMillis(),
|
||||
conversationManager.getGroupCount(c)
|
||||
.getLatestMsgTime() + 1);
|
||||
forumSharingManager.sendInvitation(g, c, msg, time);
|
||||
} catch (NoSuchContactException | NoSuchGroupException e) {
|
||||
if (LOG.isLoggable(WARNING))
|
||||
LOG.log(WARNING, e.toString(), e);
|
||||
|
||||
@@ -30,8 +30,8 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(SplashScreenActivity.class.getName());
|
||||
|
||||
// This build expires on 1 January 2017
|
||||
private static final long EXPIRY_DATE = 1483225200 * 1000L;
|
||||
// This build expires on 1 May 2017
|
||||
private static final long EXPIRY_DATE = 1493593200 * 1000L;
|
||||
|
||||
@Inject
|
||||
protected ConfigController configController;
|
||||
|
||||
@@ -11,6 +11,7 @@ import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.animation.AccelerateInterpolator;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
@@ -29,7 +30,6 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
protected final TextView textView;
|
||||
private final ViewGroup layout;
|
||||
private final AuthorView author;
|
||||
private final View topDivider;
|
||||
|
||||
public BaseThreadItemViewHolder(View v) {
|
||||
super(v);
|
||||
@@ -37,71 +37,52 @@ public abstract class BaseThreadItemViewHolder<I extends ThreadItem>
|
||||
layout = (ViewGroup) v.findViewById(R.id.layout);
|
||||
textView = (TextView) v.findViewById(R.id.text);
|
||||
author = (AuthorView) v.findViewById(R.id.author);
|
||||
topDivider = v.findViewById(R.id.top_divider);
|
||||
}
|
||||
|
||||
// TODO improve encapsulation, so we don't need to pass the adapter here
|
||||
@CallSuper
|
||||
public void bind(final ThreadItemAdapter<I> adapter,
|
||||
final ThreadItemListener<I> listener, final I item, int pos) {
|
||||
|
||||
public void bind(final I item, final ThreadItemListener<I> listener) {
|
||||
textView.setText(StringUtils.trim(item.getText()));
|
||||
|
||||
if (pos == 0) {
|
||||
topDivider.setVisibility(View.INVISIBLE);
|
||||
} else {
|
||||
topDivider.setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
author.setAuthor(item.getAuthor());
|
||||
author.setDate(item.getTimestamp());
|
||||
author.setAuthorStatus(item.getStatus());
|
||||
|
||||
if (item.equals(adapter.getReplyItem())) {
|
||||
layout.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.forum_cell_highlight));
|
||||
} else if (item.equals(adapter.getAddedItem())) {
|
||||
layout.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.forum_cell_highlight));
|
||||
animateFadeOut(adapter, adapter.getAddedItem());
|
||||
adapter.clearAddedItem();
|
||||
if (item.isHighlighted()) {
|
||||
layout.setActivated(true);
|
||||
} else if (!item.isRead()) {
|
||||
layout.setActivated(true);
|
||||
animateFadeOut();
|
||||
listener.onUnreadItemVisible(item);
|
||||
} else {
|
||||
layout.setBackgroundColor(ContextCompat
|
||||
.getColor(getContext(), R.color.window_background));
|
||||
layout.setActivated(false);
|
||||
}
|
||||
}
|
||||
|
||||
private void animateFadeOut(final ThreadItemAdapter<I> adapter,
|
||||
final I addedItem) {
|
||||
|
||||
private void animateFadeOut() {
|
||||
setIsRecyclable(false);
|
||||
ValueAnimator anim = new ValueAnimator();
|
||||
adapter.addAnimatingItem(addedItem, anim);
|
||||
ColorDrawable viewColor = (ColorDrawable) layout.getBackground();
|
||||
ColorDrawable viewColor = new ColorDrawable(ContextCompat
|
||||
.getColor(getContext(), R.color.forum_cell_highlight));
|
||||
anim.setIntValues(viewColor.getColor(), ContextCompat
|
||||
.getColor(getContext(), R.color.window_background));
|
||||
anim.setEvaluator(new ArgbEvaluator());
|
||||
anim.setInterpolator(new AccelerateInterpolator());
|
||||
anim.addListener(new Animator.AnimatorListener() {
|
||||
@Override
|
||||
public void onAnimationStart(Animator animation) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationEnd(Animator animation) {
|
||||
layout.setBackgroundResource(
|
||||
R.drawable.list_item_thread_background);
|
||||
layout.setActivated(false);
|
||||
setIsRecyclable(true);
|
||||
adapter.removeAnimatingItem(addedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationCancel(Animator animation) {
|
||||
setIsRecyclable(true);
|
||||
adapter.removeAnimatingItem(addedItem);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAnimationRepeat(Animator animation) {
|
||||
|
||||
}
|
||||
});
|
||||
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
|
||||
|
||||
@@ -23,9 +23,7 @@ public abstract class ThreadItem implements MessageNode {
|
||||
private final Author author;
|
||||
private final Status status;
|
||||
private int level = UNDEFINED;
|
||||
private boolean isShowingDescendants = true;
|
||||
private int descendantCount = 0;
|
||||
private boolean isRead;
|
||||
private boolean isRead, highlighted;
|
||||
|
||||
public ThreadItem(MessageId messageId, @Nullable MessageId parentId,
|
||||
String text, long timestamp, Author author, Status status,
|
||||
@@ -37,6 +35,7 @@ public abstract class ThreadItem implements MessageNode {
|
||||
this.author = author;
|
||||
this.status = status;
|
||||
this.isRead = isRead;
|
||||
this.highlighted = false;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
@@ -71,19 +70,11 @@ public abstract class ThreadItem implements MessageNode {
|
||||
return status;
|
||||
}
|
||||
|
||||
public boolean isShowingDescendants() {
|
||||
return isShowingDescendants;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setLevel(int level) {
|
||||
this.level = level;
|
||||
}
|
||||
|
||||
public void setShowingDescendants(boolean showingDescendants) {
|
||||
this.isShowingDescendants = showingDescendants;
|
||||
}
|
||||
|
||||
public boolean isRead() {
|
||||
return isRead;
|
||||
}
|
||||
@@ -92,13 +83,12 @@ public abstract class ThreadItem implements MessageNode {
|
||||
isRead = read;
|
||||
}
|
||||
|
||||
public boolean hasDescendants() {
|
||||
return descendantCount > 0;
|
||||
public void setHighlighted(boolean highlighted) {
|
||||
this.highlighted = highlighted;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setDescendantCount(int descendantCount) {
|
||||
this.descendantCount = descendantCount;
|
||||
public boolean isHighlighted() {
|
||||
return highlighted;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
package org.briarproject.briar.android.threaded;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
@@ -12,16 +12,11 @@ import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.util.VersionedAdapter;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||
|
||||
@UiThread
|
||||
public class ThreadItemAdapter<I extends ThreadItem>
|
||||
extends RecyclerView.Adapter<BaseThreadItemViewHolder<I>>
|
||||
implements VersionedAdapter {
|
||||
@@ -29,15 +24,9 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
static final int UNDEFINED = -1;
|
||||
|
||||
protected final NestedTreeList<I> items = new NestedTreeList<>();
|
||||
private final Map<I, ValueAnimator> animatingItems = new HashMap<>();
|
||||
private final ThreadItemListener<I> listener;
|
||||
private final LinearLayoutManager layoutManager;
|
||||
|
||||
// highlight not dependant on time
|
||||
private I replyItem;
|
||||
// temporary highlight
|
||||
private I addedItem;
|
||||
|
||||
private volatile int revision = 0;
|
||||
|
||||
public ThreadItemAdapter(ThreadItemListener<I> listener,
|
||||
@@ -56,24 +45,23 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
|
||||
@Override
|
||||
public void onBindViewHolder(BaseThreadItemViewHolder<I> ui, int position) {
|
||||
I item = getVisibleItem(position);
|
||||
if (item == null) return;
|
||||
listener.onItemVisible(item);
|
||||
ui.bind(this, listener, item, position);
|
||||
I item = items.get(position);
|
||||
ui.bind(item, listener);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contrary to the super class adapter,
|
||||
* this returns the number of <b>visible</b> items,
|
||||
* not all items in the dataset.
|
||||
*/
|
||||
@Override
|
||||
public int getItemCount() {
|
||||
return getVisiblePos(null);
|
||||
return items.size();
|
||||
}
|
||||
|
||||
I getReplyItem() {
|
||||
return replyItem;
|
||||
@Override
|
||||
public int getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void incrementRevision() {
|
||||
revision++;
|
||||
}
|
||||
|
||||
public void setItems(Collection<I> items) {
|
||||
@@ -84,249 +72,116 @@ public class ThreadItemAdapter<I extends ThreadItem>
|
||||
|
||||
public void add(I item) {
|
||||
items.add(item);
|
||||
addedItem = item;
|
||||
if (item.getParentId() == null) {
|
||||
notifyItemInserted(getVisiblePos(item));
|
||||
} else {
|
||||
// Try to find the item's parent and perform the proper ui update if
|
||||
// it's present and visible.
|
||||
for (int i = items.indexOf(item) - 1; i >= 0; i--) {
|
||||
I higherItem = items.get(i);
|
||||
if (higherItem.getLevel() < item.getLevel()) {
|
||||
// parent found
|
||||
if (higherItem.isShowingDescendants()) {
|
||||
int parentVisiblePos = getVisiblePos(higherItem);
|
||||
if (parentVisiblePos != NO_POSITION) {
|
||||
// parent is visible, we need to update its ui
|
||||
notifyItemChanged(parentVisiblePos);
|
||||
// new item insert ui
|
||||
int visiblePos = getVisiblePos(item);
|
||||
notifyItemInserted(visiblePos);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
// do not show the new item if its parent is not showing
|
||||
// descendants (this can be overridden by the user by
|
||||
// pressing the snack bar)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
notifyItemInserted(findItemPosition(item));
|
||||
}
|
||||
|
||||
void scrollTo(I item) {
|
||||
int visiblePos = getVisiblePos(item);
|
||||
MessageId parentId = item.getParentId();
|
||||
if (visiblePos == NO_POSITION && parentId != null) {
|
||||
// The item is not visible due to being hidden by its parent item.
|
||||
// Find the parent and make it visible and traverse up the parent
|
||||
// chain if necessary to make the item visible
|
||||
for (int i = items.indexOf(item) - 1; i >= 0; i--) {
|
||||
I higherItem = items.get(i);
|
||||
if (higherItem.getId().equals(parentId)) {
|
||||
// parent found
|
||||
showDescendants(higherItem);
|
||||
int parentPos = getVisiblePos(higherItem);
|
||||
if (parentPos != NO_POSITION) {
|
||||
// parent or ancestor is visible, item's visibility
|
||||
// is ensured
|
||||
notifyItemChanged(parentPos);
|
||||
visiblePos = parentPos;
|
||||
break;
|
||||
}
|
||||
// parent or ancestor is hidden, we need to continue up the
|
||||
// dependency chain
|
||||
parentId = higherItem.getParentId();
|
||||
if (parentId == null) throw new AssertionError();
|
||||
}
|
||||
}
|
||||
@Nullable
|
||||
public I getItemAt(int position) {
|
||||
if (position == NO_POSITION || position >= items.size()) {
|
||||
return null;
|
||||
}
|
||||
if (visiblePos != NO_POSITION)
|
||||
layoutManager.scrollToPositionWithOffset(visiblePos, 0);
|
||||
return items.get(position);
|
||||
}
|
||||
|
||||
int getReplyCount(I item) {
|
||||
int counter = 0;
|
||||
int pos = items.indexOf(item);
|
||||
if (pos >= 0) {
|
||||
int ancestorLvl = item.getLevel();
|
||||
for (int i = pos + 1; i < items.size(); i++) {
|
||||
int descendantLvl = items.get(i).getLevel();
|
||||
if (descendantLvl <= ancestorLvl)
|
||||
break;
|
||||
if (descendantLvl == ancestorLvl + 1)
|
||||
counter++;
|
||||
}
|
||||
protected int findItemPosition(@Nullable I item) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (items.get(i).equals(item)) return i;
|
||||
}
|
||||
return counter;
|
||||
return NO_POSITION; // Not found
|
||||
}
|
||||
|
||||
void setReplyItem(@Nullable I item) {
|
||||
if (replyItem != null) {
|
||||
notifyItemChanged(getVisiblePos(replyItem));
|
||||
}
|
||||
replyItem = item;
|
||||
if (replyItem != null) {
|
||||
notifyItemChanged(getVisiblePos(replyItem));
|
||||
}
|
||||
}
|
||||
|
||||
void setReplyItemById(MessageId id) {
|
||||
for (I item : items) {
|
||||
if (item.getId().equals(id)) {
|
||||
setReplyItem(item);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private List<Integer> getSubTreeIndexes(int pos, int levelLimit) {
|
||||
List<Integer> indexList = new ArrayList<>();
|
||||
|
||||
for (int i = pos + 1; i < getItemCount(); i++) {
|
||||
I item = getVisibleItem(i);
|
||||
if (item != null && item.getLevel() > levelLimit) {
|
||||
indexList.add(i);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return indexList;
|
||||
}
|
||||
|
||||
public void showDescendants(I item) {
|
||||
item.setShowingDescendants(true);
|
||||
int visiblePos = getVisiblePos(item);
|
||||
List<Integer> indexList =
|
||||
getSubTreeIndexes(visiblePos, item.getLevel());
|
||||
if (!indexList.isEmpty()) {
|
||||
if (indexList.size() == 1) {
|
||||
notifyItemInserted(indexList.get(0));
|
||||
} else {
|
||||
notifyItemRangeInserted(indexList.get(0),
|
||||
indexList.size());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void hideDescendants(I item) {
|
||||
int visiblePos = getVisiblePos(item);
|
||||
List<Integer> indexList =
|
||||
getSubTreeIndexes(visiblePos, item.getLevel());
|
||||
if (!indexList.isEmpty()) {
|
||||
// stop animating children
|
||||
for (int index : indexList) {
|
||||
ValueAnimator anim = animatingItems.get(items.get(index));
|
||||
if (anim != null && anim.isRunning()) {
|
||||
anim.cancel();
|
||||
}
|
||||
}
|
||||
if (indexList.size() == 1) {
|
||||
notifyItemRemoved(indexList.get(0));
|
||||
} else {
|
||||
notifyItemRangeRemoved(indexList.get(0),
|
||||
indexList.size());
|
||||
}
|
||||
}
|
||||
item.setShowingDescendants(false);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the visible item at the given position
|
||||
* Highlights the item with the given {@link MessageId}
|
||||
* and disables the highlight for a previously highlighted item, if any.
|
||||
*
|
||||
* @param position is visible item index
|
||||
* @return the visible item at index 'position' from an ordered list of
|
||||
* visible items, or null if 'position' is larger than the number of
|
||||
* visible items.
|
||||
* Only one item can be highlighted at a time.
|
||||
*/
|
||||
void setHighlightedItem(@Nullable MessageId id) {
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
I item = items.get(i);
|
||||
if (id != null && item.getId().equals(id)) {
|
||||
item.setHighlighted(true);
|
||||
notifyItemChanged(i, item);
|
||||
} else if (item.isHighlighted()) {
|
||||
item.setHighlighted(false);
|
||||
notifyItemChanged(i, item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public I getVisibleItem(int position) {
|
||||
int levelLimit = UNDEFINED;
|
||||
for (I item : items) {
|
||||
if (levelLimit >= 0) {
|
||||
// skip hidden items that their parent is hiding
|
||||
if (item.getLevel() > levelLimit) {
|
||||
continue;
|
||||
}
|
||||
levelLimit = UNDEFINED;
|
||||
}
|
||||
if (!item.isShowingDescendants()) {
|
||||
levelLimit = item.getLevel();
|
||||
}
|
||||
if (position-- == 0) {
|
||||
return item;
|
||||
}
|
||||
I getHighlightedItem() {
|
||||
for (I i : items) {
|
||||
if (i.isHighlighted()) return i;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
boolean isVisible(I item) {
|
||||
return getVisiblePos(item) != NO_POSITION;
|
||||
/**
|
||||
* Gets the number of unread items above and below the current view port.
|
||||
*
|
||||
* Attention: Do not call this when the list is still scrolling,
|
||||
* because then the view port is unknown.
|
||||
*/
|
||||
public UnreadCount getUnreadCount() {
|
||||
final int positionTop = layoutManager.findFirstVisibleItemPosition();
|
||||
final int positionBottom = layoutManager.findLastVisibleItemPosition();
|
||||
if (positionTop == NO_POSITION && positionBottom == NO_POSITION)
|
||||
return new UnreadCount(0, 0);
|
||||
|
||||
int unreadCounterTop = 0, unreadCounterBottom = 0;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
I item = items.get(i);
|
||||
if (i < positionTop && !item.isRead()) {
|
||||
unreadCounterTop++;
|
||||
} else if (i > positionBottom && !item.isRead()) {
|
||||
unreadCounterBottom++;
|
||||
}
|
||||
}
|
||||
return new UnreadCount(unreadCounterTop, unreadCounterBottom);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the visible position of the given item.
|
||||
*
|
||||
* @param item the item to find the visible position of, or null to
|
||||
* return the total count of visible items.
|
||||
* @return the visible position of 'item', or the total number of visible
|
||||
* items if 'item' is null. If 'item' is not visible, NO_POSITION is
|
||||
* returned.
|
||||
* Returns the position of the first unread item below the current viewport
|
||||
*/
|
||||
protected int getVisiblePos(@Nullable I item) {
|
||||
int visibleCounter = 0;
|
||||
int levelLimit = UNDEFINED;
|
||||
for (I i : items) {
|
||||
if (levelLimit >= 0) {
|
||||
if (i.getLevel() > levelLimit) {
|
||||
// skip all the items below a non visible branch
|
||||
continue;
|
||||
}
|
||||
levelLimit = UNDEFINED;
|
||||
}
|
||||
if (item != null && item.equals(i)) {
|
||||
return visibleCounter;
|
||||
} else if (!i.isShowingDescendants()) {
|
||||
levelLimit = i.getLevel();
|
||||
}
|
||||
visibleCounter++;
|
||||
public int getVisibleUnreadPosBottom() {
|
||||
final int positionBottom = layoutManager.findLastVisibleItemPosition();
|
||||
if (positionBottom == NO_POSITION) return NO_POSITION;
|
||||
for (int i = positionBottom + 1; i < items.size(); i++) {
|
||||
if (!items.get(i).isRead()) return i;
|
||||
}
|
||||
return item == null ? visibleCounter : NO_POSITION;
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
I getAddedItem() {
|
||||
return addedItem;
|
||||
/**
|
||||
* Returns the position of the first unread item above the current viewport
|
||||
*/
|
||||
public int getVisibleUnreadPosTop() {
|
||||
final int positionTop = layoutManager.findFirstVisibleItemPosition();
|
||||
int position = NO_POSITION;
|
||||
for (int i = 0; i < items.size(); i++) {
|
||||
if (i < positionTop && !items.get(i).isRead()) {
|
||||
position = i;
|
||||
} else if (i >= positionTop) {
|
||||
return position;
|
||||
}
|
||||
}
|
||||
return NO_POSITION;
|
||||
}
|
||||
|
||||
void clearAddedItem() {
|
||||
addedItem = null;
|
||||
}
|
||||
static class UnreadCount {
|
||||
final int top, bottom;
|
||||
|
||||
void addAnimatingItem(I item, ValueAnimator anim) {
|
||||
animatingItems.put(item, anim);
|
||||
}
|
||||
|
||||
void removeAnimatingItem(I item) {
|
||||
animatingItems.remove(item);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getRevision() {
|
||||
return revision;
|
||||
}
|
||||
|
||||
@UiThread
|
||||
@Override
|
||||
public void incrementRevision() {
|
||||
revision++;
|
||||
private UnreadCount(int top, int bottom) {
|
||||
this.top = top;
|
||||
this.bottom = bottom;
|
||||
}
|
||||
}
|
||||
|
||||
public interface ThreadItemListener<I> {
|
||||
|
||||
void onItemVisible(I item);
|
||||
void onUnreadItemVisible(I item);
|
||||
|
||||
void onReplyClick(I item);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,13 +3,13 @@ package org.briarproject.briar.android.threaded;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.CallSuper;
|
||||
import android.support.annotation.LayoutRes;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.StringRes;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.Snackbar;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.app.ActionBar;
|
||||
import android.support.v7.widget.LinearLayoutManager;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
|
||||
@@ -30,18 +30,23 @@ import org.briarproject.briar.android.threaded.ThreadListController.ThreadListLi
|
||||
import org.briarproject.briar.android.view.BriarRecyclerView;
|
||||
import org.briarproject.briar.android.view.TextInputView;
|
||||
import org.briarproject.briar.android.view.TextInputView.TextInputListener;
|
||||
import org.briarproject.briar.android.view.UnreadMessageButton;
|
||||
import org.briarproject.briar.api.client.NamedGroup;
|
||||
import org.briarproject.briar.api.client.PostHeader;
|
||||
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.support.design.widget.Snackbar.make;
|
||||
import static android.support.v7.widget.RecyclerView.NO_POSITION;
|
||||
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static java.util.logging.Level.INFO;
|
||||
import static org.briarproject.briar.android.threaded.ThreadItemAdapter.UnreadCount;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
@@ -58,8 +63,11 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
protected A adapter;
|
||||
protected BriarRecyclerView list;
|
||||
private LinearLayoutManager layoutManager;
|
||||
protected TextInputView textInput;
|
||||
protected GroupId groupId;
|
||||
private UnreadMessageButton upButton, downButton;
|
||||
@Nullable
|
||||
private MessageId replyId;
|
||||
|
||||
protected abstract ThreadListController<G, I, H> getController();
|
||||
@@ -72,7 +80,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
public void onCreate(@Nullable Bundle state) {
|
||||
super.onCreate(state);
|
||||
|
||||
setContentView(getLayout());
|
||||
setContentView(R.layout.activity_threaded_conversation);
|
||||
|
||||
Intent i = getIntent();
|
||||
byte[] b = i.getByteArrayExtra(GROUP_ID);
|
||||
@@ -84,11 +92,52 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
textInput.setVisibility(GONE);
|
||||
textInput.setListener(this);
|
||||
list = (BriarRecyclerView) findViewById(R.id.list);
|
||||
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
|
||||
list.setLayoutManager(linearLayoutManager);
|
||||
adapter = createAdapter(linearLayoutManager);
|
||||
layoutManager = new LinearLayoutManager(this);
|
||||
list.setLayoutManager(layoutManager);
|
||||
adapter = createAdapter(layoutManager);
|
||||
list.setAdapter(adapter);
|
||||
|
||||
list.getRecyclerView().addOnScrollListener(
|
||||
new RecyclerView.OnScrollListener() {
|
||||
@Override
|
||||
public void onScrolled(RecyclerView recyclerView, int dx,
|
||||
int dy) {
|
||||
super.onScrolled(recyclerView, dx, dy);
|
||||
if (dx == 0 && dy == 0) {
|
||||
// scrollToPosition has been called and finished
|
||||
updateUnreadCount();
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public void onScrollStateChanged(RecyclerView recyclerView,
|
||||
int newState) {
|
||||
super.onScrollStateChanged(recyclerView, newState);
|
||||
if (newState == SCROLL_STATE_IDLE) {
|
||||
updateUnreadCount();
|
||||
}
|
||||
}
|
||||
});
|
||||
upButton = (UnreadMessageButton) findViewById(R.id.upButton);
|
||||
downButton = (UnreadMessageButton) findViewById(R.id.downButton);
|
||||
upButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = adapter.getVisibleUnreadPosTop();
|
||||
if (position != NO_POSITION) {
|
||||
list.getRecyclerView().scrollToPosition(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
downButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
int position = adapter.getVisibleUnreadPosBottom();
|
||||
if (position != NO_POSITION) {
|
||||
list.getRecyclerView().scrollToPosition(position);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (state != null) {
|
||||
byte[] replyIdBytes = state.getByteArray(KEY_REPLY_ID);
|
||||
if (replyIdBytes != null) replyId = new MessageId(replyIdBytes);
|
||||
@@ -99,9 +148,6 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
loadSharingContacts();
|
||||
}
|
||||
|
||||
@LayoutRes
|
||||
protected abstract int getLayout();
|
||||
|
||||
protected abstract A createAdapter(LinearLayoutManager layoutManager);
|
||||
|
||||
protected void loadNamedGroup() {
|
||||
@@ -114,8 +160,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -137,7 +182,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
adapter.setItems(items);
|
||||
list.showData();
|
||||
if (replyId != null)
|
||||
adapter.setReplyItemById(replyId);
|
||||
adapter.setHighlightedItem(replyId);
|
||||
}
|
||||
} else {
|
||||
LOG.info("Concurrent update, reloading");
|
||||
@@ -147,8 +192,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO Proper error handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -165,9 +209,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException e) {
|
||||
// TODO Proper error handling
|
||||
finish();
|
||||
public void onExceptionUi(DbException exception) {
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -200,7 +243,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
super.onSaveInstanceState(outState);
|
||||
boolean visible = textInput.getVisibility() == VISIBLE;
|
||||
outState.putBoolean(KEY_INPUT_VISIBILITY, visible);
|
||||
ThreadItem replyItem = adapter.getReplyItem();
|
||||
ThreadItem replyItem = adapter.getHighlightedItem();
|
||||
if (replyItem != null) {
|
||||
outState.putByteArray(KEY_REPLY_ID, replyItem.getId().getBytes());
|
||||
}
|
||||
@@ -221,14 +264,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
public void onBackPressed() {
|
||||
if (textInput.getVisibility() == VISIBLE) {
|
||||
textInput.setVisibility(GONE);
|
||||
adapter.setReplyItem(null);
|
||||
adapter.setHighlightedItem(null);
|
||||
} else {
|
||||
super.onBackPressed();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onItemVisible(I item) {
|
||||
public void onUnreadItemVisible(I item) {
|
||||
if (!item.isRead()) {
|
||||
item.setRead(true);
|
||||
getController().markItemRead(item);
|
||||
@@ -236,8 +279,21 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReplyClick(I item) {
|
||||
public void onReplyClick(final I item) {
|
||||
showTextInput(item);
|
||||
if (textInput.isKeyboardOpen()) {
|
||||
scrollToItemAtTop(item);
|
||||
} else {
|
||||
// wait with scrolling until keyboard opened
|
||||
textInput.addOnKeyboardShownListener(
|
||||
new KeyboardAwareLinearLayout.OnKeyboardShownListener() {
|
||||
@Override
|
||||
public void onKeyboardShown() {
|
||||
scrollToItemAtTop(item);
|
||||
textInput.removeOnKeyboardShownListener(this);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -260,7 +316,15 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
}
|
||||
}
|
||||
|
||||
protected void displaySnackbarShort(@StringRes int stringId) {
|
||||
private void scrollToItemAtTop(I item) {
|
||||
int position = adapter.findItemPosition(item);
|
||||
if (position != NO_POSITION) {
|
||||
layoutManager
|
||||
.scrollToPositionWithOffset(position, 0);
|
||||
}
|
||||
}
|
||||
|
||||
protected void displaySnackbar(@StringRes int stringId) {
|
||||
Snackbar snackbar = make(list, stringId, Snackbar.LENGTH_SHORT);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
@@ -278,7 +342,8 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
textInput.showSoftKeyboard();
|
||||
textInput.setHint(replyItem == null ? R.string.forum_new_message_hint :
|
||||
R.string.forum_message_reply_hint);
|
||||
adapter.setReplyItem(replyItem);
|
||||
adapter.setHighlightedItem(
|
||||
replyItem == null ? null : replyItem.getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -286,10 +351,10 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
if (text.trim().length() == 0)
|
||||
return;
|
||||
if (StringUtils.utf8IsTooLong(text, getMaxBodyLength())) {
|
||||
displaySnackbarShort(R.string.text_too_long);
|
||||
displaySnackbar(R.string.text_too_long);
|
||||
return;
|
||||
}
|
||||
I replyItem = adapter.getReplyItem();
|
||||
I replyItem = adapter.getHighlightedItem();
|
||||
UiResultExceptionHandler<I, DbException> handler =
|
||||
new UiResultExceptionHandler<I, DbException>(this) {
|
||||
@Override
|
||||
@@ -299,15 +364,14 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO add proper exception handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
};
|
||||
getController().createAndStoreMessage(text, replyItem, handler);
|
||||
textInput.hideSoftKeyboard();
|
||||
textInput.setVisibility(GONE);
|
||||
textInput.setText("");
|
||||
adapter.setReplyItem(null);
|
||||
adapter.setHighlightedItem(null);
|
||||
}
|
||||
|
||||
protected abstract int getMaxBodyLength();
|
||||
@@ -323,8 +387,7 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
|
||||
@Override
|
||||
public void onExceptionUi(DbException exception) {
|
||||
// TODO add proper exception handling
|
||||
finish();
|
||||
handleDbException(exception);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -334,34 +397,29 @@ public abstract class ThreadListActivity<G extends NamedGroup, A extends ThreadI
|
||||
supportFinishAfterTransition();
|
||||
}
|
||||
|
||||
protected void addItem(final I item, boolean isLocal) {
|
||||
protected void addItem(I item, boolean isLocal) {
|
||||
adapter.incrementRevision();
|
||||
adapter.add(item);
|
||||
if (isLocal && adapter.isVisible(item)) {
|
||||
displaySnackbarShort(getItemPostedString());
|
||||
|
||||
if (isLocal) {
|
||||
displaySnackbar(getItemPostedString());
|
||||
scrollToItemAtTop(item);
|
||||
} else {
|
||||
Snackbar snackbar = Snackbar.make(list,
|
||||
isLocal ? getItemPostedString() : getItemReceivedString(),
|
||||
Snackbar.LENGTH_LONG);
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.setActionTextColor(ContextCompat
|
||||
.getColor(ThreadListActivity.this,
|
||||
R.color.briar_button_positive));
|
||||
snackbar.setAction(R.string.show, new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
adapter.scrollTo(item);
|
||||
}
|
||||
});
|
||||
snackbar.getView().setBackgroundResource(R.color.briar_primary);
|
||||
snackbar.show();
|
||||
updateUnreadCount();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateUnreadCount() {
|
||||
UnreadCount unreadCount = adapter.getUnreadCount();
|
||||
if (LOG.isLoggable(INFO)) {
|
||||
LOG.info("Updating unread count: top=" + unreadCount.top +
|
||||
" bottom=" + unreadCount.bottom);
|
||||
}
|
||||
upButton.setUnreadCount(unreadCount.top);
|
||||
downButton.setUnreadCount(unreadCount.bottom);
|
||||
}
|
||||
|
||||
@StringRes
|
||||
protected abstract int getItemPostedString();
|
||||
|
||||
@StringRes
|
||||
protected abstract int getItemReceivedString();
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,6 @@ import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.threaded.ThreadItemAdapter.ThreadItemListener;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.INVISIBLE;
|
||||
import static android.view.View.VISIBLE;
|
||||
|
||||
@UiThread
|
||||
@@ -17,32 +16,29 @@ import static android.view.View.VISIBLE;
|
||||
public class ThreadPostViewHolder<I extends ThreadItem>
|
||||
extends BaseThreadItemViewHolder<I> {
|
||||
|
||||
private final TextView lvlText, repliesText;
|
||||
private final TextView lvlText;
|
||||
private final View[] lvls;
|
||||
private final View chevron, replyButton;
|
||||
private final View replyButton;
|
||||
|
||||
private final static int[] nestedLineIds = {
|
||||
R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3,
|
||||
R.id.nested_line_4, R.id.nested_line_5
|
||||
};
|
||||
|
||||
public ThreadPostViewHolder(View v) {
|
||||
super(v);
|
||||
|
||||
lvlText = (TextView) v.findViewById(R.id.nested_line_text);
|
||||
repliesText = (TextView) v.findViewById(R.id.replies);
|
||||
int[] nestedLineIds = {
|
||||
R.id.nested_line_1, R.id.nested_line_2, R.id.nested_line_3,
|
||||
R.id.nested_line_4, R.id.nested_line_5
|
||||
};
|
||||
lvls = new View[nestedLineIds.length];
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i] = v.findViewById(nestedLineIds[i]);
|
||||
}
|
||||
chevron = v.findViewById(R.id.chevron);
|
||||
replyButton = v.findViewById(R.id.btn_reply);
|
||||
}
|
||||
|
||||
// TODO improve encapsulation, so we don't need to pass the adapter here
|
||||
@Override
|
||||
public void bind(final ThreadItemAdapter<I> adapter,
|
||||
final ThreadItemListener<I> listener, final I item, int pos) {
|
||||
super.bind(adapter, listener, item, pos);
|
||||
public void bind(final I item, final ThreadItemListener<I> listener) {
|
||||
super.bind(item, listener);
|
||||
|
||||
for (int i = 0; i < lvls.length; i++) {
|
||||
lvls[i].setVisibility(i < item.getLevel() ? VISIBLE : GONE);
|
||||
@@ -54,41 +50,10 @@ public class ThreadPostViewHolder<I extends ThreadItem>
|
||||
lvlText.setVisibility(GONE);
|
||||
}
|
||||
|
||||
int replies = adapter.getReplyCount(item);
|
||||
if (replies == 0) {
|
||||
repliesText.setText("");
|
||||
} else {
|
||||
repliesText.setText(getContext().getResources()
|
||||
.getQuantityString(R.plurals.message_replies, replies,
|
||||
replies));
|
||||
}
|
||||
|
||||
if (item.hasDescendants()) {
|
||||
chevron.setVisibility(VISIBLE);
|
||||
if (item.isShowingDescendants()) {
|
||||
chevron.setSelected(false);
|
||||
} else {
|
||||
chevron.setSelected(true);
|
||||
}
|
||||
chevron.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
chevron.setSelected(!chevron.isSelected());
|
||||
if (chevron.isSelected()) {
|
||||
adapter.hideDescendants(item);
|
||||
} else {
|
||||
adapter.showDescendants(item);
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
chevron.setVisibility(INVISIBLE);
|
||||
}
|
||||
replyButton.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
listener.onReplyClick(item);
|
||||
adapter.scrollTo(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -33,7 +33,7 @@ import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
|
||||
public class UiUtils {
|
||||
|
||||
public static final long MIN_RESOLUTION = MINUTE_IN_MILLIS;
|
||||
public static final long MIN_DATE_RESOLUTION = MINUTE_IN_MILLIS;
|
||||
public static final int TEASER_LENGTH = 320;
|
||||
public static final float GREY_OUT = 0.5f;
|
||||
|
||||
@@ -51,15 +51,16 @@ public class UiUtils {
|
||||
FORMAT_SHOW_DATE | FORMAT_ABBREV_TIME | FORMAT_ABBREV_MONTH;
|
||||
|
||||
long diff = System.currentTimeMillis() - time;
|
||||
if (diff < MIN_RESOLUTION) return ctx.getString(R.string.now);
|
||||
if (diff < MIN_DATE_RESOLUTION) return ctx.getString(R.string.now);
|
||||
if (diff >= DAY_IN_MILLIS && diff < WEEK_IN_MILLIS) {
|
||||
// also show time when older than a day, but newer than a week
|
||||
return DateUtils.getRelativeDateTimeString(ctx, time,
|
||||
MIN_RESOLUTION, WEEK_IN_MILLIS, flags).toString();
|
||||
MIN_DATE_RESOLUTION, WEEK_IN_MILLIS, flags).toString();
|
||||
}
|
||||
// otherwise just show "...ago" or date string
|
||||
return DateUtils.getRelativeTimeSpanString(time,
|
||||
System.currentTimeMillis(), MIN_RESOLUTION, flags).toString();
|
||||
System.currentTimeMillis(),
|
||||
MIN_DATE_RESOLUTION, flags).toString();
|
||||
}
|
||||
|
||||
public static SpannableStringBuilder getTeaser(Context ctx, Spanned body) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.support.v7.widget.RecyclerView.Adapter;
|
||||
import android.util.AttributeSet;
|
||||
@@ -17,14 +19,15 @@ import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_RESOLUTION;
|
||||
import static org.briarproject.briar.android.util.UiUtils.MIN_DATE_RESOLUTION;
|
||||
|
||||
public class BriarRecyclerView extends FrameLayout {
|
||||
|
||||
private static final long DEFAULT_REFRESH_INTERVAL = MIN_RESOLUTION;
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(BriarRecyclerView.class.getName());
|
||||
|
||||
private final Handler handler = new Handler(Looper.getMainLooper());
|
||||
|
||||
private RecyclerView recyclerView;
|
||||
private TextView emptyView;
|
||||
private ProgressBar progressBar;
|
||||
@@ -192,18 +195,20 @@ public class BriarRecyclerView extends FrameLayout {
|
||||
@Override
|
||||
public void run() {
|
||||
LOG.info("Updating Content...");
|
||||
recyclerView.getAdapter().notifyDataSetChanged();
|
||||
postDelayed(refresher, DEFAULT_REFRESH_INTERVAL);
|
||||
Adapter adapter = recyclerView.getAdapter();
|
||||
adapter.notifyItemRangeChanged(0, adapter.getItemCount());
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
}
|
||||
};
|
||||
LOG.info("Adding Handler Callback");
|
||||
postDelayed(refresher, DEFAULT_REFRESH_INTERVAL);
|
||||
handler.postDelayed(refresher, MIN_DATE_RESOLUTION);
|
||||
}
|
||||
|
||||
public void stopPeriodicUpdate() {
|
||||
if (refresher != null) {
|
||||
LOG.info("Removing Handler Callback");
|
||||
removeCallbacks(refresher);
|
||||
handler.removeCallbacks(refresher);
|
||||
refresher = null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
package org.briarproject.briar.android.view;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.res.TypedArray;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
|
||||
@UiThread
|
||||
@NotNullByDefault
|
||||
public class UnreadMessageButton extends FrameLayout {
|
||||
|
||||
private final static int UP = 0, DOWN = 1;
|
||||
|
||||
private final FloatingActionButton fab;
|
||||
private final TextView unread;
|
||||
|
||||
public UnreadMessageButton(Context context) {
|
||||
this(context, null);
|
||||
}
|
||||
|
||||
public UnreadMessageButton(Context context, @Nullable AttributeSet attrs) {
|
||||
this(context, attrs, 0);
|
||||
}
|
||||
|
||||
public UnreadMessageButton(Context context, @Nullable AttributeSet attrs,
|
||||
int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
|
||||
LayoutInflater inflater = (LayoutInflater) context
|
||||
.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
|
||||
inflater
|
||||
.inflate(R.layout.unread_message_button, this, true);
|
||||
|
||||
fab = (FloatingActionButton) findViewById(R.id.fab);
|
||||
unread = (TextView) findViewById(R.id.unreadCountView);
|
||||
|
||||
TypedArray attributes = context.obtainStyledAttributes(attrs,
|
||||
R.styleable.UnreadMessageButton);
|
||||
int direction = attributes
|
||||
.getInteger(R.styleable.UnreadMessageButton_direction, DOWN);
|
||||
setDirection(direction);
|
||||
attributes.recycle();
|
||||
|
||||
setUnreadCount(0);
|
||||
}
|
||||
|
||||
private void setDirection(int direction) {
|
||||
if (direction == UP) {
|
||||
fab.setImageResource(R.drawable.chevron_up_white);
|
||||
} else if (direction == DOWN) {
|
||||
fab.setImageResource(R.drawable.chevron_down_white);
|
||||
} else {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
}
|
||||
|
||||
public void setUnreadCount(int count) {
|
||||
if (count == 0) {
|
||||
fab.setVisibility(GONE);
|
||||
// fab.hide();
|
||||
unread.setVisibility(GONE);
|
||||
} else {
|
||||
// FIXME: Use animations when upgrading to support library 24.2.0
|
||||
// https://code.google.com/p/android/issues/detail?id=216469
|
||||
fab.setVisibility(VISIBLE);
|
||||
// if (!fab.isShown()) fab.show();
|
||||
unread.setVisibility(VISIBLE);
|
||||
unread.setText(String.valueOf(count));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
<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="#FFFFFFFF"
|
||||
android:pathData="M7.41,7.84L12,12.42l4.59,-4.58L18,9.25l-6,6 -6,-6z"/>
|
||||
</vector>
|
||||
9
briar-android/src/main/res/drawable/chevron_up_white.xml
Normal file
9
briar-android/src/main/res/drawable/chevron_up_white.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<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="#FFFFFFFF"
|
||||
android:pathData="M7.41,15.41L12,10.83l4.59,4.58L18,14l-6,-6 -6,6z"/>
|
||||
</vector>
|
||||
@@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<item
|
||||
android:drawable="@color/forum_cell_highlight"
|
||||
android:state_activated="true"/>
|
||||
<item
|
||||
android:drawable="@color/window_background"/>
|
||||
|
||||
</selector>
|
||||
@@ -11,17 +11,7 @@
|
||||
android:orientation="vertical"
|
||||
tools:context=".android.reporting.DevReportActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/BriarToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<org.briarproject.briar.android.view.BriarRecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1"
|
||||
app:emptyText="@string/no_forum_posts"
|
||||
app:scrollToEnd="false"/>
|
||||
|
||||
<org.briarproject.briar.android.view.TextInputView
|
||||
android:id="@+id/text_input_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/button_bar_background"
|
||||
android:elevation="@dimen/margin_tiny"
|
||||
app:hint="@string/forum_new_message_hint"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,18 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".android.keyagreement.KeyAgreementActivity">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragmentContainer"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -2,8 +2,10 @@
|
||||
<android.support.v4.widget.DrawerLayout
|
||||
android:id="@+id/drawer_layout"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".android.navdrawer.NavDrawerActivity">
|
||||
|
||||
<!-- The first child(root) is the content view -->
|
||||
<LinearLayout
|
||||
@@ -11,17 +13,7 @@
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/BriarToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragmentContainer"
|
||||
|
||||
@@ -1,26 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/BriarToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragmentContainer"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/window_background"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
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="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
tools:context=".android.forum.ForumActivity">
|
||||
|
||||
<include layout="@layout/toolbar"/>
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<org.briarproject.briar.android.view.BriarRecyclerView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:emptyText="@string/no_forum_posts"
|
||||
app:scrollToEnd="false"/>
|
||||
|
||||
<org.briarproject.briar.android.view.UnreadMessageButton
|
||||
android:id="@+id/upButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="top|right"
|
||||
app:direction="up"/>
|
||||
|
||||
<org.briarproject.briar.android.view.UnreadMessageButton
|
||||
android:id="@+id/downButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|right"
|
||||
app:direction="down"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<org.briarproject.briar.android.view.TextInputView
|
||||
android:id="@+id/text_input_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/button_bar_background"
|
||||
android:elevation="@dimen/margin_tiny"
|
||||
app:hint="@string/forum_new_message_hint"/>
|
||||
|
||||
</LinearLayout>
|
||||
@@ -43,7 +43,7 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignEnd="@+id/text"
|
||||
android:layout_alignRight="@+id/text"
|
||||
android:layout_below="@+id/declineButton"
|
||||
android:layout_below="@+id/acceptButton"
|
||||
android:layout_marginTop="@dimen/message_bubble_timestamp_margin"
|
||||
android:textColor="@color/private_message_date"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
@@ -57,6 +57,7 @@
|
||||
android:layout_alignEnd="@+id/text"
|
||||
android:layout_alignRight="@+id/text"
|
||||
android:layout_below="@+id/text"
|
||||
android:layout_marginBottom="-10dp"
|
||||
android:text="@string/accept"/>
|
||||
|
||||
<Button
|
||||
@@ -64,8 +65,8 @@
|
||||
style="@style/BriarButtonFlat.Negative"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/text"
|
||||
android:layout_marginBottom="-15dp"
|
||||
android:layout_alignBottom="@+id/acceptButton"
|
||||
android:layout_alignTop="@+id/acceptButton"
|
||||
android:layout_toLeftOf="@+id/acceptButton"
|
||||
android:layout_toStartOf="@+id/acceptButton"
|
||||
android:text="@string/decline"/>
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
tools:text="Dec 24"/>
|
||||
|
||||
<View
|
||||
style="@style/Divider.ForumList"
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/postCountView"/>
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
<View
|
||||
android:id="@+id/divider"
|
||||
style="@style/Divider.ForumList"
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/statusView"
|
||||
|
||||
@@ -6,23 +6,14 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<View
|
||||
android:id="@+id/top_divider"
|
||||
style="@style/Divider.ForumList"
|
||||
android:layout_alignParentTop="true"/>
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/top_divider"
|
||||
android:layout_marginBottom="@dimen/margin_small"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
android:layout_marginTop="@dimen/margin_medium"
|
||||
android:layout_margin="@dimen/margin_medium"
|
||||
android:textColor="@color/briar_text_secondary"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
android:textStyle="italic"
|
||||
@@ -32,9 +23,12 @@
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/info"
|
||||
android:layout_alignLeft="@+id/text"
|
||||
android:layout_alignTop="@+id/info"
|
||||
android:layout_below="@+id/text"
|
||||
android:layout_marginRight="@dimen/margin_small"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
android:scaleType="center"
|
||||
tools:ignore="ContentDescription"
|
||||
tools:src="@drawable/ic_visibility"/>
|
||||
|
||||
@@ -45,6 +39,7 @@
|
||||
android:layout_alignEnd="@+id/text"
|
||||
android:layout_alignRight="@+id/text"
|
||||
android:layout_below="@+id/text"
|
||||
android:layout_marginBottom="@dimen/margin_medium"
|
||||
android:layout_toRightOf="@+id/icon"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="24dp"
|
||||
@@ -57,10 +52,10 @@
|
||||
<org.briarproject.briar.android.view.AuthorView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_size"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@+id/text"
|
||||
android:layout_below="@+id/info"
|
||||
android:gravity="center"
|
||||
android:layout_toLeftOf="@+id/optionsButton"
|
||||
app:persona="commenter"/>
|
||||
|
||||
<Button
|
||||
@@ -68,11 +63,16 @@
|
||||
style="@style/BriarButtonFlat.Positive.Tiny"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/author"
|
||||
android:layout_alignEnd="@+id/text"
|
||||
android:layout_alignRight="@+id/text"
|
||||
android:layout_alignTop="@+id/author"
|
||||
android:layout_toRightOf="@+id/author"
|
||||
android:layout_below="@+id/info"
|
||||
android:gravity="right|center_vertical"
|
||||
android:text="@string/options"/>
|
||||
|
||||
<View
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_below="@+id/author"
|
||||
android:layout_marginTop="@dimen/margin_medium"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
@@ -81,7 +81,7 @@
|
||||
android:text="@string/decline"/>
|
||||
|
||||
<View
|
||||
style="@style/Divider.ForumList"
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/acceptButton"/>
|
||||
|
||||
@@ -113,7 +113,7 @@
|
||||
tools:text="This is a description of the RSS feed. It can be several lines long, but it can also not exist at all if it is not present in the feed itself."/>
|
||||
|
||||
<View
|
||||
style="@style/Divider.ForumList"
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@+id/descriptionView"
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@drawable/list_item_thread_background"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal">
|
||||
|
||||
@@ -61,23 +62,20 @@
|
||||
android:background="@drawable/level_indicator_circle"
|
||||
android:gravity="center"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:visibility="gone"
|
||||
/>
|
||||
android:visibility="gone"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_weight="1">
|
||||
|
||||
<org.thoughtcrime.securesms.components.emoji.EmojiTextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingRight="@dimen/margin_medium"
|
||||
android:paddingTop="@dimen/margin_medium"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:textColor="@color/briar_text_primary"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="@dimen/text_size_medium"
|
||||
@@ -86,26 +84,12 @@
|
||||
<org.briarproject.briar.android.view.AuthorView
|
||||
android:id="@+id/author"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="@dimen/button_size"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignLeft="@id/text"
|
||||
android:layout_below="@id/text"
|
||||
android:gravity="center"
|
||||
app:persona="commenter"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/replies"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/author"
|
||||
android:layout_alignTop="@+id/author"
|
||||
android:layout_marginLeft="@dimen/margin_medium"
|
||||
android:layout_toLeftOf="@+id/btn_reply"
|
||||
android:layout_toRightOf="@+id/author"
|
||||
android:ellipsize="end"
|
||||
android:gravity="right|end|center_vertical"
|
||||
android:maxLines="1"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:textSize="@dimen/text_size_tiny"
|
||||
tools:text="2 replies"/>
|
||||
app:persona="commenter"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/btn_reply"
|
||||
@@ -113,31 +97,18 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/author"
|
||||
android:layout_toLeftOf="@+id/chevron"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_below="@+id/text"
|
||||
android:layout_marginRight="@dimen/margin_medium"
|
||||
android:text="@string/btn_reply"
|
||||
android:textSize="@dimen/text_size_tiny"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/chevron"
|
||||
android:layout_width="@dimen/button_size"
|
||||
android:layout_height="@dimen/button_size"
|
||||
android:layout_alignBottom="@+id/author"
|
||||
android:layout_alignParentRight="true"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:padding="@dimen/margin_medium"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/selector_chevron"/>
|
||||
|
||||
<View
|
||||
android:id="@+id/top_divider"
|
||||
style="@style/Divider.ForumList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="@dimen/margin_separator"
|
||||
style="@style/Divider.ThreadItem"
|
||||
android:layout_alignLeft="@id/text"
|
||||
android:layout_alignParentTop="true"/>
|
||||
android:layout_below="@+id/author"
|
||||
android:layout_marginTop="@dimen/margin_medium"/>
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user